2011-08-26 23 views
19

Las listas o Iterables se pueden filtrar fácilmente usando guavas filter(Iterable<?> unfiltered, Class<T> type). Esta operación realiza dos tareas: la lista se filtra y transforma en una secuencia de la T. tipo dadoFiltrando listas de tipos genéricos

Muy a menudo, sin embargo termino con Iterables<Something<?>> y quiero obtener una subsecuencia de Iterables<Something<T>> por alguna T. especializada

es evidente, que la guayaba no puede resolver este problema fuera de la caja debido al tipo de borrado: Something<T> no proporciona ninguna información directa acerca de su T.

Digamos que tengo algo así como S<? extends Number>. Si soy capaz de definir algún predicado que me dice si S<?> puede ser moldeado a S<Double> que puede usarlo como un archivador:

<T extends Number> Predicate<S<?>> isOfType(Class<N> type) {...} 

con:

Iterable<S<?>> numbers; 
Iterable<S<?>> filtered = Iterable.filter(numbers, isOfType(Double.class)); 

Este realiza la tarea de filtrado, pero pierde el paso de transformación. Si pienso en mi predicado funciona bien puedo siquiera pensar en la fundición:

Iterable<S<Double>> doubles = (Iterable<S<Double>>) filtered; 

Pero esto expone a alguna operación de fundición feo.

Como alternativa, puedo proporcionar un Function<S<?>, S<Double>> para realizar el reparto. En contraste con Class.cast(), sin embargo, no debería arrojar un ClassCastException, sino que simplemente devuelve null si el elemento no se puede convertir (o transformar). De esta manera la secuencia se puede convertir sin ningún tipo de conversión explícita:

<T extends Number> Function<S<?>, S<T>> castOrNull(Class<N> type) {...} 

Iterable<S<Double>> doubles = Iterable.filter(numbers, castOrNull(Double.class)); 

Pero la lista no está realmente filtrada: en lugar todavía contiene objetos nulos para cada elemento que no pudo convertir o fundido a S<Double>. Pero esto puede resolverse fácilmente mediante una etapa de filtración adicional como:

Iterable<S<Double>> doubles = Iterables.filter(doubles, Predicates.notNull()); 

La segunda solución parece mucho más inteligente que yo. El Function que se va a definir puede realizar una conversión (que oculta la operación no seleccionada) o puede realmente crear un nuevo objeto S<T> si es necesario.

La pregunta restante es: ¿Existe alguna forma más inteligente de realizar la conversión y el filtrado necesarios en un solo paso? Puede que simplemente definir algunas función de utilidad como:

<I,O> Iterables<O> convert(
    Iterables<O> input, 
    Function<? super I, ? extends O> convert, 
    Predicate<? super O> filter); 

<I,O> Iterables<O> convert(
    Iterables<O> input, 
    Function<? super I, ? extends O> convert); 

Cuando la segunda función es un corte corto de la primera con una Predicates.notNull();

Pero también vale la pena tener la primera función, ya que el predicado no es necesario Predicates.notNull().

Imagine un Iterable<Iterable<? extends Number>>. La función del convertidor Function<Iterable<? extends Number>, Iterable<Double>> puede simplemente devolver una secuencia filtrada que puede estar vacía en lugar de devolver nula. El filtro adicional finalmente puede soltar secuencias vacías usando Iterables.isEmpty().

+1

Sería útil si 'Iterable.filter (...)' devuelve un iterable con funcionalidad extendida para que pueda encadenar filtros. '/ * S extends Collection */Iterable > double = Iterable.filter (numbers, castOrNull (Double.class)). Filter (Predicates.notNull()). Filter (Predicates.notEmpty());' – aalku

+4

¿Por qué? ¿Quieres hacerlo en un solo paso? La transformación y el filtrado son operaciones distintas. – pawstrong

Respuesta

2

El lenguaje Scala en su marco de colecciones ofrece una funcionalidad similar a la de Guava. Tenemos la clase Option [T] que se puede considerar como una colección de elementos únicos. Entre los métodos simples de filtrado o transformación hay un método que realiza ambas operaciones a la vez. Espera que la función de transformación proporcionada devuelva un valor de clase Opción. Luego combina el contenido de los objetos de opciones devueltos en una colección. Creo que puedes implementar funcionalidades similares en Java.

Estaba pensando en este problema hace algún tiempo porque primero aplicar la transformación y luego filtrar requiere pasar la colección dos veces. Entonces alguien me explicó que puedo transformar y filtrar el iterador de esta colección. En este caso, la colección se atraviesa una vez y puede aplicar tantos filtros y transformaciones como desee.

3

El enfoque monádico para este problema es definir una operación que transforma un iterable en un iterable de iterables, definiendo una función de transformación que para un objeto de tipo T devuelve un objeto del tipo Iterable<T>. A continuación, puede concatenar cada iterable para formar una sola de nuevo. Esta combinación de un mapeo seguido de una concatenación se llama concatMap en Haskell y flatMap en Scala, y estoy seguro de que tiene otros nombres en otros lugares.

Para implementar esto, primero creamos una función que transforma su S<? extends Number> en Iterable<S<Double>>. Esto es muy similar a su función existente, pero nuestro caso de éxito es iterable de uno, que contiene nuestro S, y el caso de falla (nuestro estado nulo) es un iterable vacío.

<T extends Number> Function<S<?>, Iterable<S<T>>> castOrNull(Class<T> type) { 
    return new Function<S<?>, Iterable<S<T>>> { 
     @Override 
     public Iterable<S<T>> apply(S<?> s) { 
      Object contained = s.get(); 
      if (!(contained instanceof T)) { 
       return ImmutableSet.of(); 
      } 

      return ImmutableSet.of(new S<T>(contained)); 
     } 
    }; 
} 

A continuación, aplicamos esto al iterable original como se especifica arriba.

Iterable<Iterable<S<Double>>> doubleIterables = Iterables.map(numbers, castOrNull(Double.class)); 

Entonces podemos concatenar todos estos juntos para producir un nuevo iterable, que tiene todos los valores deseados y ninguna de las personas que queremos eliminar.

Iterable<S<Double>> doubles = Iterables.concat(doubleIterables); 

de responsabilidad: No he probado la compilación de este. Puede que tenga que jugar con los genéricos para que funcione.

+1

Tener la función return un Iterable que contiene cero o un elemento es un enfoque muy interesante. No había pensado en esto, y creo que es genial que deje que tu función sea nula si es necesario. Como Guava agrega el tipo opcional en r10 (http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html), me pregunto si podríamos usar una función , Opcional >> en su lugar, filtrando el Iterable resultante para los valores "actuales". –

+1

Acabo de leer la respuesta de luckyjaca, y parece que ese es el enfoque que usan en Scala, usando el tipo "Opción", con el beneficio adicional de la función flatMap. Vea esta respuesta SO para obtener más información: http://stackoverflow.com/questions/1059776/scala-iterablemap-vs-iterableflatmap/1060400#1060400 "' flatMap' convierte una 'Lista [Opción [A]]' en 'Lista [ A] ', con cualquier' Opción 'que taladra a 'Ninguno', eliminado". Muy genial. –

+2

@eneveu: Eso sería genial si 'Optional 'implementara' Iterable', pero por lo que yo sé, no hay planes para hacerlo. Sin embargo, la implementación de Maybe in Java (https://github.com/npryce/maybe-java) de Nat Pryce sí lo hace.Un tipo de la empresa para la que trabajo lo bifurcó y lo mejoró un poco; puede ver esa versión en https://github.com/youdevise/maybe-java. –