membersound
membersound

Reputation: 86747

How to extract only one allowed element from a stream?

I have a list of elements, and want to extract the value of the fields' propery. Problem: all elements should have the same property value.

Can I do better or more elegant than the following?

Set<String> matches = fields.stream().map(f -> f.getField()).collect(Collectors.toSet());
if (matches.size() != 1) throw new IllegalArgumentException("could not match one exact element");
String distrinctVal = matches.iterator().next(); //continue to use the value

Is this possible directly using the stream methods, eg using reduce?

Upvotes: 8

Views: 1176

Answers (6)

Anonymous
Anonymous

Reputation: 86276

As others have said, it’s largely a matter of taste. Here’s mine.

    String distinctVal = fields.iterator().next().getField();
    if (fields.stream().map(Field::getField).anyMatch(e -> ! e.equals(distinctVal)) {
        throw new IllegalArgumentException("could not match one exact element");
    }
    //continue to use the value

(Code is not tested; forgive typos.)

I didn’t particularly code with performance efficiency in mind, but the code will only search until the first non-matching string, so should be efficient.

Upvotes: 0

lczapski
lczapski

Reputation: 4120

Similar to this:

String distrinctVal = fields.stream()
    .map(f -> f.getField())
    .reduce((a, b) 
        -> { throw new IllegalArgumentException("could not match one exact element");}
    ).get();

Upvotes: 0

Eklavya
Eklavya

Reputation: 18430

Your current solution is good. You can try this way also to avoid collecting.

Use distinct() then count()

if (fields.stream().map(f -> f.getField()).distinct().count() != 1) 
      throw new IllegalArgumentException("could not match one exact element");

To get the value

String distrinctVal = fields.get(0).getField();

Upvotes: 5

Ousmane D.
Ousmane D.

Reputation: 56433

Well you could certainly do this in several ways but as to which is more elegant can vary from person to person.

Anyway if you were to attempt this via streams this is how i would have done it:

With a slight modification to my answer here you could do:

boolean result = fields.stream()
                       .map(f -> f.getField())
                       .distinct()
                       .limit(2) // ENABLE SHORT CIRCUITING
                       .count() != 1;

if (result) throw new IllegalArgumentException("could not match one exact element");

String distinctVal = fields.get(0).getField();

The benefit of this approach is basically utilising limit(2) to enable optimisation where possible.

Conclusion : your current approach is quite good actually so I wouldn't be surprised if you were to stick to that but you also have the choice of this approach where you can short-circuit the pipeline.

Upvotes: 4

Sven D&#246;ring
Sven D&#246;ring

Reputation: 4368

That would be the reduce solution.

Optional<String> distinctVal = fields.stream()
    .map(f -> f.getField())
    .reduce((a, b) -> {
        if(a != b) throw new IllegalArgumentException("could not match one exact element");
        return a;
    });

Upvotes: 3

TreffnonX
TreffnonX

Reputation: 2930

Depending on the frequency of invocation and the size of your set, the iterative code can be significantly faster.

public boolean allEqual(Collection<Fields> fields) {
    if (fields.size() > 1) {
        String last;
        boolean first = true;
        for (Field field : fields) {
            String thisString = field.getField();
            if (first) {
                last = thisString;
            } else {
                if (!StringUtils.equals(last, thisString)) {
                    return false;
                }
            }
        }
    }
    return true;
}

While this is not a streaming solution, it aborts when the first mismatch is found, which - depending on the input - can be significantly faster.

Upvotes: 0

Related Questions