【问题标题】:How to judge whether a list is a sub sequence of another with java8 stream?java8流如何判断一个list是否是另一个list的子序列?
【发布时间】:2017-02-17 08:23:42
【问题描述】:

例如,我有一个很长的列表[1, 2, 3, ..., 10],还有一个很短的[1, 3, 6],那么我可以看出这个短的就是另一个的子序列。另一方面,列表[1 6 3] 并不是因为它违反了顺序约束。

下面是我针对这个问题的 java7 样式代码:

List<Integer> sequence = Arrays.asList(1, 3, 6);
List<Integer> global = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Iterator<Integer> iterGlobal = global.iterator();
boolean allMatch = true;
for(Integer itemSequence: sequence) {
    boolean match = false;
    while(iterGlobal.hasNext()) {
        if(itemSequence.equals(iterGlobal.next())) {
            match = true;
            break;
        }
    }
    if(!match) {
        allMatch = false;
        break;
    }
}
System.out.println(allMatch); //=> true

而我的愿望是找到一个java8流样式来达到同样的效果。

【问题讨论】:

  • 如果输入是1, 2, 3, 4, 5, 6, 3,那么1, 3, 61, 6, 3可能有效?
  • 我猜答案是肯定的。
  • @Flown:是的,两者都是有效的。

标签: java functional-programming java-8 java-stream


【解决方案1】:

很难找到真正的函数式解决方案,即不包含可变状态。到目前为止,所有答案都包含可变状态这一事实最好地说明了这一点。

此外,没有List.indexOf(T object, int startIndex) 操作。为了说明它有多么有用,让我们通过辅助方法来定义它:

public static int indexOf(List<?> list, int startIndex, Object o) {
    if(startIndex!=0) list=list.subList(startIndex, list.size());
    int ix=list.indexOf(o);
    return ix<0? -1: ix+startIndex;
}

如果担心的话,很容易找到没有临时对象的替代实现

现在,使用可变状态的简单解决方案是:

boolean allMatch = sequence.stream().allMatch(new Predicate<Integer>() {
    int index = 0;
    public boolean test(Integer t) {
        return (index = indexOf(global, index, t)) >=0;
    }
});

没有可变状态的函数式解决方案需要一个值类型在两个列表中保持两个位置。当我们为此使用int[2] 数组时,解决方案是:

boolean allMatch = Stream.iterate(
        new int[]{ 0, global.indexOf(sequence.get(0)) },
        a -> new int[] { a[0]+1, indexOf(global, a[1], sequence.get(a[0]+1)) }
    )
    .limit(sequence.size())
    .allMatch(a -> a[1]>=0);

【讨论】:

  • 这很好。我希望你不介意我接受你的想法并使用它有点不同,因为 jdk-9 可以像使用 hasNextnext 的迭代器一样迭代 Stream。
【解决方案2】:

我是提问者,我首先回答我的问题只是为了标记:

List<Integer> sequence = Arrays.asList(1, 3, 6);
List<Integer> global = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Iterator<Integer> iter = global.iterator();
boolean subSequence = sequence.stream().allMatch(itemSequence -> {
    return Stream.generate(iter::next)
            .anyMatch(itemGlobal -> itemSequence.equals(itemGlobal));
});
System.out.println(subSequence);

它适用于序列列表 [1, 3, 6],而序列 [1, 6, 3] 则抛出错误 java.util.NoSuchElementException。这不是我最终想要达到的目标。

【讨论】:

  • 嗯,如果这不是您想要实现的目标,那么您不应该将其发布为答案,因为它可能会使未来寻找此问题的实际解决方案的读者感到困惑。最好将其作为示例包含在您的问题中。
【解决方案3】:

@Eugene 的@Run 答案变体的变体将涉及在全局 List 值上调用 Iterable::spliterator,然后将结果应用到 StreamSupport::stream:

final Spliterator<Integer> spliterator = global.spliterator();

final boolean subSequence = sequence.stream().allMatch(
  itemSequence -> StreamSupport.stream(
    spliterator,
    false
  ).anyMatch(itemSequence::equals)
);

System.out.println(subSequence);

【讨论】:

  • 如果 global = [1,6,3],subSequence = false,如果 global = [1,3,6],subSequence 为 true,遵循 @Run 的顺序约束。注意 List::spliterator 报告 Spliterator.SIZED 和 Spliterator.ORDERED,与实际 List 接口默认方法一样。
【解决方案4】:

我认为您已经非常接近解决方案(我什至没有想过像这样的Iterator,所以对您来说是一个加分项)。问题是 Stream.generate 是无限流。

我只是稍微更改了您的代码。

    Iterator<Integer> iter = global.iterator();

    boolean subSequence = sequence.stream().allMatch(itemSequence -> {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iter, Spliterator.ORDERED), false)
                .anyMatch(itemGlobal -> itemSequence.equals(itemGlobal));
    });
    System.out.println(subSequence);

【讨论】:

    【解决方案5】:

    我会在看到 Holger 的回答后添加另一个选项;但这仅适用于 jdk-9 Stream.iterate

    我用同样的方式定义了一个辅助方法,只是有点不同:

    private static int fits(List<Integer> global, int elementIndex, int element) {
        return global.indexOf(element) >= elementIndex ? global.indexOf(element) : -1;
    }
    

    然后只需使用int[2]

    boolean allMatch = Stream.iterate(new int[] { 0, 0 },
                array -> array[0] < sequence.size() && array[1] >= 0,
                array -> new int[] { array[0] + 1, fits(global, array[1], sequence.get(a[0])) })
                .allMatch(array -> array[0] >= array[1]);
    

    编辑 Holger 是对的,这仅适用于非重复项。

    我也可以为重复编写它,但是现在需要调用两次 fits

     boolean allMatch = Stream.iterate(new int[] { 0, 0 },
                a -> {
                    return a[0] == sequence.size() ? false : fits(global, sequence.get(a[0])) >= a[1];
                },
                a -> {
                    int nextFits = fits(global, sequence.get(a[0]));
                    return new int[] { a[0] + 1, nextFits > a[1] ? nextFits + 1 : -1 };
                })
                .count() == sequence.size();
    

    【讨论】:

    • fits 方法的问题在于,当列表有重复项时,它不会产生相同的结果。除此之外,每次从头开始搜索甚至两次都可能比创建轻量级子列表实例的恒定成本要高得多。
    • 请注意expression? false: condition!expression &amp;&amp; condition 相同。换句话说,你可以写a[0]!=sequence.size() &amp;&amp; fits(global, sequence.get(a[0]))&gt;=a[1]而不是a[0]==sequence.size()? false: fits(global, sequence.get(a[0]))&gt;=a[1]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-14
    • 2011-03-01
    • 1970-01-01
    相关资源
    最近更新 更多