在从事玩具项目时,我遇到了生成一组N 2d点的问题,其中每个点都在距离A和B之间,与集合中的每个其他点之间(以及在某些绝对边界内)。
我更喜欢使用java stream和lambdas进行练习,因为它们的优雅和易于并行化的可能性,所以我不询问如何以命令式方式解决这个问题!
首先想到的解决方案是:
对于命令式编程(循环)来说,这对我来说是微不足道的,但是在执行此功能时我感到困惑,因为流中新生成的元素依赖于先前在同一流中生成的元素。
这是我想出的 - 注意开头的icky循环。
while (pointList.size() < size) {
// find a suitable position, not too close and not too far from another one
Vec point =
// generate a stream of random vectors
Stream.generate(vecGen::generate)
// elongate the vector and add it to the position of one randomly existing vector
.map(v -> listSelector.getRandom(pointList).add(v.mul(random.nextDouble() * (maxDistance - minDistance) + minDistance)))
// remove those that are outside the borders
.filter(v -> v.length < diameter)
// remove those that are too close to another one
.filter(v -> pointList.stream().allMatch(p -> Vec.distance(p, v) > minDistance))
// take the first one
.findAny().get();
pointList.add(point);
}
我知道这个循环可能永远不会终止,具体取决于参数 - 真正的代码有额外的检查。
我想到的一个有效的功能解决方案是生成完全随机的N个向量集,直到其中一个集合满足条件,但性能会很糟糕。此外,这将绕过我面临的问题:是否可以使用流中已经生成的元素同时向流中添加新元素(非常肯定会违反某些基本原则,所以我猜答案是否定的) ?
有没有办法以功能性而非浪费的方式做到这一点?
答案 0 :(得分:1)
下面显示了一个简单的解决方案。 Pair类可以在Apache commons lang3中找到。
public List<Pair<Double, Double>> generate(int N, double A, double B) {
Random ySrc = new Random();
return new Random()
.doubles(N, A, B)
.boxed()
.map(x -> Pair.of(x, (ySrc.nextDouble() * (B - A)) + A))
.collect(Collectors.toList());
}
我的原始解决方案(上图)错过了A和B表示任意两点之间的最小和最大距离的点。因此,我会提出一种不同的解决方案(更复杂的方法),它依赖于在单位圆上生成点。我使用最小为-1/2 B且最大为1/2 B的随机距离来缩放(乘)表示该点的单位向量。该方法在由半径1/2 B的圆所界定的区域中均匀分布点。这解决了点约束之间的最大距离。给定A和B之间的足够差异,其中A <1。 B和N不是太大,也可能满足最小距离约束。满足最大距离约束可以用纯函数代码完成(即没有副作用)。
为了确保满足最小约束,需要一些命令性代码(即副作用)。为此,我使用带副作用的谓词。谓词累积满足最小约束条件的点,并在累积N个点时返回true。
请注意,运行时间未知,因为随机生成了点。当N = 100,A = 1.0,B = 30.0时,测试代码快速运行。我为B尝试了10和20的值,并没有等到它结束。如果你想要一个更紧密的点集,你可能需要加快这个代码或开始查看线性求解器。
public class RandomPoints {
/**
* The stop rule is a predicate implementation with side effects. Not sure
* about the wisdom of this approach. The class does not support concurrent
* modification.
*
* @author jgmorris
*
*/
private class StopRule implements Predicate<Pair<Double, Double>> {
private final int N;
private final List<Pair<Double, Double>> points;
public StopRule(int N, List<Pair<Double, Double>> points) {
this.N = N;
this.points = points;
}
@Override
public boolean test(Pair<Double, Double> t) {
// Brute force test. A hash based test would work a lot better.
for (int i = 0; i < points.size(); ++i) {
if (distance(t, points.get(i)) < dL) {
// List size unchanged, continue
return false;
}
}
points.add(t);
return points.size() >= N;
}
}
private final double dL;
private final double dH;
private final double maxRadius;
private final Random r;
public RandomPoints(double dL, double dH) {
this.dL = dL;
this.dH = dH;
this.maxRadius = dH / 2;
r = new Random();
}
public List<Pair<Double, Double>> generate(int N) {
List<Pair<Double, Double>> points = new ArrayList<>();
StopRule pred = new StopRule(N, points);
new Random()
// Generate a uniform distribution of doubles between 0.0 and
// 1.0
.doubles()
// Transform primitive double into a Double
.boxed()
// Transform to a number between 0.0 and 2ϖ
.map(u -> u * 2 * Math.PI)
// Generate a random point
.map(theta -> randomPoint(theta))
// Add point to points if it meets minimum distance criteria.
// Stop when enough points are gathered.
.anyMatch(p -> pred.test(p));
return points;
}
private final Pair<Double, Double> randomPoint(double theta) {
double x = Math.cos(theta);
double y = Math.sin(theta);
double radius = randRadius();
return Pair.of(radius * x, radius * y);
}
private double randRadius() {
return maxRadius * (r.nextDouble() - 0.5);
}
public static void main(String[] args) {
RandomPoints rp = new RandomPoints(1.0, 30.0);
List<Pair<Double, Double>> points = rp.generate(100);
for (int i = 0; i < points.size(); ++i) {
for (int j = 1; j < points.size() - 1; ++j) {
if (i == j) {
continue;
}
double distance = distance(points.get(i), points.get(j));
if (distance < 1.0 || distance > 30.0) {
System.out.println("oops");
}
}
}
}
private static double distance(Pair<Double, Double> p1, Pair<Double, Double> p2) {
return Math.sqrt(Math.pow(p1.getLeft() - p2.getLeft(), 2.0) + Math.pow(p1.getRight() - p2.getRight(), 2.0));
}
}