Как я могу отфильтровать Iterable на основе предиката? - PullRequest
2 голосов
/ 29 мая 2019

Я хочу сделать функцию фильтрации списка строк, используя Iterable<String> и предикат для выбора строк, которые нужно сохранить, остальные должны быть удалены из списка, но я не понимаю, как я делаю удаление.

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    for (T s: it) {
        if (pred.test(s)==false) {
            // what to do here?
        }
    }
    return ...;
}

Для этого ввода:

{"a","","b",""}

Я ожидаю

{"a","b"}

Ответы [ 4 ]

3 голосов
/ 31 мая 2019

Iterable представляет возможность предоставить Iterator по запросу.Итак, чтобы украсить существующую итерацию с помощью логики фильтрации, вы должны реализовать декорирование Iterator.

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    return () -> new Iterator<T>() {
        Iterator<T> sourceIterator = it.iterator();
        T current;
        boolean hasCurrent;

        @Override
        public boolean hasNext() {
            while(!hasCurrent) {
                if(!sourceIterator.hasNext()) {
                    return false;
                }
                T next = sourceIterator.next();
                if(pred.test(next)) {
                    current = next;
                    hasCurrent = true;
                }
            }
            return true;
        }

        @Override
        public T next() {
            if(!hasNext()) throw new NoSuchElementException();
            T next = current;
            current = null;
            hasCurrent = false;
            return next;
        }
    };
}

, которое вы можете протестировать с помощью

List<String> original = new ArrayList<>();
Collections.addAll(original, "foo", "bar", "baz");
Iterable<String> filter = select(original, s -> s.startsWith("b"));
System.out.println(String.join(", ", filter));
original.removeIf(s -> !s.endsWith("r"));
System.out.println(String.join(", ", filter));

Самая большая проблема при реализациитакой Iterator, должен предоставить двум методам hasNext и next правильную семантику, без каких-либо гарантий относительно того, как вызывающий вызов вызовет их, т.е. вы не можете предполагать, что он никогда не вызовет hasNext() дважды, ничто next() всегда будет вызываться с предшествующим hasNext().

Та же логика может быть реализована намного проще с помощью Stream API:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    return () -> StreamSupport.stream(it.spliterator(), false)
        .filter(pred).iterator();
}
1 голос
/ 29 мая 2019

Поскольку любой Collection равен Iterable, просто добавьте соответствующие элементы в новую коллекцию и верните ее позже:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    Collection<T> collection = new ArrayList<>();
    for (T s: it) {
        if (!pred.test(s)) {
            collection.add(s);
        }
    }
    return collection;
}

Несколько идей:

  • Выражение pred.test(s)==false должно быть довольно упрощено до !pred.test(s)
  • Все содержание метода можно сократить, используя следующим образом:

    static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
        return StreamSupport.stream(it.spliterator(), false)
            .filter(pred)
            .collect(Collectors.toList());
    }
    
0 голосов
/ 31 мая 2019

Альтернативное решение Хольгера, которое я имел в виду в комментарии, выглядит следующим образом:

static <T> Iterable<T> select(Iterable<T> toIterate, Predicate<T> pred) {
    return () -> new Iterator<T>() {
        Iterator<T> delegate = toIterate.iterator();
        T next = findNextValid();
        public boolean hasNext() {
            return next != null;
        }
        public T next() {
            if (next == null) throw new NoSuchElementException();
            T result = next;
            next = findNextValid();
            return result;
        }
        private T findNextValid() {
            T result = null;
            while (result == null && delegate.hasNext()) {
                T candidate = delegate.next();
                if (pred.test(candidate)) {
                    result = candidate;
                }
            }
            return result;
        }
    };
}

Разница в том, что нет необходимости в дополнительном маркере для hasCurrent, и он опережает Iteratorдо того, как на самом деле запрашивается следующий элемент.Возможно, вы считаете, что последнее нежелательно.

0 голосов
/ 31 мая 2019

Сначала оберните Iterable<T> в Stream<T>:

  • Обычный Java:

    StreamSupport.stream(it.spliterator(), false)
    
  • Гуава

    Streams.stream(it)
    
  • StreamEx

    StreamEx.of(it.iterator())
    

Затем отфильтруйте его по Predicate<T>:

...
stream.filter(pred.negate())
...

И, наконец, верните Iterable<T>:

  • как lambda:

    return () -> stream.iterator();
    
  • как method reference

    return stream::iterator;
    

Полный пример:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    return StreamSupport.stream(it.spliterator(), false).filter(pred.negate())::iterator;
}

или:

static <T> Iterable<T> select(Iterable<T> it, Predicate<T> pred) {
    Stream<T> stream = stream(it.spliterator(), false);
    Predicate<T> negatedPred = pred.negate();
    Stream<T> filteredStream = stream.filter(negatedPred);
    return filteredStream::iterator;
}
...