While working on a toy project I was faced with the problem of generating a set of N 2d points where every point was between distance A and B from every other point in the set (and also within certain absolute bounds).
I prefer working with java streams and lambdas for practice, because of their elegance and the possibility for easy parallelization, so I'm not asking how to solve this problem in an imperative manner!
The solution that first came to mind was:
- seed the set (or list) with a random vector
- until the set reaches size N:
- create a random vector with length between A and B and add it to a random "parent" vector
- if it's outside the bounds or closer than A to any vector in the set, discard it, otherwise add it to the set
- repeat
This would be trivial for me with imperative programming (loops), but I was stumped when doing this the functional way because the newly generated elements in the stream depend on previously generated elements in the same stream.
Here's what I came up with - notice the icky loop at the beginning.
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);
}
I know that this loop might never terminate, depending on the parameters - the real code has additional checks.
One working functional solution that comes to mind is to generate completely random sets of N vectors until one of the sets satisfy the condition, but the performance would be abysmal. Also, this would circumvent the problem I'm facing: is it possible to work with the already generated elements in a stream while adding new elements to the stream (Pretty sure that would violate some fundamental principle, so I guess the answer is NO)?
Is there a way to do this in a functional - and not too wasteful - way?