11

I am new to lambda expressions and am trying to use them to reduce the following code to the lambda equivalent. I have looked into reduce and flatMap and forEach, as well as several other things, but I am obviously missing something because everything that I try is either syntactically incorrect or I don't have a reference for what I need.

I need to perform an analysis of each element against all other elements in a collection. I coded that as nested loops with a conditional. Once non-matching elements have been identified, a computation is done using both elements. Finally, I want a collection of results for each comparative computation.

So, here's the original code:

final List<Element> updated = new ArrayList<>(elements.size());

for (final Element first : elements) {
    Attribute newAttribute = first.getAttribute();

    for (final Element second : elements) {
        if (!first.equals(second)) {
            newAttribute = newAttribute.add(computeChange(first, second));
        }
    }
    final Element newElement = new Element(first.getEntry(), newAttribute, first.getValue());
    updated.add(newElement);
}

Then, I tried many variations of lambda expressions, the simplest of which is:

elements.parallelStream()
         .map(first -> new Element(first.getEntry(), first.getAttribute().add(
         computeChange(first, second)), first
         .getValue())).collect(Collectors.toList()));

Obviously, this is wrong as there is no reference to second available to me and no condition/filter for second being not equal to first.

How do I reduce this nested loop with conditional returning a collection to a lambda expression?

Any help here is greatly appreciated.

mkobit
  • 43,979
  • 12
  • 156
  • 150
Todd
  • 1,895
  • 4
  • 15
  • 18
  • 3
    This is tough because your `newAttribute = newAttribute.add(...)` updates aren't parallelizable. This would be easier if you could aggregate all of the `computeChange` results and then create an `Attribute` (or `Element`) from that aggregate. – John Kugelman Apr 28 '15 at 20:13
  • I would just leave it as it is. – Paul Boddington Apr 28 '15 at 20:18
  • What does `computeChange` return? An `Element`, `Attribute`, or a number? – tobias_k Apr 28 '15 at 20:45
  • Not 100% sure, but I think what you are trying to do is not possible, at least not with parallel streams. You'd have to use `reduce` with three parameters, as you are trying to accumulate Attributes and Elements, but then you'll need a combiner for combining the results of the different parallel streams, combining two Attributes to a new one. So: How would you combine two Attributes? – tobias_k Apr 28 '15 at 21:02
  • computeChange returns an Attribute – Todd Apr 28 '15 at 22:52
  • To combine two attributes, the add method is called. – Todd Apr 28 '15 at 22:52
  • parallel streams are not required. I was just trying that stream as one of my options. – Todd Apr 28 '15 at 22:57

1 Answers1

3

Try:

elements.stream()
    .map(first -> {
       Attribute newAttribute = elements.stream().filter(second -> !first.equals(second))
                .map(second -> computeChange(first, second))
                .reduce(first.getAttribute(), (a, b) -> a.add(b))
                return new Element(first.getEntry(), newAttribute, first.getValue());
            }).collect(Collectors.toList()));
pgerstoft
  • 489
  • 6
  • 15
  • 4
    I don't think this `.foreach(newAttribute::add);` will work, as `add` seems to create a new attribute. Maybe `reduce` will work, though. – tobias_k Apr 28 '15 at 20:38
  • @tobias_k the foreach reports the error: The method foreach(Attribute::add) is undefined for the type Stream. Using reduce eliminates the error. – Todd Apr 28 '15 at 23:40
  • pgerstoft, @tobias_k I needed to change the argument of the foreach/reduce to be Attribute:add otherwise I received the error "The type Attribute does not define add(Attribute, Attribute) that is applicable here". However, I am not passing unit tests, so I don't think that the repeated update/add is working properly. I am going to keep poking at it, but if you have any suggestions, I would love to hear them. – Todd Apr 28 '15 at 23:44
  • so, it appears that using foreach produced misleading errors, but forEach allowed the arguments as provided by pgerstoft. However, it does not produce the desired results. I'm still looking. – Todd Apr 28 '15 at 23:57
  • @pgerstoft it almost worked, there appears to be an off by one error. Thanks for all the help. When I update the code as follow, I get it to work: elements.stream() .map(first -> { Attribute zero = new Attribute(); Attribute newAttribute = elements.stream().filter(second -> first != second).map(second -> computeChange(first, second)).reduce(zero, (a, b) -> a.add(b)); return new Element(first.getEntry(), first.getAttribute().add(newAttribute), first.getValue()); }).collect(Collectors.toList())) – Todd Apr 29 '15 at 01:45