java tails_java - 你能将一个流分成两个流吗?

java - 你能将一个流分成两个流吗?

我有一个由Java 8流表示的数据集:

Stream stream = ...;

我可以看到如何过滤它以获得随机子集 - 例如

Random r = new Random();

PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();

Stream heads = stream.filter((x) -> (coin.nextInt() == 0));

我还可以看到如何减少此流以获取,例如,两个列表代表数据集的两个随机半部分,然后将它们转换回流。但是,有没有直接的方法从最初的流生成两个流? 就像是

(heads, tails) = stream.[some kind of split based on filter]

感谢您的任何见解。

9个解决方案

230 votes

可以使用收集器。

对于两个类别,请使用Stream工厂。

这将从IntStream到.collect(Collectors)创建Stream,并基于Predicate将项目放在一个或另一个列表中。

注意:由于流需要整体消耗,因此无法处理无限流。 因为无论如何都要使用流,所以此方法只是将它们放入列表中,而不是使用内存创建新的流。

此外,不需要迭代器,即使在您提供的仅限于头部的示例中也是如此。

Random r = new Random();

Map> groups = stream

.collect(Collectors.partitioningBy(x -> r.nextBoolean()));

System.out.println(groups.get(false).size());

System.out.println(groups.get(true).size());

有关更多类别,请使用Stream工厂。

Map> groups = stream

.collect(Collectors.groupingBy(x -> r.nextInt(3)));

System.out.println(groups.get(0).size());

System.out.println(groups.get(1).size());

System.out.println(groups.get(2).size());

如果流不是Stream,而是其中一个原始流,如IntStream,则此.collect(Collectors)方法不可用。 没有收集器工厂,您必须以手动方式完成。 它的实现如下:

IntStream intStream = IntStream.iterate(0, i -> i + 1).limit(1000000);

Predicate p = x -> r.nextBoolean();

Map> groups = intStream.collect(() -> {

Map> map = new HashMap<>();

map.put(false, new ArrayList<>());

map.put(true, new ArrayList<>());

return map;

}, (map, x) -> {

boolean partition = p.test(x);

List list = map.get(partition);

list.add(x);

}, (map1, map2) -> {

map1.get(false).addAll(map2.get(false));

map1.get(true).addAll(map2.get(true));

});

System.out.println(groups.get(false).size());

System.out.println(groups.get(true).size());

编辑

正如所指出的,以上的解决方法&#39; 不是线程安全的。 在收集之前转换为正常Stream是要走的路:

Stream stream = intStream.boxed();

Mark Jeronimus answered 2019-07-03T08:36:14Z

18 votes

不幸的是,你要求的东西在JavaDoc of Stream中直接不受欢迎:

应该操作流(调用中间或终端   流操作)只有一次。 例如,这排除了&#34;分叉&#34;   流,相同的源提供两个或多个管道,或   多次遍历同一个流。

如果您真的希望这种行为,您可以使用Stream或其他方法解决此问题。 在这种情况下,您应该做的不是尝试使用分支过滤器从同一原始Stream源备份两个流,而是复制流并适当地过滤每个重复项。

但是,您可能希望重新考虑Stream是否适合您的用例。

Trevor Freeman answered 2019-07-03T08:37:07Z

13 votes

我偶然发现了这个问题,我觉得分叉的流有一些可以证明有效的用例。 我将以下代码作为消费者编写,以便它不会做任何事情,但您可以将其应用于函数和您可能遇到的任何其他内容。

class PredicateSplitterConsumer implements Consumer

{

private Predicate predicate;

private Consumer positiveConsumer;

private Consumer negativeConsumer;

public PredicateSplitterConsumer(Predicate predicate, Consumer positive, Consumer negative)

{

this.predicate = predicate;

this.positiveConsumer = positive;

this.negativeConsumer = negative;

}

@Override

public void accept(T t)

{

if (predicate.test(t))

{

positiveConsumer.accept(t);

}

else

{

negativeConsumer.accept(t);

}

}

}

现在你的代码实现可能是这样的:

personsArray.forEach(

new PredicateSplitterConsumer<>(

person -> person.getDateOfBirth().isPresent(),

person -> System.out.println(person.getName()),

person -> System.out.println(person.getName() + " does not have Date of birth")));

LudgerP answered 2019-07-03T08:37:42Z

9 votes

不完全是。 你不能从中获得两个Streams; 这没有意义 - 如何在不需要同时生成另一个的情况下迭代一个? 流只能运行一次。

但是,如果要将它们转储到列表或其他内容中,则可以执行此操作

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));

Louis Wasserman answered 2019-07-03T08:34:48Z

7 votes

这违反了Stream的一般机制。 假设您可以将Stream S0拆分为Sa和Sb,就像您想要的那样。 在Sa上执行任何终端操作,例如count(),必然会消耗&#34; S0中的所有元素。 因此Sb丢失了它的数据源。

以前,我认为Stream有一个tee()方法,它将流复制到两个。 它现在被删除了。

Stream虽然有peek()方法,但您可以使用它来实现您的要求。

ZhongYu answered 2019-07-03T08:38:27Z

5 votes

不完全是这样,但您可以通过调用Collectors.groupingBy()来完成所需的操作。您可以创建一个新的Collection,然后可以在该新集合上实例化流。

aepurniet answered 2019-07-03T08:38:55Z

1 votes

这是我能想到的最不好的答案。

import org.apache.commons.lang3.tuple.ImmutablePair;

import org.apache.commons.lang3.tuple.Pair;

public class Test {

public static Pair splitStream(Stream inputStream, Predicate predicate,

Function, L> trueStreamProcessor, Function, R> falseStreamProcessor) {

Map> partitioned = inputStream.collect(Collectors.partitioningBy(predicate));

L trueResult = trueStreamProcessor.apply(partitioned.get(Boolean.TRUE).stream());

R falseResult = falseStreamProcessor.apply(partitioned.get(Boolean.FALSE).stream());

return new ImmutablePair(trueResult, falseResult);

}

public static void main(String[] args) {

Stream stream = Stream.iterate(0, n -> n + 1).limit(10);

Pair, String> results = splitStream(stream,

n -> n > 5,

s -> s.filter(n -> n % 2 == 0).collect(Collectors.toList()),

s -> s.map(n -> n.toString()).collect(Collectors.joining("|")));

System.out.println(results);

}

}

这将获取整数流并将它们拆分为5.对于大于5的那些,它仅过滤偶数并将它们放入列表中。 对于其余的,它与|连接。

输出:

([6, 8],0|1|2|3|4|5)

它并不理想,因为它将所有内容收集到打破流的中间集合中(并且有太多的参数!)

Ian Jones answered 2019-07-03T08:39:42Z

1 votes

我偶然发现了这个问题,同时寻找一种方法来过滤流中的某些元素并将其记录为错误。 所以我真的不需要拆分流,就像将一个过早的终止动作附加到具有不显眼语法的谓词一样。 这就是我想出的:

public class MyProcess {

/* Return a Predicate that performs a bail-out action on non-matching items. */

private static Predicate withAltAction(Predicate pred, Consumer altAction) {

return x -> {

if (pred.test(x)) {

return true;

}

altAction.accept(x);

return false;

};

/* Example usage in non-trivial pipeline */

public void processItems(Stream stream) {

stream.filter(Objects::nonNull)

.peek(this::logItem)

.map(Item::getSubItems)

.filter(withAltAction(SubItem::isValid,

i -> logError(i, "Invalid")))

.peek(this::logSubItem)

.filter(withAltAction(i -> i.size() > 10,

i -> logError(i, "Too large")))

.map(SubItem::toDisplayItem)

.forEach(this::display);

}

}

Sebastian Hans answered 2019-07-03T08:40:11Z

-2 votes

怎么样:

Supplier> randomIntsStreamSupplier =

() -> (new Random()).ints(0, 2).boxed();

Stream tails =

randomIntsStreamSupplier.get().filter(x->x.equals(0));

Stream heads =

randomIntsStreamSupplier.get().filter(x->x.equals(1));

Matthew answered 2019-07-03T08:40:34Z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮您实现一个吐金币小游戏。以下是代码实现: ```python import random class Coin: def __init__(self, value): self.value = value self.is_heads = random.choice([True, False]) def __str__(self): return f"{self.value} {'heads' if self.is_heads else 'tails'}" class Player: def __init__(self, name): self.name = name self.coins = [] def add_coin(self, coin): self.coins.append(coin) def remove_coin(self): if len(self.coins) > 0: return self.coins.pop(0) else: return None class CoinGame: def __init__(self, player_names): self.players = [Player(name) for name in player_names] self.current_player_index = 0 self.total_score = 0 def play(self): while True: current_player = self.players[self.current_player_index] input(f"{current_player.name}, press enter to toss your coin.") coin = Coin(random.randint(1, 10)) current_player.add_coin(coin) print(f"{current_player.name} got {coin}.") if coin.is_heads: current_player_score = sum([coin.value for coin in current_player.coins]) self.total_score += current_player_score print(f"{current_player.name} got {current_player_score} points. Total score: {self.total_score}") if self.total_score >= 50: print(f"{current_player.name} wins!") break else: print(f"{current_player.name} got tails. No points for this turn.") self.current_player_index = (self.current_player_index + 1) % len(self.players) if __name__ == "__main__": game = CoinGame(["Player 1", "Player 2"]) game.play() ``` 这个游戏的规则是:每个玩家轮掷一枚硬币,如果是正面朝上则加上硬币面值的分数,如果是反面朝上则不得分。当某个玩家的分数达到50或以上时,他将获胜。 希望这个代码可以满足您的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值