← Kursa Dön
📄 Text · 10 min

SortedSet, SortedMap, NavigableSet

Bazen sadece sıralı tutmak yetmez — sıralı verinin alt kümelerine erişmek istersin. "50-100 arasındaki puanlar neler?" veya "Ali'den önceki isimler kim?" gibi sorulara cevap vermek istersin. İşte SortedSet, SortedMap ve NavigableSet/NavigableMap bu ihtiyaç için var.

Bunu bir ansiklopedi gibi düşün. Harflere göre sıralı, herhangi bir harften diğerine kadar olan kısmı kolayca kesip alabilirsin. "D'den K'ya kadar olan maddeler" — bu bir range query ve sorted koleksiyonlar tam bunu yapıyor.

SortedSet Interface

SortedSet, elemanları sıralı tutan bir Set. TreeSet en yaygın implementasyonu.

SortedSet<Integer> scores = new TreeSet<>();
scores.add(85);
scores.add(92);
scores.add(67);
scores.add(78);
scores.add(95);
scores.add(45);
// Otomatik sıralı: [45, 67, 78, 85, 92, 95]

// İlk ve son eleman
System.out.println(scores.first()); // 45
System.out.println(scores.last());  // 95

// Alt küme operasyonları
SortedSet<Integer> head = scores.headSet(80);     // 80'den küçükler: [45, 67, 78]
SortedSet<Integer> tail = scores.tailSet(80);     // 80 ve üstü: [85, 92, 95]
SortedSet<Integer> sub = scores.subSet(60, 90);   // 60 (dahil) - 90 (hariç): [67, 78, 85]

Önemli: headSet, tailSet, subSet view döndürür — kopya değil. Orijinal değişirse view da değişir.

SortedSet<Integer> tailView = scores.tailSet(80);
System.out.println(tailView); // [85, 92, 95]

scores.add(88);
System.out.println(tailView); // [85, 88, 92, 95] — otomatik güncellendi!

tailView.add(99);
System.out.println(scores.contains(99)); // true — orijinale de eklendi!

⚠️ View sınırları: View'a, range dışı eleman eklemeye çalışırsan IllegalArgumentException alırsın.

SortedSet<Integer> sub = scores.subSet(60, 90);
sub.add(75);  // OK — 60-90 arasında
// sub.add(50); // IllegalArgumentException! Range dışı
// sub.add(95); // IllegalArgumentException! Range dışı

SortedMap Interface

SortedMap, key'leri sıralı tutan bir Map. TreeMap en yaygın implementasyonu.

SortedMap<String, Integer> ages = new TreeMap<>();
ages.put("Zeynep", 28);
ages.put("Ali", 25);
ages.put("Mehmet", 30);
ages.put("Can", 22);
ages.put("Deniz", 27);
// Key'lere göre sıralı: {Ali=25, Can=22, Deniz=27, Mehmet=30, Zeynep=28}

// İlk ve son
System.out.println(ages.firstKey()); // "Ali"
System.out.println(ages.lastKey());  // "Zeynep"

// Alt küme
SortedMap<String, Integer> head = ages.headMap("Deniz");     // Deniz'den öncekiler
// {Ali=25, Can=22}

SortedMap<String, Integer> tail = ages.tailMap("Deniz");     // Deniz ve sonrası
// {Deniz=27, Mehmet=30, Zeynep=28}

SortedMap<String, Integer> sub = ages.subMap("Can", "Mehmet"); // Can (dahil) - Mehmet (hariç)
// {Can=22, Deniz=27}

NavigableSet, SortedSet'i genişletir. Daha güçlü navigasyon method'ları ekler: "en yakın eleman" bulma, ters sırada gezinme, dahil/hariç kontrollü range.

NavigableSet<Integer> nums = new TreeSet<>(Set.of(10, 20, 30, 40, 50, 60));

// floor — verilen değere eşit veya küçük en büyük eleman
System.out.println(nums.floor(35));   // 30
System.out.println(nums.floor(30));   // 30 (eşit dahil)
System.out.println(nums.floor(5));    // null (yok)

// ceiling — verilen değere eşit veya büyük en küçük eleman
System.out.println(nums.ceiling(35)); // 40
System.out.println(nums.ceiling(30)); // 30 (eşit dahil)

// lower — verilen değerden kesinlikle küçük en büyük eleman
System.out.println(nums.lower(30));   // 20 (30 hariç)

// higher — verilen değerden kesinlikle büyük en küçük eleman
System.out.println(nums.higher(30));  // 40 (30 hariç)

Bu dört method'un farkı:

MethodDahil mi?Yön
floor(e)✅ (≤)Aşağı
lower(e)❌ (<)Aşağı
ceiling(e)✅ (≥)Yukarı
higher(e)❌ (>)Yukarı
// pollFirst / pollLast — çıkararak al
NavigableSet<Integer> set = new TreeSet<>(Set.of(1, 2, 3, 4, 5));
System.out.println(set.pollFirst()); // 1 — en küçüğü çıkar
System.out.println(set.pollLast());  // 5 — en büyüğü çıkar
System.out.println(set);             // [2, 3, 4]

// descendingSet — ters sırada view
NavigableSet<Integer> desc = set.descendingSet();
System.out.println(desc); // [4, 3, 2]

// descendingIterator — ters sırada iterasyon
Iterator<Integer> descIt = set.descendingIterator();
while (descIt.hasNext()) {
    System.out.println(descIt.next()); // 4, 3, 2
}

SortedSet'in subSet, headSet, tailSet method'larında sınırlar fixed'dı (fromInclusive, toExclusive). NavigableSet'te sen seçersin:

NavigableSet<Integer> nums = new TreeSet<>(Set.of(10, 20, 30, 40, 50));

// subSet(from, fromInclusive, to, toInclusive)
nums.subSet(20, true, 40, true);   // [20, 30, 40] — ikisi de dahil
nums.subSet(20, false, 40, false); // [30]          — ikisi de hariç
nums.subSet(20, true, 40, false);  // [20, 30]      — from dahil, to hariç

// headSet(to, inclusive)
nums.headSet(30, true);   // [10, 20, 30] — 30 dahil
nums.headSet(30, false);  // [10, 20]     — 30 hariç

// tailSet(from, inclusive)
nums.tailSet(30, true);   // [30, 40, 50] — 30 dahil
nums.tailSet(30, false);  // [40, 50]     — 30 hariç

NavigableMap, NavigableSet'in Map karşılığı. TreeMap implement eder.

NavigableMap<Integer, String> scores = new TreeMap<>();
scores.put(50, "Başarısız");
scores.put(60, "Geçer");
scores.put(70, "Orta");
scores.put(80, "İyi");
scores.put(90, "Pekiyi");

// floor/ceiling/lower/higher — key bazlı
Map.Entry<Integer, String> entry;

entry = scores.floorEntry(75);   // 70=Orta (≤ 75)
entry = scores.ceilingEntry(75); // 80=İyi (≥ 75)
entry = scores.lowerEntry(70);   // 60=Geçer (< 70)
entry = scores.higherEntry(70);  // 80=İyi (> 70)

// Sadece key
Integer key = scores.floorKey(75);   // 70
Integer key2 = scores.ceilingKey(75); // 80

// İlk ve son
entry = scores.firstEntry();     // 50=Başarısız
entry = scores.lastEntry();      // 90=Pekiyi

// Çıkararak al
entry = scores.pollFirstEntry(); // 50=Başarısız — map'ten silindi
entry = scores.pollLastEntry();  // 90=Pekiyi — map'ten silindi

TreeMap Range Operations

NavigableMap<String, Double> prices = new TreeMap<>();
prices.put("Elma", 5.0);
prices.put("Armut", 7.5);
prices.put("Muz", 12.0);
prices.put("Portakal", 8.0);
prices.put("Üzüm", 15.0);

// Alt harita — "E" ile "P" arasındaki ürünler
NavigableMap<String, Double> sub = prices.subMap("E", true, "P", false);
System.out.println(sub); // {Elma=5.0, Muz=12.0} — alfabetik E-P arası

// Ters sırada
NavigableMap<String, Double> desc = prices.descendingMap();
System.out.println(desc); // {Üzüm=15.0, Portakal=8.0, Muz=12.0, ...}

Gerçek Dünya: Not Sistemi

public class GradeSystem {
    private final NavigableMap<Integer, String> gradeMap = new TreeMap<>();
    
    public GradeSystem() {
        gradeMap.put(0, "F");
        gradeMap.put(50, "D");
        gradeMap.put(60, "C");
        gradeMap.put(70, "B");
        gradeMap.put(80, "A");
        gradeMap.put(90, "A+");
    }
    
    public String getGrade(int score) {
        Map.Entry<Integer, String> entry = gradeMap.floorEntry(score);
        return entry != null ? entry.getValue() : "F";
    }
    
    public static void main(String[] args) {
        GradeSystem gs = new GradeSystem();
        System.out.println(gs.getGrade(85)); // A
        System.out.println(gs.getGrade(72)); // B
        System.out.println(gs.getGrade(55)); // D
        System.out.println(gs.getGrade(45)); // F
    }
}

💡 İpucu: floorEntry() range-based lookup için mükemmel. Tam eşleşme aramak yerine "en yakın alt sınır" bulur. Not sistemi, vergi dilimleri, kargo fiyatlandırması gibi aralık bazlı mantıklar için ideal.

Gerçek Dünya: Zaman Aralığı Sorguları

public class EventLog {
    private final NavigableMap<LocalDateTime, String> events = new TreeMap<>();
    
    public void log(String event) {
        events.put(LocalDateTime.now(), event);
    }
    
    // Son 1 saatteki event'ler
    public NavigableMap<LocalDateTime, String> getLastHour() {
        LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
        return events.tailMap(oneHourAgo, true);
    }
    
    // İki tarih arasındaki event'ler
    public NavigableMap<LocalDateTime, String> getBetween(
            LocalDateTime from, LocalDateTime to) {
        return events.subMap(from, true, to, true);
    }
}

Özet

  • SortedSet/SortedMap elemanları sıralı tutar — first(), last(), headSet(), tailSet(), subSet() ile range erişim

  • NavigableSet/NavigableMap daha güçlü: floor(), ceiling(), lower(), higher() ile "en yakın eleman" bulma

  • headSet/tailSet/subSet view döndürür (kopya değil) — orijinal değişirse view da değişir, view'a range dışı eleman eklenemez

  • NavigableSet'te subSet(from, fromInclusive, to, toInclusive) ile dahil/hariç kontrolü senin elinde

  • floorEntry()/ceilingEntry() range-based lookup için mükemmel — not sistemi, vergi dilimi, fiyat aralığı senaryoları

  • Tüm sorted/navigable yapılar arka planda TreeSet/TreeMap (Red-Black Tree) kullanır — operasyonlar O(log n)