我有以下实现,用于根据给定谓词修剪元素列表(即删除前导和尾随空元素)。
我想让实现更具可读性,最好使用Java的流API。
/**
* Trim a List based on a given predicate, that is, remove leading
* and trailing elements that match the predicate (but not in-between
* non-matching elements).
*
* @param list the list to trim
* @param trimPredicate the predicate for trimming
* @param <T> type of the list
* @return the same list minus the trimmed elements.
* @throws NullPointerException if the list is {@code null}
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by the list iterator
*/
public static <T> List<T> trim(List<T> list, Predicate<T> trimPredicate)
throws NullPointerException, UnsupportedOperationException {
if (list == null) throw new NullPointerException("list is null");
ListIterator<T> it = list.listIterator();
while (it.hasNext() && trimPredicate.test(it.next())) it.remove();
it = list.listIterator(list.size());
while (it.hasPrevious() && trimPredicate.test(it.previous())) it.remove();
return list;
}
有什么建议吗?
一个例子,让事情更清楚:
对于List<Integer>
,将0
视为空值,请输入以下列表:
[0, 0, 3, 5, 0, 4, 0, -3, 0, 0]
将被修剪为:
[3, 5, 0, 4, 0, -3]
(而且这里至少有两位不同的读者弄错了,这表明了我对代码可读性的看法:)。
答案 0 :(得分:3)
您的原始代码非常易读且高效。推荐使用。
static <T> List<T> trim2(List<T> list, Predicate<T> isEmpty) {
ListIterator<T> it = list.listIterator();
while (it.hasNext() && isEmpty.test(it.next())) {
it.remove();
}
it = list.listIterator(list.size());
while (it.hasPrevious() && isEmpty.test(it.previous())) {
it.remove();
}
return list;
}
使用java的流版本9.只是为了它,不推荐。
static <T> List<T> trim3(List<T> list, Predicate<T> isEmpty) {
Collection<T> ltrimreverse = list.stream().dropWhile(isEmpty)
.collect(ArrayDeque::new, ArrayDeque::push, ArrayDeque::addAll);
Collection<T> rtrim = ltrimreverse.stream().dropWhile(isEmpty)
.collect(ArrayDeque::new, ArrayDeque::push, ArrayDeque::addAll);
return new ArrayList<>(rtrim);
}
答案 1 :(得分:1)
一点点hackery使它成为可能,但我不确定它是否更清楚。
/**
* Stateful predicate to only match until matching fails.
* It seems this is not necessary in Java 9.
*/
static class MatchWhile<T> implements Predicate<T> {
final Predicate<T> matcher;
boolean match = true;
MatchWhile(Predicate<T> matcher) {
this.matcher = matcher;
}
@Override
public boolean test(T t) {
return match && (match = matcher.test(t));
}
}
// Hides the horrible stuff.
static <T> Stream<T> asStream(Iterator<T> it) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it,Spliterator.ORDERED), false);
}
<T> List<T> trim2(List<T> list, Predicate<T> isEmpty) {
// Trim right using a Deque to reverse it.
Deque<T> reversedAndTrimmedAtEnd = asStream(new ArrayDeque<>(list).descendingIterator())
.filter(new MatchWhile<>(isEmpty).negate())
.collect(Collectors.toCollection(ArrayDeque::new));
// Reverse it again to trim left.
List<T> leftTrimmed = asStream(reversedAndTrimmedAtEnd.descendingIterator())
.filter(new MatchWhile<>(isEmpty).negate())
.collect(Collectors.toList());
return leftTrimmed;
}
答案 2 :(得分:1)
如果您关注效率,则应避免重复单remove
次操作,尤其是在List
的开头,因为最常用的实现ArrayList
不能很好地执行好吧,因为它必须在删除条目时复制其内部数组中的所有剩余元素。
最坏的情况,即以这种方式删除所有元素,将具有二次时间复杂度。
public static <T> List<T> trim(List<T> list, Predicate<T> trimPredicate) {
Objects.requireNonNull(list, "list is null");
Objects.requireNonNull(trimPredicate, "trimPredicate is null");
int lastMatch;
for(ListIterator<T> it = list.listIterator(lastMatch = list.size());
it.hasPrevious() && trimPredicate.test(it.previous());) lastMatch = it.nextIndex();
if(lastMatch < list.size()) list.subList(lastMatch, list.size()).clear();
for(ListIterator<T> it = list.listIterator(lastMatch = 0);
it.hasNext() && trimPredicate.test(it.next()); ) lastMatch = it.previousIndex();
if(lastMatch > 0) list.subList(0, lastMatch+1).clear();
return list;
}
List.subList(…).clear()
是有效删除一系列项目的正确习惯用法。在ArrayList
的情况下,它意味着对整个范围移除仅进行单个复制操作。所以我们只是迭代而不删除,以识别范围,然后执行单个删除操作。
由于最后删除没有额外费用,因为没有剩余的要复制的元素,此解决方案首先删除最后的匹配,以便可能减少后续删除匹配的剩余元素数量列表。
对于将直接修改列表的就地操作,没有可以改进它的Stream解决方案。
即使您想要返回新列表,最有效的解决方案之一也将基于subList
:
public static <T> List<T> trim(List<T> list, Predicate<T> trimPredicate) {
Objects.requireNonNull(list, "list is null");
Objects.requireNonNull(trimPredicate, "trimPredicate is null");
int firstToKeep = 0, lastToKeep = list.size();
for(T t: list) if(trimPredicate.test(t)) firstToKeep++; else break;
for(ListIterator<T> it = list.listIterator(lastToKeep);
lastToKeep > firstToKeep && it.hasPrevious() && trimPredicate.test(it.previous());)
lastToKeep = it.nextIndex();
return new ArrayList<>(list.subList(firstToKeep, lastToKeep));
}