Lista de matrices ordenadas en Java


Estoy desconcertado de que no puedo encontrar una respuesta rápida a esto. Esencialmente estoy buscando una estructura de datos en Java que implemente la interfaz java.util.List, pero que almacene sus miembros en un orden ordenado. Sé que puedes usar un ArrayList normal y usar Collections.sort() en él, pero tengo un escenario en el que de vez en cuando estoy agregando y a menudo recuperando miembros de mi lista y no quiero tener que ordenarlo cada vez que recupero un miembro en caso de que se haya agregado uno nuevo. ¿Puede alguien señalarme hacia tal cosa que existe en las bibliotecas JDK o incluso 3rd party?

EDITAR : La estructura de datos necesitará conservar duplicados.

RESUMEN DE LA RESPUESTA: Encontré todo esto muy interesante y aprendí mucho. Aioobe en particular merece mención por su perseverancia en tratar de lograr mis requisitos anteriores (principalmente un java ordenado.útil.Lista de implementación que soporta duplicados). He aceptado su respuesta como la más precisa para lo que pregunté y más provocador a la reflexión sobre las implicaciones de lo que estaba buscando, incluso si lo que pregunté no era exactamente lo que necesitaba.

El problema con lo que pedí radica en la propia interfaz de Lista y el concepto de métodos opcionales en una interfaz. Para citar el javadoc:

El usuario de esta interfaz tiene un control preciso sobre dónde en la lista se inserta cada elemento.

Insertar en una lista ordenada no tiene un control preciso sobre el punto de inserción. Entonces, usted tiene que pensar cómo va a manejar algunos de los métodos. Tomemos add por ejemplo:

Public boolean add(Object o)

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

Ahora se ha quedado en la incómoda situación de 1) Romper el contrato e implementar una versión ordenada de add 2) Dejar que add agregue un elemento al final de la lista, rompiendo su orden ordenado 3) Dejando add fuera (como su opcional) lanzando un UnsupportedOperationException e implementando otro método que agrega elementos en un ordenado orden.

La opción 3 es probablemente la mejor, pero me parece desagradable tener un método add que no puedes usar y otro método sortedAdd que no está en la interfaz.

Otras soluciones relacionadas (sin orden particular):

  • java.útil.PriorityQueue que es probablemente lo más cercano a lo que necesitaba que lo que pedí. Una cola no es la definición más precisa de una colección de objetos en mi caso, pero funcionalmente hace todo lo que necesito.
  • net.sourceforge.noche.útil.SortedList . Sin embargo, esta implementación rompe el contrato de la interfaz de Lista implementando la ordenación en el método add(Object obj) y extrañamente tiene un método sin efecto para add(int index, Object obj). El consenso general sugiere que throw new UnsupportedOperationException() podría ser una mejor opción en este escenario.
  • Guava's TreeMultiSet Una implementación de conjunto que soporta duplicados
  • ca.odell.listas esmaltadas.SortedList Esta clase viene con la advertencia en su javadoc: Warning: This class breaks the contract required by List
Author: Chris Knight, 2010-10-27

11 answers

Solución minimalista

Aquí hay una solución "mínima".

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);
    }
}

La inserción se ejecuta en tiempo lineal, pero eso sería lo que obtendría usando una ArrayList de todos modos (todos los elementos a la derecha del elemento insertado tendrían que ser desplazados de una manera u otra).

Insertar algo no comparable resulta en una ClassCastException. (Este es el enfoque adoptado por PriorityQueue también: Una cola de prioridad basada en el orden natural tampoco permite inserción de objetos no comparables (hacerlo puede resultar en ClassCastException).)

Overriding List.add

Tenga en cuenta que sobreescribir List.add (o List.addAll para el caso) insertar elementos de una manera ordenada sería una violación directa de la especificación de interfaz. Lo que podría hacer es anular este método para lanzar un UnsupportedOperationException.

De los documentos de List.add:

boolean add(E e)
     Añade el elemento especificado al final de esta lista (operación opcional).

El mismo razonamiento se aplica para ambas versiones de add, ambas versiones de addAll y set. (Todas las cuales son operaciones opcionales según la interfaz de la lista.)


Algunas pruebas

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);

....impresiones:

[ddd]
[aaa, ddd]
[aaa, ccc, ddd]
[aaa, bbb, ccc, ddd]
[aaa, bbb, ccc, ddd, eee]
 61
Author: aioobe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-17 13:05:09
 11
Author: Gadolin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-27 09:26:11

Echa un vistazo a SortedList

Esta clase implementa una lista ordenada. Se construye con un comparador que puede comparar dos objetos y ordenar los objetos en consecuencia. Cuando agrega un objeto a la lista, se inserta en el lugar correcto. Los objetos que son iguales según el comparador, estarán en la lista en el orden en que se agregaron a esta lista. Agregue solo objetos que el comparador pueda comparar.


Cuando la lista ya contiene objetos que son iguales según el comparador, el nuevo objeto se insertará inmediatamente después de estos otros objetos.

 6
Author: Jigar Joshi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-27 09:25:16

Puede intentar Guayaba del TreeMultiSet.

 Multiset<Integer> ms=TreeMultiset.create(Arrays.asList(1,2,3,1,1,-1,2,4,5,100));
 System.out.println(ms);
 6
Author: Emil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-31 08:28:32

Las listas normalmente conservan el orden en el que se agregan los elementos. ¿Necesita definitivamente una lista , o un conjunto ordenado (p.ej. TreeSet<E>) ¿estar bien para ti? Básicamente, ¿necesita conservar duplicados?

 4
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-27 09:14:52

El enfoque de Aioobe es el camino a seguir. Sin embargo, me gustaría sugerir la siguiente mejora sobre su solución.

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
    }
}

La solución de Aioobe se vuelve muy lenta cuando se usan listas grandes. Usar el hecho de que la lista está ordenada nos permite encontrar el punto de inserción para nuevos valores utilizando la búsqueda binaria.

También usaría la composición sobre la herencia, algo así como

SortedList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 3
Author: Emanuel Moecklin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-21 18:33:51

Podría ser un poco demasiado pesado para usted, pero GlazedLists tiene un SortedList que es perfecto para usar como modelo de una tabla o JList

 2
Author: I82Much,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-27 10:57:55

Puede subclase ArrayList, y llamar a Colecciones.ordenar (esto) después de que se agrega cualquier elemento - usted tendría que anular dos versiones de add, y dos de addAll, para hacer esto.

El rendimiento no sería tan bueno como una implementación más inteligente que inserta elementos en el lugar correcto, pero haría el trabajo. Si la adición a la lista es rara, el costo amortizado sobre todas las operaciones en la lista debe ser bajo.

 1
Author: Tom Anderson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-27 09:35:17

Creo que la elección entre SortedSets/Lists y 'normal' sortable collections depende, si necesita ordenar solo para fines de presentación o en casi todos los puntos durante el tiempo de ejecución. Usar una colección ordenada puede ser mucho más caro porque la ordenación se realiza cada vez que se inserta un elemento.

Si no puede optar por una colección en el JDK, puede echar un vistazo a Apache Commons Collections

 0
Author: codeporn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-27 09:30:51

Dado que las implementaciones actualmente propuestas que implementan una lista ordenada al romper la API de Colección, tienen una implementación propia de un árbol o algo similar, era curioso cómo se desempeñaría una implementación basada en el mapa de árbol. (Especialy desde el TreeSet hace base en TreeMap, demasiado)

Si alguien también está interesado en eso, puede sentirse libre de investigarlo:

Lista de árboles

Es parte de la biblioteca core , puede agregarla a través de La dependencia de Maven, por supuesto. (Licencia Apache)

Actualmente la implementación parece compararse bastante bien en el mismo nivel que el guava SortedMultiSet y con el TreeList de la biblioteca Apache Commons.

Pero estaría feliz si más que solo yo probara la implementación para estar seguro de que no me perdí algo importante.

Saludos cordiales!

 0
Author: Omnaest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-11 20:01:40

Yo tenía el mismo problema. Así que tomé el código fuente de Java.útil.TreeMap and wrote IndexedTreeMap. Implementa mi propio IndexedNavigableMap :

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

La implementación se basa en actualizar los pesos de los nodos en el árbol rojo-negro cuando se cambia. El peso es el número de nodos hijos debajo de un nodo dado, más uno mismo. Por ejemplo, cuando un árbol se gira a la izquierda:

    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 simplemente actualiza los pesos hasta la raíz:

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

Y cuando necesitamos encontrar el elemento por índice aquí es la implementación que usa pesos:

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);
}

También es muy útil encontrar el índice de una clave:

    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;
}

Puedes encontrar el resultado de este trabajo en http://code.google.com/p/indexed-tree-map /

TreeSet/TreeMap (así como sus contrapartes indexadas del proyecto indexed-tree-map) no permiten claves duplicadas , puede usar 1 clave para una matriz de valores. Si necesita un SortedSet con duplicados use TreeMap con valores como matrices. Yo haría eso.

 0
Author: Vitaly Sazanovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-10 21:41:05