Java'da sıralı dizi listesi


85

Buna hızlı bir cevap bulamadığım için şaşkınım. Esasen Java'da java.util.Listarayüzü uygulayan , ancak üyelerini sıralı bir düzende depolayan bir veri yapısı arıyorum . Normal ArrayListkullanabileceğinizi ve Collections.sort()üzerinde kullanabileceğinizi biliyorum, ancak ara sıra listemden üyeler ekleyip sık sık geri çağırdığım bir senaryom var ve her üye aldığımda bunu sıralamak istemiyorum. yenisi eklendi. Birisi beni JDK'da veya hatta 3. parti kitaplıklarda var olan böyle bir şeye yönlendirebilir mi?

DÜZENLE : Veri yapısının kopyaları koruması gerekecek.

CEVAP ÖZETİ : Bütün bunları çok ilginç buldum ve çok şey öğrendim. Özellikle Aioobe, yukarıdaki gereksinimlerime ulaşma konusundaki kararlılığından bahsetmeyi hak ediyor (çoğunlukla kopyaları destekleyen sıralı bir java.util.List uygulaması). Onun cevabını, sorduğum şey için en doğru olarak kabul ettim ve sorduğum şey tam olarak ihtiyacım olan şey olmasa bile aradığım şeyin sonuçlarını kışkırtır.

İstediğim şeyle ilgili problem Liste arayüzünün kendisinde ve bir arayüzdeki isteğe bağlı yöntemler konseptinde yatıyor. Javadoc'tan alıntı yapmak için:

Bu arayüzün kullanıcısı, her bir elemanın listenin neresine eklendiği konusunda kesin kontrole sahiptir.

Sıralanmış bir listeye ekleme, ekleme noktası üzerinde kesin kontrole sahip değildir. Ardından, bazı yöntemleri nasıl kullanacağınızı düşünmelisiniz. addÖrneğin alın :

genel boolean ekleme (Nesne o)

 Appends the specified element to the end of this list (optional operation).

Artık her ikisinin de rahatsız edici durumda kaldınız: 1) Sözleşmeyi addbozmak ve toplama işleminin sıralı bir sürümünü uygulamak 2) Listenin sonuna bir öğe eklemek, sıralı sıranızı bozmak 3) Atarak add(isteğe bağlı olarak) dışarı çıkmak bir UnsupportedOperationExceptionve sıralı bir sırayla öğeleri ekleyen başka bir yöntemi uygulamak.

Seçenek 3 muhtemelen en iyisidir, ancak kullanamayacağınız bir ekleme yöntemine ve arayüzde olmayan başka bir sıralı Ekleme yöntemine sahip olmayı tatsız buluyorum.

Diğer ilgili çözümler (belirli bir sıra olmadan):

  • java.util.PriorityQueue , muhtemelen ihtiyacım olana istediğimden daha yakın. Bir kuyruk, benim durumumda bir nesne koleksiyonunun en kesin tanımı değildir, ancak işlevsel olarak ihtiyacım olan her şeyi yapar.
  • net.sourceforge.nite.util.SortedList . Bununla birlikte, bu uygulama, add(Object obj)yöntemdeki sıralamayı uygulayarak Liste arayüzünün sözleşmesini bozar ve tuhaf bir şekilde bunun için hiçbir etkisi yoktur add(int index, Object obj). Genel fikir birliği, throw new UnsupportedOperationException()bu senaryoda daha iyi bir seçim olabileceğini öne sürüyor .
  • Guava'nın TreeMultiSet'i Yinelemeleri destekleyen bir set uygulaması
  • ca.odell.glazedlists.SortedList Bu sınıf, javadoc'unda uyarı ile birlikte gelir:Warning: This class breaks the contract required by List

4
Ara sıra ekliyor ve sık sık okuyorsanız, neden yerleştirme sırasında sıralamıyorsunuz?
serg

Yanıtlar:


62

Minimalist Çözüm

İşte "minimal" bir çözüm.

class SortedArrayList<T> extends ArrayList<T> {

    @SuppressWarnings("unchecked")
    public void insertSorted(T value) {
        add(value);
        Comparable<T> cmp = (Comparable<T>) value;
        for (int i = size()-1; i > 0 && cmp.compareTo(get(i-1)) < 0; i--)
            Collections.swap(this, i, i-1);
    }
}

Ek, doğrusal zamanda çalışır, ancak yine de bir ArrayList kullanarak elde edeceğiniz şey budur (eklenen öğenin sağındaki tüm öğelerin bir şekilde kaydırılması gerekir).

Karşılaştırılamaz bir şey eklemek ClassCastException'da sonuçlanır. (Bu tarafından alınan yaklaşım PriorityQueueayrıca: . Bir öncelik sırası da () ClassCastException neden olabilir Böylece olmayan benzer nesnelerin sokulmasına izin vermez doğal sipariş dayanarak )

Geçersiz kılma List.add

Öğeleri sıralı bir şekilde eklemek için geçersiz kılmanın List.add(veya List.addAllbu nedenle) arayüz belirtiminin doğrudan ihlali olacağını unutmayın . Ne olabilir yapmak, bir atmak için bu yöntemi geçersiz etmektir UnsupportedOperationException.

Belgelerden List.add:

boolean add(E e)
    Belirtilen öğeyi bu listenin sonuna ekler (isteğe bağlı işlem).

Aynı mantık add, addAllve öğesinin her iki sürümü için de geçerlidir set. (Hepsi liste arayüzüne göre isteğe bağlı işlemlerdir.)


Bazı testler

SortedArrayList<String> test = new SortedArrayList<String>();

test.insertSorted("ddd");    System.out.println(test);
test.insertSorted("aaa");    System.out.println(test);
test.insertSorted("ccc");    System.out.println(test);
test.insertSorted("bbb");    System.out.println(test);
test.insertSorted("eee");    System.out.println(test);

.... baskılar:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]

İyi bir başlangıç, ancak add veya addall'ı çağırmak, üyeleri sıralanmamış bir şekilde ekleyecektir.
Chris Knight

Evet. Bunları listeye eklemek dışında her şey, Liste arayüzünün doğrudan ihlali olur. Güncellenen cevabıma bakın.
aioobe

@aioobe İyi nokta. Ancak bir arayüz yönteminin desteklenmeyen bir işlemi bir kod kokusu değil midir? Doğru yol ArrayList'i genişletmek değil, List'i uygulamak olabilir, ancak o zaman bile List bu amaç için tasarlanmamış olabilir. Liste için Javadoc'tan: The user of this interface has precise control over where in the list each element is insertedbu, öğeleri sıralı bir şekilde eklemek için en iyi açıklama değildir ve yine de add(int index, Object obj)arayüz yöntemiyle uğraşmanız gerekir. Bu sorunlar muhtemelen List'in neden sıralı bir şekilde uygulanmadığını açıklıyor.
Chris Knight

Şey, operasyon bir sebepten dolayı isteğe bağlıdır. .addSortedArrayList üzerinde çalışırken UnsupportedExceptionOperation alırsam şaşırmam. Evet, addAll ve set'in her iki sürümü için de aynı mantık geçerlidir. (Bunların tümü, liste arayüzüne göre isteğe bağlı işlemlerdir.)
aioobe

Ah, isteğe bağlı operasyonlar olduğunu bilmiyordum. Arsa kalınlaşıyor ...;)
Chris Knight

10

7
bu bir Liste değil, yani rastgele erişim yok.
Thilo

1
Sıraya dayalı bir öncelik yığınıdır, a List'i uygulamaz.
zengr

3
Elbette, sıralama düzenini koruyan bir listeyle, dizinler her zaman değişir, bu nedenle muhtemelen zaten rasgele erişime gerek yoktur.
Thilo

5
@Qwerky, kesin cevabın her zaman en iyi cevap olmadığını veya OP'nin aslında sonra geldiği cevabın olmadığını unutmayın.
aioobe

3
öncelik sırası, yinelemede sıralı sıra sağlamaz.
marcorossi

6

SortedList'e bir göz atın

Bu sınıf sıralı bir liste uygular. İki nesneyi karşılaştırabilen ve nesneleri buna göre sıralayabilen bir karşılaştırıcı ile oluşturulmuştur. Listeye bir nesne eklediğinizde, doğru yere yerleştirilir. Karşılaştırıcıya göre eşit olan nesneler bu listeye eklendikleri sırayla listede olacaktır. Yalnızca karşılaştırıcının karşılaştırabileceği nesneleri ekleyin.


Liste halihazırda karşılaştırıcıya eşit nesneler içeriyorsa, yeni nesne bu diğer nesnelerin hemen arkasına eklenecektir.


5
Bu iyi görünüyor, ama aynı zamanda hatalı görünüyor: addAll'ın her iki sürümünde de geçersiz kılma yoktur, bu nedenle liste bunları çağırdıktan sonra sıralanmaz.
Tom Anderson

3
Ve add yönteminin "etkisi yoktur". Kullanılamıyorsa, UnsupportedOperationException oluşturmayı tercih etmelidir.
Thilo

@Tom Anderson @Thilo, ikinize de katılıyorum.
jmj

1
İlginç, ancak gelecekte birisinin addAll()tüm öğeleri sıralı bir şekilde kullanması ve kullanması konusunda temkinli davranıyorum . UnsupportedOperationException ile de anlaşın.
Chris Knight

1
Bu listeye eklemenin zaman karmaşıklığı nedir?
shrini1000

6

Deneyebilirsin Guava en TreeMultiSet .

 Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
 System.out.println(ms);

+1. Bu harika bir kütüphane. MultiSetA collection that supports order-independent equality, like Set, but may have duplicate elements
Shervin Asgari

5

Aioobe'nin yaklaşımı, gidilecek yoldur. Yine de çözümüne göre aşağıdaki iyileştirmeyi önermek isterim.

class SortedList<T> extends ArrayList<T> {

    public void insertSorted(T value) {
        int insertPoint = insertPoint(value);
        add(insertPoint, value);
    }

    /**
     * @return The insert point for a new value. If the value is found the insert point can be any
     * of the possible positions that keeps the collection sorted (.33 or 3.3 or 33.).
     */
    private int insertPoint(T key) {
        int low = 0;
        int high = size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = (Comparable<T>) get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else {
                return mid; // key found
            }
        }

        return low;  // key not found
    }
}

aioobe'nin çözümü büyük listeleri kullanırken çok yavaşlıyor. Listenin sıralı olduğu gerçeğini kullanmak, ikili aramayı kullanarak yeni değerler için ekleme noktasını bulmamızı sağlar.

Kompozisyonu kalıtım yerine de kullanırdım.

SortedList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

4

Listeler genellikle öğelerin eklendiği sırayı korur. Kesinlikle bir listeye mi ihtiyacınız var , yoksa sıralı bir küme (örneğin TreeSet<E>) sizin için uygun mu? Temel olarak, kopyaları korumanız gerekiyor mu?


2
Teşekkürler Jon, ama kopyaları
Chris Knight


1

Herhangi bir öğe eklendikten sonra ArrayList'i alt sınıflayabilir ve Collections.sort'u (bu) çağırabilirsiniz - bunu yapmak için add'nin iki sürümünü ve addAll'ın iki sürümünü geçersiz kılmanız gerekir.

Performans, öğeleri doğru yere yerleştiren daha akıllı bir uygulama kadar iyi olmaz, ancak işi yapar. Listeye ek nadiren gelirse, listedeki tüm işlemler üzerinden amorti edilen maliyet düşük olmalıdır.


1

Bunun gibi yeni bir sınıf oluşturun:

public class SortedList<T> extends ArrayList<T> {

private final Comparator<? super T> comparator;

public SortedList() {
    super();
    this.comparator = null;
}

public SortedList(Comparator<T> comparator) {
    super();
    this.comparator = comparator;
}

@Override
public boolean add(T item) {
    int index = comparator == null ? Collections.binarySearch((List<? extends Comparable<? super T>>)this, item) :
            Collections.binarySearch(this, item, comparator);
    if (index < 0) {
        index = index * -1 - 2;
    }
    super.add(index+1, item);
    return true;
}

@Override
public void add(int index, T item) {
    throw new UnsupportedOperationException("'add' with an index is not supported in SortedArrayList");
}

@Override
public boolean addAll(Collection<? extends T> items) {
    boolean allAdded = true;
    for (T item : items) {
        allAdded = allAdded && add(item);
    }
    return allAdded;
}

@Override
public boolean addAll(int index, Collection<? extends T> items) {
    throw new UnsupportedOperationException("'addAll' with an index is not supported in SortedArrayList");
}

}

Bunu şu şekilde test edebilirsiniz:

    List<Integer> list = new SortedArrayList<>((Integer i1, Integer i2) -> i1.compareTo(i2));
    for (Integer i : Arrays.asList(4, 7, 3, 8, 9, 25, 20, 23, 52, 3)) {
        list.add(i);
    }
    System.out.println(list);

0

SortedSets / Listeler ve 'normal' sıralanabilir koleksiyonlar arasındaki seçimin, yalnızca sunum amacıyla mı yoksa çalışma süresi boyunca hemen hemen her noktada sıralamanız gerekip gerekmediğine bağlı olduğunu düşünüyorum. Sıralı bir koleksiyon kullanmak çok daha pahalı olabilir, çünkü her öğe eklediğinizde sıralama yapılır.

JDK'da bir koleksiyon seçemiyorsanız, Apache Commons Koleksiyonlarına göz atabilirsiniz.


0

Koleksiyon API'sini kırarak sıralı bir liste uygulayan halihazırda önerilen uygulamalar, kendi bir ağaç uygulamasına veya benzer bir şeye sahip olduğundan, TreeMap'e dayalı bir uygulamanın nasıl performans göstereceğini merak ediyordum. (Özellikle TreeSet, TreeMap'i de temel aldığından)

Birisi bununla da ilgileniyorsa, onu incelemekte özgür hissedebilir:

Ağaç Listesi

Çekirdek kütüphanesinin bir parçası , elbette Maven bağımlılığı aracılığıyla ekleyebilirsiniz. (Apache Lisansı)

Şu anda uygulama, guava SortedMultiSet ile aynı seviyede ve Apache Commons kütüphanesinin TreeList'iyle oldukça iyi karşılaştırılıyor gibi görünüyor.

Ancak, önemli bir şeyi kaçırmadığımdan emin olmak için uygulamayı sadece benden daha fazla test etsem mutlu olurum.

Saygılarımla!


0

Ben de aynı sorunu yaşadım. Bu yüzden java.util.TreeMap'in kaynak kodunu aldım ve IndexedTreeMap'i yazdım . Kendi IndexedNavigableMap'imi uygular :

public interface IndexedNavigableMap<K, V> extends NavigableMap<K, V> {
   K exactKey(int index);
   Entry<K, V> exactEntry(int index);
   int keyIndex(K k);
}

Uygulama, değiştirildiğinde kırmızı-siyah ağaçtaki düğüm ağırlıklarının güncellenmesine dayanır. Ağırlık, belirli bir düğümün altındaki alt düğümlerin sayısı artı bir benliktir. Örneğin bir ağaç sola döndürüldüğünde:

    private void rotateLeft(Entry<K, V> p) {
    if (p != null) {
        Entry<K, V> r = p.right;

        int delta = getWeight(r.left) - getWeight(p.right);
        p.right = r.left;
        p.updateWeight(delta);

        if (r.left != null) {
            r.left.parent = p;
        }

        r.parent = p.parent;


        if (p.parent == null) {
            root = r;
        } else if (p.parent.left == p) {
            delta = getWeight(r) - getWeight(p.parent.left);
            p.parent.left = r;
            p.parent.updateWeight(delta);
        } else {
            delta = getWeight(r) - getWeight(p.parent.right);
            p.parent.right = r;
            p.parent.updateWeight(delta);
        }

        delta = getWeight(p) - getWeight(r.left);
        r.left = p;
        r.updateWeight(delta);

        p.parent = r;
    }
  }

updateWeight, ağırlıkları köke kadar günceller:

   void updateWeight(int delta) {
        weight += delta;
        Entry<K, V> p = parent;
        while (p != null) {
            p.weight += delta;
            p = p.parent;
        }
    }

Ve öğeyi indekse göre bulmamız gerektiğinde, ağırlıkları kullanan uygulama şu şekildedir:

public K exactKey(int index) {
    if (index < 0 || index > size() - 1) {
        throw new ArrayIndexOutOfBoundsException();
    }
    return getExactKey(root, index);
}

private K getExactKey(Entry<K, V> e, int index) {
    if (e.left == null && index == 0) {
        return e.key;
    }
    if (e.left == null && e.right == null) {
        return e.key;
    }
    if (e.left != null && e.left.weight > index) {
        return getExactKey(e.left, index);
    }
    if (e.left != null && e.left.weight == index) {
        return e.key;
    }
    return getExactKey(e.right, index - (e.left == null ? 0 : e.left.weight) - 1);
}

Ayrıca, bir anahtarın dizinini bulmakta çok kullanışlı olur:

    public int keyIndex(K key) {
    if (key == null) {
        throw new NullPointerException();
    }
    Entry<K, V> e = getEntry(key);
    if (e == null) {
        throw new NullPointerException();
    }
    if (e == root) {
        return getWeight(e) - getWeight(e.right) - 1;//index to return
    }
    int index = 0;
    int cmp;
    index += getWeight(e.left);

    Entry<K, V> p = e.parent;
    // split comparator and comparable paths
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        while (p != null) {
            cmp = cpr.compare(key, p.key);
            if (cmp > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    } else {
        Comparable<? super K> k = (Comparable<? super K>) key;
        while (p != null) {
            if (k.compareTo(p.key) > 0) {
                index += getWeight(p.left) + 1;
            }
            p = p.parent;
        }
    }
    return index;
}

Bu çalışmanın sonucunu http://code.google.com/p/indexed-tree-map/ adresinde bulabilirsiniz.

TreeSet / TreeMap (indekslenmiş ağaç haritası projesinden indekslenmiş emsallerinin yanı sıra) yinelenen anahtarlara izin vermez, bir değer dizisi için 1 anahtar kullanabilirsiniz. Yinelenen bir SortedSet'e ihtiyacınız varsa, değerleri dizi olarak içeren TreeMap'i kullanın. Ben yapardım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.