Find the remaining Enum

145 views Asked by At

I have the Enum A, B, C. In a method, I am given two different Enum and must return the remaining Enum.

Example: I receive A and C I must return B

The solution that I have is to use if elseif else:

private EnumABC findRemaining(EnumABC pEnum1, EnumABC pEnum2){
    if((pEnum1 == EnumABC.A || pEnum2 == EnumABC.A) 
        && (pEnum1 == EnumABC.B || pEnum2 == EnumABC.B)){
        return EnumABC.C; 
    } else
    if((pEnum1 == EnumABC.A || pEnum2 == EnumABC.A)
        && (pEnum1 == EnumABC.C || pEnum2 == EnumABC.C)){
        return EnumABC.B;
    } else{
       return EnumABC.A;
    }
}

I was wondering if there was a more readable solution then this.

5

There are 5 answers

2
Basil Bourque On BEST ANSWER

tl;dr

enum Animal { DOG , CAT , BIRD }
EnumSet<Animal> flyingAnimals = EnumSet.complementOf ( EnumSet.of( DOG , CAT ) )

EnumSet.complementOf

You can make a Set of enum objects. Indeed, you can make a highly performant EnumSet.

enum Animal { DOG , CAT , BIRD }
EnumSet<Animal> furry = EnumSet.of( DOG , CAT ) ;

Ask for those enum objects not found in an EnumSet by calling the static method EnumSet.complementOf.

EnumSet<Animal> nonFurry = EnumSet.complementOf ( furry ) ;

Tip: You can define an enum locally in modern Java. Ditto for interface and record.

0
f1sh On

While Basil's answer is the most elegant one, this one iterates and is rather readable:

enum EnumABC {
  A,B,C;

  EnumABC findRemaining(EnumABC other) {
    return Arrays.stream(values())
                 .filter(v -> v!=this&&v!=other)
                 .findFirst()
                 .get();
  }
}

This is now a method of the enum, so instead of

EnumABC remaining = findRemaining(pEnum1, pEnum2);

you have to call it using

EnumABC remaining = pEnum1.findRemaining(pEnum2);
1
cup On

If you are using integers

int a = 1, b = 2, c = 3;
int findMissing(int e1, int e2)
{
    return a + b + c - e1 - e2;
}

There are lots of "don't do it that way" discussions about using ordinals and value but if you only have 3 values, it could be clumsily coded as

enum Letter {A, B, C};
Letter findMissing(Letter e1, Letter e2)
{
    int missing = A.ordinal() + B.ordinal() + C.ordinal() - e1.ordinal() - e2.ordinal();
    return Letter.values()[missing];
}
3
Mushroomator On

More general solution for enums with arbitrary amount of values using maths, varargs and reflection

Enums all have ordinal values starting from 0 to n. So using that knowledge we can sum up all the ordinal values to a total and substract the ordinal values of the provided enums. If we mix in varargs and some reflextion that works for any enum with an arbitrary amount of values.

Better yet, you can compute the sum without iterating through all the values if you want. Since the the sum of the first n natural numbers is given by (n * (n + 1)) / 2.

Therefore the following would work. You could even use varargs to

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

enum MyEnumWith3Values { A, B, C }

enum MyEnumWith4Values { A, B, C, D }

public class Main {

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        // For enum with 3 values
        System.out.println(RemainingEnumValue(MyEnumWith3Values.A, MyEnumWith3Values.B));
        System.out.println(RemainingEnumValue(MyEnumWith3Values.A, MyEnumWith3Values.C));
        System.out.println(RemainingEnumValue(MyEnumWith3Values.C, MyEnumWith3Values.B));

        // For enum with 4 values
        System.out.println(RemainingEnumValue(MyEnumWith4Values.A, MyEnumWith4Values.B, MyEnumWith4Values.C));
    }

    public static <T extends Enum<T>> Enum<T> RemainingEnumValue(T... enumValues) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if(enumValues.length < 1)
            throw new IllegalArgumentException("At least one enum  value!");
        var enumClazz = enumValues[0].getClass();
        var valuesMethod = enumClazz.getMethod("values");
        var enumAllValues = (Enum<T>[])valuesMethod.invoke(enumClazz);
        if(enumAllValues.length - 1 != enumValues.length)
            throw new IllegalArgumentException("You must provide exactly one less then all the available enum values");
        var maxEnumValue = enumAllValues.length - 1;
        var enumOrdinalTotal = (maxEnumValue * maxEnumValue + maxEnumValue) >> 1;
        var totalProvidedEnums = Arrays.stream(enumValues).map(Enum::ordinal).reduce(0, Integer::sum);
        return enumAllValues[enumOrdinalTotal - totalProvidedEnums];
    }
}

Remark on this solution

I've added this solution since I think it's an interesting and different approach which uses a mathematical idea (which I always find interesting).

However, due to reflection and MyEnum.values() calls being involved it's certainly not the most performant solution. It's also not the most straightforward, robust or maintainable version of all the answers here. Lastly, you shouldn't really use ordinal() (See this SO thread as it can cause some problems.

Without reflection and with cached MyEnum.values()

If don't need it to be generic (=> eliminates reflection) and cache the MyEnum.values() call, you could however also make this quite a bit more performant. You'd still be using ordinal() though.

enum MyEnumWith3Values { A, B, C }

public class Main {
    private static MyEnumWith3Values[] cachedEnumValues = MyEnumWith3Values.values();

    public static void main(String[] args){
        // For enum with 3 values
        System.out.println(RemainingEnumValue(MyEnumWith3Values.A, MyEnumWith3Values.B));
        System.out.println(RemainingEnumValue(MyEnumWith3Values.A, MyEnumWith3Values.C));
        System.out.println(RemainingEnumValue(MyEnumWith3Values.C, MyEnumWith3Values.B));
    }

    public static MyEnumWith3Values RemainingEnumValue(MyEnumWith3Values a, MyEnumWith3Values b){
        var maxEnumValue = cachedEnumValues.length - 1;
        var enumOrdinalTotal = (maxEnumValue * maxEnumValue + maxEnumValue) >> 1;
        var totalProvidedEnums = a.ordinal() + b.ordinal();
        return cachedEnumValues[enumOrdinalTotal - totalProvidedEnums];
    }
}
0
Andy Turner On

Just to throw another idea into the mix, you can exploit the comparability of enums:

EnumABC[] args = {pEnum1, pEnum2};
Arrays.sort(args, Comparator.naturalOrder());  // Sort by ordinal.

EnumABC[] vals = EnumABC.values();  // Already sorted by ordinal.

int i = 0;
while (i < args.length && args[i] == vals[i]) {
  ++i;
}

return vals[i];

This looks for the first element of EnumABC.values() which doesn't equal an element in args - because both arrays are sorted, you can just do a linear probe to find that.