Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering lists of generic types

Lists or Iterables can be filtered easily using guavas filter(Iterable<?> unfiltered, Class<T> type). This operation performs two tasks: the list is filtered and transformed into a sequence of the given type T.

Quite often however I end up with Iterables<Something<?>> and I want to get a subsequence of Iterables<Something<T>> for some specialized T.

It is clear, that Guava can't solve this problem out of the box due to type erasure: Something<T> does not provide any direct information about its T.

Lets say I have something like S<? extends Number>. If I am able to define some predicate which tells me if S<?> may be casted to S<Double> I may use it as a filer:

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

with:

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

This performs the task of filtering but it misses the transformation step. If I think my Predicate works well I may even think of casting:

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

But this exposes some ugly cast operation.

As an alternative I may provide a Function<S<?>, S<Double>> to perform the cast. In constrast to Class.cast() however it should not throw a ClassCastException but simply return null if the element can not be casted (or transformed). This way the sequence may be converted without any explicit cast:

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

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

But the list is not really filtered: instead it still contains null objects for each element which could not converted or casted to S<Double>. But this may solved easily by an additional filtering step like:

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

The second solution seems much smarter to me. The Function to be defined may either perform a cast (which hides the unchecked operation) or it may really create some new Object S<T> if necessary.

The remaining question is: Is there any smarter way to perform the necessary converting and filtering by a single step? I may simply define some utility function like:

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

Where the second function is a short cut of the first one with a Predicates.notNull();

But it's worth to have the first function, too, as the predicate is not necessary Predicates.notNull().

Imagine an Iterable<Iterable<? extends Number>>. The converter function Function<Iterable<? extends Number>, Iterable<Double>> may simply return a filtered sequence which may be empty instead of returning null. The additional filter may finally drop empty sequences using Iterables.isEmpty().

like image 510
Ditz Avatar asked Aug 26 '11 12:08

Ditz


2 Answers

The monadic approach to this problem is to define an operation that transforms an iterable into an iterable of iterables, by defining a transformation function that for an object of type T, returns an object of type Iterable<T>. You can then concatenate each iterable to form a single one again. This combination of a mapping followed by a concatenation is called concatMap in Haskell and flatMap in Scala, and I'm sure it has other names elsewhere.

To implement this, we first create a function that transforms your S<? extends Number> into Iterable<S<Double>>. This is very similar to your existing function, but our success case is an iterable of one, containing our S, and the failure case (our null state) is an empty iterable.

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

We then apply this to the original iterable as you specify above.

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

We can then concatenate all these together to produce one iterable again, which has all of the desired values and none of those we want to remove.

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

Disclaimer: I haven't tried compiling this. You may have to play around with generics to get it to work.

like image 155
Samir Talwar Avatar answered Nov 11 '22 02:11

Samir Talwar


Scala language in its collections framework offers similar functionality to Guava. We have Option[T] class which can be considered as at-most-single-element-collection. Among simple filtering or transformation methods there is a method which performs both operations at once. It expects provided transformation function to return a value of Option class. Then it merges content of returned Option objects into a collection. I think you can implement similar functionality in Java.

I was thinking of this problem some time ago because firstly applying transformation and then filtering requires passing the collection twice. Then someone enlightened me that I can transform and filter iterator of this collection. In this case the collection is traversed once and you may apply as many filters and transformations as you want.

like image 2
Jacek L. Avatar answered Nov 11 '22 01:11

Jacek L.