Reputation: 65
I need to check whether at least 1 item in a list has X, Y, and Z (not all at the same time). e.g. item 1 has x, and item 2 has y and z.
I thought it'd be better to do this without creating multiple loops and just checking for one of them, but instead store a variable and then check it so it can't be set to false again once true.
Seems like I'm probably missing a better way to do this, so is there one?
Thanks
boolean hasX = false;
boolean hasY = false;
boolean hasZ = false;
for (ItemType item : Items) {
if (!hasX) { hasX = DoesHaveX(item); }
if (!hasY) { hasY = DoesHaveY(item); }
if (!hasZ) { hasZ = DoesHaveZ(item); }
}
Upvotes: 0
Views: 161
Reputation: 3618
Just to add a BitSet
variant too, and under the assumption that checking has...
is a semi-expensive operation:
private static final int xBit = 0;
private static final int yBit = 1;
private static final int zBit = 2;
public static boolean hasAll(final Collection<ItemType> items) {
if (items.isEmpty()) return false;
final BitSet bits = new BitSet(3);
for (final ItemType item : items) {
// Check if bit is already set to avoid
// needless `has*` evaluation
if (!bits.get(xBit) && hasX(item)) bits.set(xBit);
if (!bits.get(yBit) && hasY(item)) bits.set(yBit);
if (!bits.get(zBit) && hasZ(item)) bits.set(zBit);
// You could repeat this INSIDE all of the 'if's
// above to potentially avoid computing bits.get
// but I'd sacrifice that for the slightly improved
// readability.
if (bits.cardinality() == 3) return true;
}
return false;
}
I can't tell you if this is faster or anything, as that depends on your has*
implementations, amongst other things. But it avoids most recomputations whereever possible.
Upvotes: 0
Reputation: 9756
A Stream map/reduce version of the loop for fun. Not sure if it is better to be honest. But at least we get rid of all the variables
boolean allGood = items.stream()
.map(i -> Arrays.asList(doesHaveX(i), doesHaveY(i), doesHaveZ(i)))
.reduce(Arrays.asList(false, false, false),
(acc, elem) -> Arrays.asList(acc.get(0) || elem.get(0),
acc.get(1) || elem.get(1),
acc.get(2) || elem.get(2)))
.stream()
.allMatch(Boolean::booleanValue);
Upvotes: 0
Reputation: 1818
Here is an extendable approach that uses an enum so you never have to touch the logic of hasOneOfAll
again. You just have to extend the given enum.
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
class StackOverflowQuestion56902308Scratch {
class ItemType {
boolean x;
boolean y;
boolean z;
}
enum ItemTypeCheck implements Predicate<ItemType> {
HASX() {
@Override
public boolean test(ItemType itemType) {
//TODO: implement me
return itemType.x;
}
},
HASY() {
@Override
public boolean test(ItemType itemType) {
//TODO: implement me
return itemType.y;
}
},
HASZ() {
@Override
public boolean test(ItemType itemType) {
//TODO: implement me
return itemType.z;
}
}
}
public static boolean hasOneOfAll(List<ItemType> itemTypes) {
Map<ItemTypeCheck, Boolean> result = new EnumMap<>(ItemTypeCheck.class);
for (ItemType itemType : itemTypes) {
for (ItemTypeCheck check : ItemTypeCheck.values()) {
result.merge(check, check.test(itemType), Boolean::logicalOr);
}
}
return result.values().stream().allMatch(hadOne -> hadOne);
}
}
Personally I am not sure if this is too overengineered but it alleviates the pain of manually adjusting the function if another check is added in the future.
Upvotes: 0
Reputation: 1109
If you are going to stick to a JVM below 1.8 then your code is just fine!
Maybe you could skip few operations like breaking the loop once you found a match for the three booleans, and checking only those which are not found any yet.
for (ItemType item : items) {
hasX = hasX || doesHaveX(item);
hasY = hasY || doesHaveY(item);
hasZ = hasZ || doesHaveZ(item);
if (hasX && hasY && hasZ) {
break;
}
}
If you are just fine to use streams maybe it's better to initialize each of the variables at it's creation like so:
boolean hasX = items.stream().anyMatch(this::doesHaveX); // goes trough the elements until a match is found.
boolean hasY = items.stream().anyMatch(this::doesHaveY); // goes trough the elements until a match is found.
boolean hasZ = items.stream().anyMatch(this::doesHaveZ); // goes trough the elements until a match is found.
Upvotes: 1