Guava: ¿Por qué no hay función Lists.filter ()?

86

¿Hay alguna razón por la que hay

Lists.transform()

pero no

Lists.filter()

?

¿Cómo filtro una lista correctamente? Podría usar

new ArrayList(Collection2.filter())

por supuesto, pero de esta manera no se garantiza que mi pedido sea el mismo, si lo entiendo correctamente.

Fabián Zeindl
fuente
8
Para su información, List.newArrayList (Iterables.filter (...)) es generalmente más rápido que el nuevo ArrayList (Collection2.filter (...)). El constructor ArrayList llama a size () en la colección filtrada, y calcular el tamaño requiere que el filtro se aplique a cada elemento de la lista original.
Jared Levy
4
@JaredLevy Quizás en lugar de List.newArrayList(Iterables.filter(...)), debería decir Lists.newArrayList(Iterables.filter(...)) .
Abdull

Respuestas:

57

No se implementó porque expondría una gran cantidad de métodos lentos peligrosos, como #get (index) en la vista de lista devuelta (invitando errores de rendimiento). Y ListIterator también sería complicado de implementar (aunque envié un parche hace años para cubrir eso).

Dado que los métodos indexados no pueden ser eficientes en la vista de lista filtrada, es mejor ir con un Iterable filtrado, que no los tiene.

Dimitris Andreou
fuente
7
Está asumiendo que se devolverá una vista de lista. Sin embargo, #filter podría implementarse devolviendo una nueva lista materializada, que es en realidad lo que esperaría de un método de filtro para listas en contraposición al de Iterable.
Felix Leipold
@FelixLeipold Sin embargo, esto enturbiaría las aguas. Tal como está, filterconsistentemente significa una vista (junto con el comportamiento que implica) si eso es Iterables.filter, Sets.filteretc. Dado que se Iterables.filtercombina fácilmente con copyOfcualquiera ImmutableCollection, encuentro que esta es una buena compensación de diseño (en lugar de crear métodos y nombres adicionales, como filteredCopyo lo que sea , para combinaciones de utilidades simples).
Luke Usherwood
37

Puede usar Iterables.filter, lo que definitivamente mantendrá el pedido.

Tenga en cuenta que al construir una nueva lista, estará copiando los elementos (solo referencias, por supuesto), por lo que no será una vista en vivo de la lista original. Crear una vista sería bastante complicado; considere esta situación:

Predicate<StringBuilder> predicate = 
    /* predicate returning whether the builder is empty */
List<StringBuilder> builders = Lists.newArrayList();
List<StringBuilder> view = Lists.filter(builders, predicate);

for (int i = 0; i < 10000; i++) {
    builders.add(new StringBuilder());
}
builders.get(8000).append("bar");

StringBuilder firstNonEmpty = view.get(0);

Eso tendría que iterar sobre toda la lista original, aplicando el filtro a todo. Supongo que podría requerir que la coincidencia de predicados no cambiara durante la vida útil de la vista, pero eso no sería del todo satisfactorio.

(Esto es solo una suposición, fíjate. Tal vez uno de los mantenedores de Guava contribuya con la verdadera razón :)

Jon Skeet
fuente
1
Collections2.filter.iteratorsolo llama Iterables.filter, por lo que el resultado es el mismo.
skaffman
@skaffman: En cuyo caso usaría la Iterables.filterversión solo para mayor claridad.
Jon Skeet
3
... a menos que necesite un view.size()lugar más adelante en el código :)
Xaerxess
28

Podría usar, new List(Collection2.filter())por supuesto, pero de esta manera no se garantiza que mis pedidos sigan siendo los mismos.

Eso no es cierto. Collections2.filter()es una función evaluada de forma perezosa: en realidad no filtra su colección hasta que comienza a acceder a la versión filtrada. Por ejemplo, si itera sobre la versión filtrada, los elementos filtrados saldrán del iterador en el mismo orden que su colección original (menos los filtrados, obviamente).

Quizás estaba pensando que hace el filtrado por adelantado, luego vuelca los resultados en una Colección arbitraria y desordenada de alguna forma, no es así.

Así que si se utiliza la salida de Collections2.filter()como la entrada a una nueva lista, entonces su orden original será retenido.

Usando importaciones estáticas (y la Lists.newArrayListfunción), se vuelve bastante conciso:

List filteredList = newArrayList(filter(originalList, predicate));

Nótese que si bien Collections2.filterno con impaciencia iterar sobre la colección subyacente, Lists.newArrayList será - se va a extraer todos los elementos de la colección filtrada y copiarlos en una nueva ArrayList.

skaffman
fuente
Es más como: List filteredList = newArrayList(filter(originalList, new Predicate<T>() { @Override public boolean apply(T input) { return (...); } }));o es decir. List filteredList = newArrayList(filter(originalList, Predicates.notNull()));
Xaerxess
@Xaerxess: Ups, sí, olvidé el predicado ... arreglado
skaffman
@Bozho: Gracias ... me tomó bastante tiempo :)
skaffman
12

Como mencionó Jon, puede usar Iterables.filter(..)o Collections2.filter(..)y, si no necesita una vista en vivo, puede usar ImmutableList.copyOf(Iterables.filter(..))o Lists.newArrayList( Iterables.filter(..))y sí, se mantendrá el pedido.

Si está realmente interesado en saber por qué parte, puede visitar https://github.com/google/guava/issues/505 para obtener más detalles.

Premraj
fuente
6

Resumiendo lo que dijeron los demás, puede crear fácilmente un contenedor genérico para filtrar listas:

public static <T> List<T> filter(Iterable<T> userLists, Predicate<T> predicate) {
    return Lists.newArrayList(Iterables.filter(userLists, predicate));
}
Holger Brandl
fuente