This is a part of my Minesweeper analyzer code that didn't fit into the last question.
This code uses FieldGroup
which extends ArrayList
because I am using some more data than just the elements themselves.
I am mostly wondering: Can any simplifications be made in the split
method? Even though the current code is fast enough, I am wondering if it can be optimized further.
The idea is to split two FieldGroup
s (A
and B
) into three parts:
onlyA
: Elements that exists inA
but not inB
both
: Elements that exists in bothA
andB
onlyB
: Elements that exists inB
but not inA
The reason for why I am using List
instead of Set
is that I want the capability to have multiple equal elements. The order does not matter of the elements. I am aware of Guava's MultiSet
but I'm not a big fan of introducing a dependency in this code.
For testing purposes the FieldGroup
class can be as follows:
class FieldGroup<T> extends ArrayList<T> {
}
public class FieldGroupSplit<T> {
private final FieldGroup<T> onlyA;
private final FieldGroup<T> both;
private final FieldGroup<T> onlyB;
private FieldGroupSplit(FieldGroup<T> onlyA, FieldGroup<T> both, FieldGroup<T> onlyB) {
this.onlyA = onlyA;
this.onlyB = onlyB;
this.both = both;
}
public FieldGroup<T> getBoth() {
return both;
}
public FieldGroup<T> getOnlyA() {
return onlyA;
}
public FieldGroup<T> getOnlyB() {
return onlyB;
}
public boolean splitPerformed() {
return !onlyA.isEmpty() || !onlyB.isEmpty();
}
@Override
public String toString() {
return "FieldGroupSplit:" + onlyA + " -- " + both + " -- " + onlyB;
}
public static <T> FieldGroupSplit<T> split(FieldGroup<T> a, FieldGroup<T> b) {
if (a == b) {
return null;
}
if (Collections.disjoint(a, b)) {
return null; // Return if the groups have no fields in common
}
FieldGroup<T> both = new FieldGroup<T>(a);
FieldGroup<T> onlyA = new FieldGroup<T>(a);
FieldGroup<T> onlyB = new FieldGroup<T>(b);
both.retainAll(b);
onlyA.removeAll(both);
onlyB.removeAll(both);
// Check if ALL fields are in common
if (onlyA.isEmpty() && onlyB.isEmpty()) {
// If this is called in a loop an infinite loop can occur if we don't do this because we're creating a NEW object all the time to hold them both.
// We should reuse one of the existing ones and go back to using == above.
both = a;
}
return new FieldGroupSplit<T>(onlyA, both, onlyB);
}
}
I am using return null
in the split
method because it is a fairly common result and I feel that creating an object to represent that response is not necessary.
Here's a test for the method:
public class FieldGroupSplitTest {
private <T> FieldGroupSplit<T> split(FieldGroup<T> a, FieldGroup<T> b) {
return FieldGroupSplit.split(a, b);
}
@Test
public void allFieldsCommon() {
FieldGroup<String> a = new FieldGroup<String>(Arrays.asList("a", "b", "c"));
FieldGroup<String> b = new FieldGroup<String>(Arrays.asList("a", "b", "c"));
FieldGroupSplit<String> split = split(a, b);
assertTrue(split.getOnlyA().isEmpty());
assertFalse(split.getBoth().isEmpty());
assertSame(a, split.getBoth());
assertTrue(split.getOnlyB().isEmpty());
assertFalse(split.splitPerformed());
}
@Test
public void split() {
FieldGroup<String> a = new FieldGroup<String>(Arrays.asList("a", "b", "c"));
FieldGroup<String> b = new FieldGroup<String>(Arrays.asList("b", "c", "d"));
FieldGroupSplit<String> split = split(a, b);
assertEquals(new FieldGroup<String>(Arrays.asList("a")), split.getOnlyA());
assertEquals(new FieldGroup<String>(Arrays.asList("b", "c")), split.getBoth());
assertEquals(new FieldGroup<String>(Arrays.asList("d")), split.getOnlyB());
assertTrue(split.splitPerformed());
}
@Test
public void disjointGroups() {
FieldGroup<String> a = new FieldGroup<String>(Arrays.asList("a", "b", "c"));
FieldGroup<String> b = new FieldGroup<String>(Arrays.asList("d", "e", "f"));
FieldGroupSplit<String> split = split(a, b);
assertNull(split);
}
}
The code is using Java 1.6