Java Stream有状态的findFirst

3
下面的方法是用于选择歌曲的加权随机选择算法的一部分。
我想将下面的方法转换为使用流,以确定是否更清晰/更可取。我不确定这是否可能,因为计算是一种有状态的操作,依赖于列表中的位置。
public Song songForTicketNumber(long ticket)
{
    if(ticket<0) return null;
    long remaining = ticket;
    for(Song s : allSongs)  // allSongs is ordered list
    {
        rem-=s.numTickets; // numTickets is a long and never negative
        if(remaining<0)
            return s;
    }
    return null;
}

更正式地说:如果nallSongs中每个Song对象的numTickets之和,则对于任何整数0n-1,上述方法应返回列表中的一首歌曲。将返回特定Song对象x的整数数量将由x.numTickets决定。特定歌曲的选择条件是由其numTickets属性和列表中左侧项目的numTickets属性共同确定的连续整数范围。目前的方法会在范围外返回null。
注意:超出范围的行为可以修改以适应流(而不是返回null)。

1
这不适用于流。 - NiVeR
你的代码存在短路现象,即在找到票据时停止循环。流式解决方案不会这样做,因此不要使用流。流是一个强大的工具,但像所有工具一样,它只适用于某些任务。你的任务不是其中之一,而且你第一个提示就是“有状态”的单词。除了收集器之外,流基本上是无状态的。这是你使用流时需要遵循的合同的一部分。 - Andreas
2
流不应该是有状态的。建议作为流操作参数的函数应该是无状态的:“为了保持正确的行为,在大多数情况下,这些行为参数必须是无状态的(它们的结果不应该依赖于在流管道执行期间可能发生变化的任何状态)。” - 当使用流可以使事情更简单时,请使用它们(这意味着不要强迫使用它们,特别是如果您对它们不完全理解)。通过不专注于不必要的更改来节省时间。 - Dioxin
2个回答

1
一个 Stream 相对于基本的 for 或 for-each 循环的效率取决于具体情况。在你的情况下,Stream 可能比你当前的代码更低效,主要原因有以下几点:
  1. 如你所提及,你的函数是有状态的。使用这种方法维护状态可能意味着需要捣鼓某种匿名实现的 BinaryOperatorStream.reduce 结合使用,而这将比你当前的代码更加臃肿和难以理解。
  2. 你正在当前循环中进行短路操作,没有任何 Stream 操作会反映出这种效率,特别是考虑到这一点与 #1 的结合。
  3. 你的集合是有序的,这意味着流将以非常类似于现有循环的方式迭代元素。根据集合的大小,你可能会从 parallelStream 中获得一些效率,但在这种情况下需要维护顺序,这将导致流的效率降低。
唯一从切换到Stream中获得的真正好处是内存消耗的差异(您可以将allSongs保留在外部内存中,让Stream以更节省内存的方式处理),但这似乎不适用于此处。
总之,由于Stream操作会更加复杂,并且可能会对您的效率产生负面影响,因此我建议您不要追求这种变化。
话虽如此,就实际回答您如何将此工作转换为Stream的问题,我个人无法想出基于Stream的解决方案。再次强调,这将涉及到一个类似于减速器或其他奇怪的东西...(如果这不够详细,我会删除这个答案。)

0

Java流具有短路评估的功能,例如可以查看findFirst()的文档。话虽如此,递减和检查remaining需要状态变化,这并不是很好的做法。虽然不太好,但仍然可行:

    public Optional<Song> songForTicketNumber(long ticket, Stream<Song> songs) {
        if (ticket < 0) return Optional.empty();

        AtomicLong remaining = new AtomicLong(ticket);
        return songs.filter(song -> decrementAndCheck(song, remaining)).findFirst();
    }

    private boolean decrementAndCheck(Song song, AtomicLong total) {
        total.addAndGet(-song.numTickets);
        return total.get() < 0;
    }


据我所知,这种方法的唯一优点就是如果需要的话,你可以切换到并行流。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接