Skip to Content
Course content

263: NavigableMap and NavigableSet

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're managing a massive physical archive of records, organized strictly by date. If I ask you for the record from exactly January 1st, 2023, and it's not there, a standard sorted map would just tell you "not found." But in the real world, you'd probably say, "I don't have the 1st, but I have the 31st of December—that's the closest one before it—and the 2nd of January, which is the closest one after."

That's exactly what NavigableMap and NavigableSet bring to the table. They take the sorting we already have in SortedMap and SortedSet and add "navigation" capabilities. Instead of just checking if a key exists, you can now ask for the "nearest neighbor" of a key, or grab a slice of the collection that falls between two specific points.

Finding the Nearest Neighbor

I've seen a lot of developers try to find the "closest" key by iterating through the whole map with a loop. Please, don't do that. It's inefficient and clutters your code. NavigableMap gives us four specific methods to handle this. Let's look at them using a tax bracket example, where the key is the income threshold and the value is the tax rate.

NavigableMap<Integer, Double> taxBrackets = new TreeMap<>();
taxBrackets.put(0, 0.10);
taxBrackets.put(11000, 0.12);
taxBrackets.put(44725, 0.22);
taxBrackets.put(95375, 0.24);

// I want to know which bracket a person making $50,000 falls into.
// floorKey returns the greatest key less than or equal to the given key.
Integer bracket = taxBrackets.floorKey(50000); 
System.out.println("Your bracket starts at: " + bracket); // Output: 44725

// ceilingKey returns the smallest key greater than or equal to the given key.
Integer nextBracket = taxBrackets.ceilingKey(50000); 
System.out.println("The next bracket starts at: " + nextBracket); // Output: 95375

The distinction between floor/ceiling and lower/higher is subtle but important: floor and ceiling are inclusive (they return the key itself if it matches exactly), while lower and higher are strictly exclusive. If you're looking for "the one right after this, regardless of whether this one exists," use higherKey.

Slicing the Map and Set

Sometimes you don't just want one key; you want a range. You might want every record from the start of Q1 to the end of Q2. NavigableMap allows you to create "views" of your data. These aren't copies of the map—they are windows into the original data. If you change the original map, the view changes too.

// Get everything from $11,000 up to (but not including) $95,375
SortedMap<Integer, Double> middleClass = taxBrackets.subMap(11000, 95375);

// Get everything from $44,725 to the very end of the map
SortedMap<Integer, Double> highEarners = taxBrackets.tailMap(44725);

The same logic applies to NavigableSet. If you have a set of available appointment timestamps, you can use ceiling() to find the first available slot after the current time, or descendingSet() to flip the entire set and process your appointments in reverse order without having to manually sort a list.

The Convenience of Polling

One of my favorite additions in the NavigableMap interface is the "poll" methods. In a standard Map, removing the first or last element is a bit of a chore—you have to find the key first, then call remove(). pollFirstEntry() and pollLastEntry() do both in one shot.

This is incredibly useful when you're implementing something like a priority queue or a task scheduler where you always want to process the highest (or lowest) priority item and immediately remove it from the pending list so it doesn't get processed twice.




📋 Practical Task

Build a Shipping Weight Tier Lookup

You are building a shipping calculator for a logistics company. Shipping costs are based on weight tiers. If a package's weight falls between two tiers, it is charged the rate of the lower tier (the floor).

Requirements:

  • Create a NavigableMap<Double, Double> (Weight in kg $\rightarrow$ Price in USD).
  • Populate it with these tiers:
    • 0.0 kg: $5.00
    • 1.0 kg: $8.00
    • 5.0 kg: $15.00
    • 10.0 kg: $25.00
    • 20.0 kg: $40.00
  • Write a method calculateShipping(double weight) that uses floorEntry() to find the correct price for a given weight.
  • Test it with three weights: 0.5 (should be $5.00), 4.2 (should be $8.00), and 25.0 (should be $40.00).
  • Bonus: Use descendingMap() to print the tiers from heaviest to lightest.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.