Фильтрация списков универсальных типов - PullRequest
19 голосов
/ 26 августа 2011

Списки или итерации можно легко фильтровать с помощью guavas filter(Iterable<?> unfiltered, Class<T> type).Эта операция выполняет две задачи: список фильтруется и , преобразованный в последовательность заданного типа T.

Довольно часто, однако я получаю Iterables<Something<?>> и хочу получить подпоследовательностьIterables<Something<T>> для некоторого специализированного T.

Ясно, что Guava не может решить эту проблему из коробки из-за стирания типа: Something<T> не предоставляет никакой прямой информации о своем T.

Допустим, у меня есть что-то вроде S<? extends Number>.Если я могу определить какой-то предикат, который говорит мне, можно ли привести S<?> к S<Double>, я могу использовать его как файл:

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

с:

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

Это выполняет задачу фильтрации, но пропускает шаг преобразования.Если я думаю, что мой Предикат работает хорошо, я могу даже подумать о приведении:

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

Но это обнажает некрасивую операцию приведения.

В качестве альтернативы я могу предоставить Function<S<?>, S<Double>> для выполнениябросать.В отличие от Class.cast(), однако, он не должен бросать ClassCastException, а просто возвращать null, если элемент не может быть приведен (или преобразован).Таким образом, последовательность может быть преобразована без какого-либо явного преобразования:

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

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

Но список на самом деле не фильтруется: вместо этого он все еще содержит нулевые объекты для каждого элемента, который не может быть преобразован или приведен к S<Double>.Но это может быть легко решено с помощью дополнительного шага фильтрации, например:

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

Второе решение мне кажется намного умнее.Определяемый Function может либо выполнить приведение (которое скрывает непроверенную операцию), либо действительно создать новый объект S<T>, если это необходимо.

Остается вопрос: есть ли более разумный способвыполнить необходимое преобразование и фильтрацию за один шаг?Я могу просто определить некоторую служебную функцию, например:

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

Где вторая функция - это сокращение первой из них с Predicates.notNull();

Но стоит иметь первую функциютоже как предикат не нужен Predicates.notNull().

Представьте себе Iterable<Iterable<? extends Number>>.Функция преобразователя Function<Iterable<? extends Number>, Iterable<Double>> может просто возвращать отфильтрованную последовательность, которая может быть пустой, вместо того, чтобы возвращать ноль.Дополнительный фильтр может, наконец, отбросить пустые последовательности, используя Iterables.isEmpty().

Ответы [ 2 ]

3 голосов
/ 20 сентября 2011

Монадический подход к этой проблеме заключается в определении операции, которая преобразует итерируемое в итерируемое итерируемое, определяя функцию преобразования, которая для объекта типа T возвращает объект типа Iterable<T>. Затем вы можете объединить каждую итерацию, чтобы снова сформировать одну. Эта комбинация сопоставления, сопровождаемого конкатенацией, называется concatMap в Haskell и flatMap в Scala, и я уверен, что у нее есть другие имена в других местах.

Чтобы реализовать это, мы сначала создаем функцию, которая преобразует ваш S<? extends Number> в Iterable<S<Double>>. Это очень похоже на вашу существующую функцию, но наш случай успеха является итерируемой единицей, содержащей наш S, а случай сбоя (наше нулевое состояние) является пустой итерацией.

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

Затем мы применяем это к исходной итерации, как вы указали выше.

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

Затем мы можем объединить все это вместе, чтобы снова создать одну итерацию, которая имеет все желаемые значения и ни одно из тех, которые мы хотим удалить.

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

Отказ от ответственности: я не пытался скомпилировать это. Возможно, вам придется поиграться с дженериками, чтобы заставить его работать.

2 голосов
/ 15 сентября 2011

Язык Scala в своей структуре коллекций предлагает функциональность, аналогичную Guava. У нас есть класс Option [T], который можно рассматривать как набор не более одного элемента. Среди простых методов фильтрации или преобразования есть метод, который выполняет обе операции одновременно. Ожидается, что предоставленная функция преобразования вернет значение класса Option. Затем он объединяет содержимое возвращенных объектов Option в коллекцию. Я думаю, что вы можете реализовать аналогичную функциональность в Java.

Я думал об этой проблеме некоторое время назад, потому что сначала применение преобразования, а затем фильтрация требует двойной передачи коллекции. Тогда кто-то просветил меня, что я могу преобразовать и отфильтровать итератор этой коллекции. В этом случае коллекция просматривается один раз, и вы можете применить столько фильтров и преобразований, сколько захотите.

...