1
\$\begingroup\$

I have this method that takes a list of my individuals (class has public field fitnessValue). I sum all fitnessValue and then draw a random value between 0 and sum of fitnessValue. In the end, I check for each individual where cumulative sum of fitnessValue passes the point I counted before and return the one which passed first.

public Individual pickRandomIndividual(List<Individual> pool) {
    Double totalScore = 0d;
    Double runningScore = 0d;
    for (Individual ind : pool) {
        totalScore += ind.fitnessValue;
    }

    Double rnd = Math.random() * totalScore;

    for (Individual ind : pool) {
        if (rnd >= runningScore &&
                rnd <= runningScore + ind.fitnessValue) {
            return ind;
        }
        runningScore += ind.fitnessValue;
    }
    return null;
}  

I want to refactor this function with Java 8 streams. Any idea how can I achieve that?

I changed it a bit, into:

Double fitnessSum = genePool.stream()
                .mapToDouble(individual -> individual.fitnessValue)
                .sum();
Double runningScore = 0d;

Double rnd = random() * fitnessSum;

for (Individual g : genePool) {
    if (rnd >= runningScore && rnd <= runningScore + g.fitnessValue) {
            return g;
    }
    runningScore += g.fitnessValue;
}
return null;

Still, I do not know how to change cumulative sum part into a stream. Maybe entire logic is poorly implemented? I just want to draw one Individual from the list depending on how large the Individual's fitnessValue is.

\$\endgroup\$
3
\$\begingroup\$

You have to keep in mind that Streams are not the solution to everything. In this case for example, you need to keep track of multiple values, i.e. the current individual, and a cumulative sum (and you have to return a a value as soon as a certain condition is met). Yes, there are ways to do this with Streams, but I think in this case it is best to implement it with a simple loop like you have.

A couple of side notes:

  • Use the primitive type double instead of the wrapper class.
  • Declare variables as late as possible.
  • Perhaps allow the user to pass a Random instance.
  • I think your implementation can be simplified by subtracting, which would then mean you only have to check for < 0, e.g.:

    double fitnessSum = genePool.stream()
            .mapToDouble(individual -> individual.fitnessValue)
            .sum();
    
    double remainder = Math.random() * fitnessSum;
    for (Individual gene : genePool) {
        remainder -= gene.fitnessValue;
        if (remainder <= 0.0) {
            return gene;
        }
    }
    return genePool.get(genePool.size() - 1);
    
\$\endgroup\$
  • \$\begingroup\$ Couldn't agree more. Streams is not the solution for this. Good answer! \$\endgroup\$ – Simon Forsberg May 26 '18 at 17:25
  • \$\begingroup\$ Thanks for the answer. Nice idea with substracting :) I guess the only way to do it by streams it to keep it in a separate field but it looks like an unnecessary workaround. \$\endgroup\$ – Rafalsky May 26 '18 at 20:16
  • 1
    \$\begingroup\$ @Rafalsky It would not only be a workaround, it would be against the nature of streams, being an operation that depends on state that changes during the stream's execution. You might be interested in this. Of course, one can ensure that the stream is sequential, but then, why bother? Streams are not only not a solution to everything, they are also not a replacement, or a newer version, of for loops, so you shouldn't aim to use streams just for the sake of using streams. \$\endgroup\$ – Stingy May 26 '18 at 21:32
2
\$\begingroup\$

There's also this variation on the classic solution to picking a random element from an Iterator while iterating only once:

Double fitnessSum = 0.0;
Individual chosen = null;
for (Individual g : genePool) {
  fitnessSum += g.fitnessValue;
  if (Math.random() * fitnessSum < g.fitnessValue) {
    chosen = g;
  }
}
return g;
\$\endgroup\$
  • 1
    \$\begingroup\$ Though what the other answer said about Random - you really should let the user pass in a Random inject if their own \$\endgroup\$ – Daniel Martin May 26 '18 at 13:40
1
\$\begingroup\$
  • In the second loop, the loop where you update runningScore, there is no need to check whether rnd >= runningScore, because at the first loop iteration, it will be true by default, and at every subsequent iteration, runningScore will be equal to the runningScore of the previous iteration plus the fitnessValue of the previous individual, and if this value were greater than (or even equal to) rnd, then the loop would have already been terminated at the previous iteration. In fact, you could rewrite this loop to:

    for (Individual ind : pool) {
        runningScore += ind.fitnessValue;
        if (rnd <= runningScore) {
            return ind;
        }
    }
    

    That way, you only have to perform the addition once, saving performance. Of course, this assumes that fitnessValue will always be non-negative.

  • Based on how the code is supposed to work, the return null; statement after the last for loop should never be reached. Since the compiler does not know that, you could insert an assert false; before the return statement. It is also possible to use assert statements with a detail message, for example like this:

    assert false : "totalScore: " + totalScore;
    
\$\endgroup\$
  • \$\begingroup\$ Thanks, for an answer. Is it a good idea to use an assert on production code? For me it was always a feature it could be used for testing and debugging. \$\endgroup\$ – Rafalsky May 26 '18 at 21:32
  • 1
    \$\begingroup\$ @Rafalsky Yes, testing and debugging is exactly what assertions are for, which is why they are disabled by default and have to be explicitly enabled with the JVM option -ea. I did not intend to suggest the usage of assertions for anything other than testing and debugging. After all, one of your individuals might be a superhero with infinite fitness, and if you're unlucky and the call to Math.random() yields 0, you'll have a NaN infestation in your method, and the statement presumed to be unreachable will, in fact, be executed. \$\endgroup\$ – Stingy May 26 '18 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.