How do I check the equality of three values elegantly?

timthelion picture timthelion · Jun 17, 2016 · Viewed 8.6k times · Source

Say I have values a, b and c. I want to find out if they are equal. If I do

if a == b == c{...}

Then I get a compile error

invalid operation: a == b == c (mismatched types bool and TypeOfABandC)

This is pretty obvious, because this parses to:

(a == b) == c

And (a == b) is a bool.

Of course I can do:

if a == b && a == c {...}

However, this isn't very nice looking and feels confusing. Is there another way?

Answer

icza picture icza · Jun 17, 2016

A note beforehand:

Your last proposed solution is the shortest, clearest and most efficient way to compare if 3 values are equal:

if a == b && a == c {
    fmt.Println("Clearest: all 3 are equal")
}

or alternatively (to your liking):

if a == b && b == c {
    fmt.Println("Clearest: all 3 are equal")
}

The rest of this answer (what follows) is just toying with the language specification and the language's capabilities, presenting what I find fun and creative. They do not attempt to provide a superior solution.


Try all the examples below on the Go Playground. The examples build on the terms and the result of the comparisons which are defined in Spec: Comparison operators.

General note: in the examples below I used the type interface{} which will work whatever type your values have (a, b and c), but if you know they are of type int for example, you can use that specific type too (which would improve efficiency and shorten the length of the examples).

With a map as a set

if len(map[interface{}]int{a: 0, b: 0, c: 0}) == 1 {
    fmt.Println("Map set: all 3 are equal")
}

Effectively we put all comparable values into a map as keys, and if all are equal, there will be only 1 pair in the map, so the "length" of the map will be 1. The value type of the map doesn't play any role here, it could be anything. I used int because this results in the shortest composite literal (that defines the map value).

This solution is flexible as you can use any number of values to test if all are equal, not just 3.

With arrays

Arrays are comparable (unlike slices):

if [2]interface{}{a, b} == [2]interface{}{b, c} {
    fmt.Println("Arrays: all 3 are equal")
}

The result of the array comparison will be true if a == b and b == c (if the corresponding elements are equal).

Note that you can apply this method on any number of values as well. It would look like this for 5 values:

if [4]interface{}{a, b, c, d} == [4]interface{}{b, c, d, e} {
    fmt.Println("Arrays: all 5 are equal")
}

With a tricky map

if map[interface{}]bool{a: b == c}[b] {
    fmt.Println("Tricky map: all 3 are equal")
}

This composite literal will assign the comparision result of b == c to the key a. And we ask the value associated with the key b. If a == b, the result of the indexing expression will be the result of b == c, that is, whether all 3 values are equal. If a != b, then the zero value of the value type will be the result, which is false in case of bool, properly telling that all 3 values are not equal.

With anonymous structs

struct values are also comparable, so:

if struct{ a, b interface{} }{a, b} == struct{ a, b interface{} }{b, c} {
    fmt.Println("Anon structs: all 3 are equal")
}

With (named) structs

If we define a simple struct beforehand:

type P struct{ a, b interface{} }

Comparison will be much more compact:

if (P{a, b} == P{b, c}) {
    fmt.Println("Structs: all 3 are equal")
}

(Note that the expression of the if statement must be put in parenthesis to avoid parsing ambiguity - else it's a compile time error!)

With slices

Slices are not comparable, so we will need to borrow some help from reflect.DeepEqual():

if reflect.DeepEqual([]interface{}{a, b}, []interface{}{b, c}) {
    fmt.Println("Slices: all 3 are equal")
}

With a helper function

func AllEquals(v ...interface{}) bool {
    if len(v) > 1 {
        a := v[0]
        for _, s := range v {
            if a != s {
                return false
            }
        }
    }
    return true
}

And using it:

if AllEquals(a, b, c) {
    fmt.Println("Helper function: all 3 are equal")
}

This solution is also flexible, you can call AllEquals() with any number of values.

Note that we can make the AllEquals() function more compact if we're also willing to call reflect.DeepEqual() here:

func AllEquals2(v ...interface{}) bool {
    if len(v) < 2 {
        return true
    }
    return reflect.DeepEqual(v[:len(v)-1], v[1:])
}