所以这真让我感到困惑。假设我有Player
个对象,Point p
包含x
和y
值:
class Player {
void movePlayer(Point p) {
...
}
}
如果我有一堆静态点(当然比玩家更多),我需要随机地,但唯一地,映射到每个玩家的movePlayer函数,我该怎么做?这个过程不需要快速完成,但每次都经常随机完成。要添加一个复杂的图层,我的点是由变化的x和y值生成的。截至目前,我正在做以下事情(这使我的JVM崩溃了):
public List<Stream<Point>> generatePointStream() {
Random random = new Random();
List<Stream<Point>> points = new ArrayList<Stream<Point>>();
points.add(random.ints(2384, 2413).distinct().mapToObj(x -> new Point(x, 3072)));
points.add(random.ints(3072, 3084).distinct().mapToObj(y -> new Point(2413, y)));
....
points.add(random.ints(2386, 2415).distinct().mapToObj(x -> new Point(x, 3135)));
Collections.shuffle(points);
return points;
}
请注意,在我仅使用Stream.concat
方法的一个流之前,但是这会导致错误并且看起来非常丑陋,导致我陷入目前的困境。并将它们分配给List<Player> players
中的所有Player对象:
players.stream().forEach(p->p.movePlayer(generatePointStream().stream().flatMap(t->t).
findAny().orElse(new Point(2376, 9487))));
现在,当我使用一些荒谬的抽象Stream<Stream<Point>>
时,这几乎起作用,除了它只使用了第一个Stream<Point>
中的点。
我在这里完全忽略了溪流的意义吗?我只是喜欢不创建我不会使用的显式Point
对象的想法。
答案 0 :(得分:2)
您应该执行以下操作:
final int PLAYERS_COUNT = 6;
List<Point> points = generatePointStream()
.stream()
.limit(PLAYERS_COUNT)
.map(s -> s.findAny().get())
.collect(Collectors.toList());
此输出
2403, 3135
2413, 3076
2393, 3072
2431, 3118
2386, 3134
2368, 3113
答案 1 :(得分:2)
好吧,你可以定义一个返回Point
的流的方法,如
public Stream<Point> allValues() {
return Stream.of(
IntStream.range(2384, 2413).mapToObj(x -> new Point(x, 3072)),
IntStream.range(3072, 3084).mapToObj(y -> new Point(2413, y)),
//...
IntStream.range(2386, 2415).mapToObj(x -> new Point(x, 3135))
).flatMap(Function.identity());
}
包含所有有效点,但由于Stream的惰性而未实现。然后,创建一个方法来选择随机元素,如:
public List<Point> getRandomPoints(int num) {
long count=allValues().count();
assert count > num;
return new Random().longs(0, count)
.distinct()
.limit(num)
.mapToObj(i -> allValues().skip(i).findFirst().get())
.collect(Collectors.toList());
}
在一个完美的世界中,这已经拥有了你想要的所有懒惰,包括只创建所需数量的Point
个实例。
但是,有几个实现细节可能会使这比仅仅收集到列表更糟糕。
其中一个对flatMap
操作很特殊,请参阅“Why filter() after flatMap() is “not completely” lazy in Java streams?”。不仅要急切处理子流,还要评估可能允许内部优化的流属性。在这方面,基于concat
的流更有效。
public Stream<Point> allValues() {
return Stream.concat(
Stream.concat(
IntStream.range(2384, 2413).mapToObj(x -> new Point(x, 3072)),
IntStream.range(3072, 3084).mapToObj(y -> new Point(2413, y))
),
//...
IntStream.range(2386, 2415).mapToObj(x -> new Point(x, 3135))
);
}
关于创建过于深层的连接流有一个警告,但如果你像这里一样控制创建,你可以创建一个平衡的树,比如
Stream.concat(
Stream.concat(
Stream.concat(a, b),
Stream.concat(c, d)
),
Stream.concat(
Stream.concat(a, b),
Stream.concat(c, d)
)
)
然而,即使这样的Stream允许在没有处理元素的情况下计算大小,但这在Java 9之前不会发生。在Java 8中,count()
将始终迭代所有元素,这意味着已经实例化为在Point
操作之后将所有元素收集到List
时的count()
个实例很多。
更糟糕的是,skip
没有传播到Stream的源代码,因此在说stream.map(…).skip(n).findFirst()
时,映射函数的评估时间最多为n+1
次而不是一次。当然,这使得使用它作为惰性构造的getRandomPoints
方法的整个想法变得毫无用处。由于封装和我们在这里的嵌套流,我们甚至无法在skip
之前移动map
操作。
请注意,临时实例仍然可能比收集到列表更有效,其中所有实例同时存在,但由于我们在这里有更大的数字,因此很难预测。因此,如果实例创建确实是一个问题,我们可以解决这个特定情况,因为构成一个点的两个int
值可以封装在一个原始的long
值中:
public LongStream allValuesAsLong() {
return LongStream.concat(LongStream.concat(
LongStream.range(2384, 2413).map(x -> x <<32 | 3072),
LongStream.range(3072, 3084).map(y -> 2413L <<32 | y)
),
//...
LongStream.range(2386, 2415).map(x -> x <<32 | 3135)
);
}
public List<Point> getRandomPoints(int num) {
long count=allValuesAsLong().count();
assert count > num;
return new Random().longs(0, count)
.distinct()
.limit(num)
.mapToObj(i -> allValuesAsLong().skip(i)
.mapToObj(l -> new Point((int)(l>>>32), (int)(l&(1L<<32)-1)))
.findFirst().get())
.collect(Collectors.toList());
}
这确实只会创建num
的{{1}}个实例。