Фильтрация списков универсальных типов

Списки или Iterables можно легко фильтровать с помощью guavas _ 1_. Эта операция выполняет две задачи: список фильтруется и преобразуется в последовательность заданного типа 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>> может просто возвращать отфильтрованную последовательность, которая может быть пустой, вместо того, чтобы возвращать значение NULL. Дополнительный фильтр может окончательно отбросить пустые последовательности, используя Iterables.isEmpty().


person Ditz    schedule 26.08.2011    source источник
comment
Было бы полезно, если бы Iterable.filter(...) возвращал итерацию с расширенными функциями, чтобы вы могли связать фильтры. /* S extends Collection */ Iterable<S<Double>> doubles = Iterable.filter(numbers, castOrNull(Double.class)).filter(Predicates.notNull()).filter(Predicates.notEmpty());   -  person aalku    schedule 05.09.2011
comment
Почему вы хотите сделать это за один шаг? Преобразование и фильтрация - разные операции.   -  person pawstrong    schedule 05.09.2011


Ответы (2)


Монадический подход к этой проблеме состоит в том, чтобы определить операцию, которая преобразует итерацию в итерацию итераций, путем определения функции преобразования, которая для объекта типа 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);

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

person Samir Talwar    schedule 19.09.2011
comment
Очень интересный подход, когда функция возвращает Iterable, содержащий ноль или один элемент. Я не думал об этом и думаю, что это здорово, что это позволяет вашей функции возвращать null, если это необходимо. Поскольку Guava добавляет необязательный тип в r10 (docs.guava-libraries.googlecode.com/git/javadoc/com/google/), интересно, можем ли мы вместо этого использовать функцию ‹S‹? ›, необязательную‹ S ‹T› ››, фильтрующую итоговый Iterable для приведенных значений. - person Etienne Neveu; 23.09.2011
comment
Я только что прочитал ответ luckyjaca, и кажется, что это подход, который они используют в Scala, используя тип Option, с дополнительным преимуществом функции flatMap. См. Этот ответ SO для получения дополнительной информации: stackoverflow.com/ questions / 1059776 / flatMap превращает List[Option[A]] в List[A], удаляя все Option, переходящие в None. Довольно круто. - person Etienne Neveu; 23.09.2011
comment
@eneveu: Было бы здорово, если бы Optional реализовал Iterable, но, насколько мне известно, нет никаких планов сделать это так. Реализация Maybe в Java Нэта Прайса (github.com/npryce/maybe-java) делает, тем не мение. Парень из компании, в которой я работаю, разработал ее и несколько улучшил - вы можете увидеть эту версию на github.com / youdevise / возможно-java. - person Samir Talwar; 24.09.2011
comment
Хорошая точка зрения. Это означает, что мы не можем использовать Iterables.concat для сглаживания Iterable<Optional<T>> до Iterable<T> всех текущих значений ... Я думаю, мы могли бы написать служебный метод, который сделает это за нас, хотя (или Guava мог бы). По несвязанному замечанию, я бы использовал ImmutableSet.of() в вашем примере, чтобы каждый раз не инициализировать пустой ArrayList. Что-то вроде return contained instanceof T ? ImmutableSet.of(new S<T>(contained)) : ImmutableSet.of();. Круто то, что пустой неизменяемый набор представляет собой синглтон под капотом, что позволяет избежать ненужного создания экземпляров объекта / массива. - person Etienne Neveu; 24.09.2011

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

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

person Jacek L.    schedule 15.09.2011