ChatGPT解决这个技术问题 Extra ChatGPT

Checking the equality of two slices

How can I check if two slices are equal, given that the operators == and != are not an option?

package main

import "fmt"

func main() {
    s1 := []int{1, 2}
    s2 := []int{1, 2}
    fmt.Println(s1 == s2)
}

This does not compile with:

invalid operation: s1 == s2 (slice can only be compared to nil)


S
Samuel Liew

You should use reflect.DeepEqual()

DeepEqual is a recursive relaxation of Go's == operator. DeepEqual reports whether x and y are “deeply equal,” defined as follows. Two values of identical type are deeply equal if one of the following cases applies. Values of distinct types are never deeply equal. Array values are deeply equal when their corresponding elements are deeply equal. Struct values are deeply equal if their corresponding fields, both exported and unexported, are deeply equal. Func values are deeply equal if both are nil; otherwise they are not deeply equal. Interface values are deeply equal if they hold deeply equal concrete values. Map values are deeply equal if they are the same map object or if they have the same length and their corresponding keys (matched using Go equality) map to deeply equal values. Pointer values are deeply equal if they are equal using Go's == operator or if they point to deeply equal values. Slice values are deeply equal when all of the following are true: they are both nil or both non-nil, they have the same length, and either they point to the same initial entry of the same underlying array (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) are not deeply equal. Other values - numbers, bools, strings, and channels - are deeply equal if they are equal using Go's == operator.


A very useful answer. Regardless of the general reflect package performance, it is very nice to have a prepackaged deep equality function for use in test cases where simplicity and correctness are paramount.
I just ran a benchmark and reflect.DeepEqual is 150 times slower than a loop. Just FYI if anyone wants to use this method in production.
It does not compare randomly orderded slices with same items :(
@Hemant_Negi two slices aren't equal if they have a different order. If you want to compare equality of two slices while ignoring order then sort them and then check, or move the items from one slice into a map, and then check that each element on the other slice is in the map. ( additionally make sure they have same length )
Rob Pike (in 2011) on reflection in Go, writing in the official Go blog: "It's a powerful tool that should be used with care and avoided unless strictly necessary" blog.golang.org/laws-of-reflection . I would not use reflection in production code just to compare slices. That's an easy function to write. But note that there's a potential flaw in the chosen answer to this question too, depending on what behavior you expect from it: it will find that slices that have been initialized but are still at len 0 and cap 0 do not match slices that have been declared but not initialized.
I
Inanc Gumus

You need to loop over each of the elements in the slice and test. Equality for slices is not defined. However, there is a bytes.Equal function if you are comparing values of type []byte.

func testEq(a, b []Type) bool {
    if len(a) != len(b) {
        return false
    }
    for i := range a {
        if a[i] != b[i] {
            return false
        }
    }
    return true
}

Suggestion: for i, v := range a { if v != b[i] { return false } }.
@zzzz Careful, this will fail on different lengths.
This does not work if the element type does not support ==. Also, IIUC, Go does not have anything like generics. This means that you must copy n' paste this function for each element type that you want to support. This is obviously something that should ship with the language. In fact, it does (albeit with the magic of reflect), and Victor provides the answer. The fact that this is chosen above that answer, and more highly voted is simply maddening...
Go as a language tends to recommend not using reflection unless absolutely necessary. Yes, it would need to be done for each type but it is generally not something you do often anyways. Also, reflect.DeepEqual may do something you don't expect such as saying two different pointers are equal because the values they point to are equal.
@FiloSottile Length is checked beforehand, the loop is only reached if lengths differ.
R
R‌‌‌..

This is just example using reflect.DeepEqual() that is given in @VictorDeryagin's answer.

package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := []int {4,5,6}
    b := []int {4,5,6}
    c := []int {4,5,6,7}

    fmt.Println(reflect.DeepEqual(a, b))
    fmt.Println(reflect.DeepEqual(a, c))

}

Result:

true
false

Try it in Go Playground


In terms of performance, how does this compare with the accepted answer?
K
KeksArmee

If you have two []byte, compare them using bytes.Equal. The Golang documentation says:

Equal returns a boolean reporting whether a and b are the same length and contain the same bytes. A nil argument is equivalent to an empty slice.

Usage:

package main

import (
    "fmt"
    "bytes"
)

func main() {
    a := []byte {1,2,3}
    b := []byte {1,2,3}
    c := []byte {1,2,2}

    fmt.Println(bytes.Equal(a, b))
    fmt.Println(bytes.Equal(a, c))
}

This will print

true
false

why is this not top
@lurfjurv since this only answers the very specific case of []byte and not any arbitrary slice
thog don't caare
l
lk_vc

And for now, here is https://github.com/google/go-cmp which

is intended to be a more powerful and safer alternative to reflect.DeepEqual for comparing whether two values are semantically equal.

package main

import (
    "fmt"

    "github.com/google/go-cmp/cmp"
)

func main() {
    a := []byte{1, 2, 3}
    b := []byte{1, 2, 3}

    fmt.Println(cmp.Equal(a, b)) // true
}

G
Gabriel Furstenheim

In case that you are interested in writing a test, then github.com/stretchr/testify/assert is your friend.

Import the library at the very beginning of the file:

import (
    "github.com/stretchr/testify/assert"
)

Then inside the test you do:


func TestEquality_SomeSlice (t * testing.T) {
    a := []int{1, 2}
    b := []int{2, 1}
    assert.Equal(t, a, b)
}

The error prompted will be:

                Diff:
                --- Expected
                +++ Actual
                @@ -1,4 +1,4 @@
                 ([]int) (len=2) {
                + (int) 1,
                  (int) 2,
                - (int) 2,
                  (int) 1,
Test:           TestEquality_SomeSlice

assert.Equal internally uses reflect.DeepEqual which might make your tests to run slower and eventually your pipeline.
@DeepakSah Do you have benchmarks for the performance difference? In my experience performance bottleneck in the tests is not in the assert equal, and you get great quality messages which has a boost in productivity
You could use assert.ElementsMatch(t, a, b) to ignore the elements order.
b
blackgreen

You cannot use == or != with slices but if you can use them with the elements then Go 1.18 has a new function to easily compare two slices, slices.Equal:

Equal reports whether two slices are equal: the same length and all elements equal. If the lengths are different, Equal returns false. Otherwise, the elements are compared in increasing index order, and the comparison stops at the first unequal pair. Floating point NaNs are not considered equal.

The slices package import path is golang.org/x/exp/slices. Code inside exp package is experimental, not yet stable. It will be moved into the standard library in Go 1.19 eventually.

Nevertheless you can use it as soon as Go 1.18 (playground)

    sliceA := []int{1, 2}
    sliceB := []int{1, 2}
    equal := slices.Equal(sliceA, sliceB)
    fmt.Println(equal) // true

    type data struct {
        num   float64
        label string
    }

    sliceC := []data{{10.99, "toy"}, {500.49, "phone"}}
    sliceD := []data{{10.99, "toy"}, {200.0, "phone"}}
    equal = slices.Equal(sliceC, sliceD)
    fmt.Println(equal) // true

If the elements of the slice don't allow == and !=, you can use slices.EqualFunc and define whatever comparator function makes sense for the element type.


W
Wug

Thought of a neat trick and figured I'd share.

If what you are interested in knowing is whether two slices are identical (i.e. they alias the same region of data) instead of merely equal (the value at each index of one slice equals the value in the same index of the other) then you can efficiently compare them in the following way:

foo := []int{1,3,5,7,9,11,13,15,17,19}

// these two slices are exactly identical
subslice1 := foo[3:][:4]
subslice2 := foo[:7][3:]

slicesEqual := &subslice1[0]  == &subslice2[0]   && 
               len(subslice1) == len(subslice2)

There are some caveats to this sort of comparison, in particular that you cannot compare empty slices in this way, and that the capacity of the slices isn't compared, so this "identicality" property is only really useful when reading from a slice or reslicing a strictly narrower subslice, as any attempt to grow the slice will be affected by the slices' capacity. Still, it's very useful to be able to efficiently declare, "these two huge blocks of memory are in fact the same block, yes or no."


you have trailing parenthesis breaking the code syntax
someone might want to run fmt.Printf("%p %p\n", &subslice1[0], &subslice2[0]) to see that both shares the same memory address. and that it works fine as long as he compares the same index against both slices. ie fmt.Printf("%p %p\n", &subslice1[1], &subslice2[1]) etc
They will. Reslicing doesn't reallocate, it aliases the storage in use by the original slice, and slices are also always contiguous so there's no way to end up with a slice where this will be true with some indices but not others.