How to compare if two structs, slices or maps are equal? - go

I want to check if two structs, slices and maps are equal.
But I'm running into problems with the following code. See my comments at the relevant lines.
package main
import (
"fmt"
"reflect"
)
type T struct {
X int
Y string
Z []int
M map[string]int
}
func main() {
t1 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
t2 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
fmt.Println(t2 == t1)
//error - invalid operation: t2 == t1 (struct containing []int cannot be compared)
fmt.Println(reflect.ValueOf(t2) == reflect.ValueOf(t1))
//false
fmt.Println(reflect.TypeOf(t2) == reflect.TypeOf(t1))
//true
//Update: slice or map
a1 := []int{1, 2, 3, 4}
a2 := []int{1, 2, 3, 4}
fmt.Println(a1 == a2)
//invalid operation: a1 == a2 (slice can only be compared to nil)
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(m1 == m2)
// m1 == m2 (map can only be compared to nil)
}
http://play.golang.org/p/AZIzW2WunI

You can use reflect.DeepEqual, or you can implement your own function (which performance wise would be better than using reflection):
http://play.golang.org/p/CPdfsYGNy_
m1 := map[string]int{
"a":1,
"b":2,
}
m2 := map[string]int{
"a":1,
"b":2,
}
fmt.Println(reflect.DeepEqual(m1, m2))

reflect.DeepEqual is often incorrectly used to compare two like structs, as in your question.
cmp.Equal is a better tool for comparing structs.
To see why reflection is ill-advised, let's look at the documentation:
Struct values are deeply equal if their corresponding fields, both exported and unexported, are deeply equal.
....
numbers, bools, strings, and channels - are deeply equal if they are equal using Go's == operator.
If we compare two time.Time values of the same UTC time, t1 == t2 will be false if their metadata timezone is different.
go-cmp looks for the Equal() method and uses that to correctly compare times.
Example:
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(cmp.Equal(m1, m2)) // will result in true
Important Note:
Be careful when using cmp.Equal as it may lead to a panic condition
It is intended to only be used in tests, as performance is not a goal
and it may panic if it cannot compare the values. Its propensity
towards panicking means that its unsuitable for production
environments where a spurious panic may be fatal.

Here's how you'd roll your own function http://play.golang.org/p/Qgw7XuLNhb
func compare(a, b *T) bool {
if a == b {
return true
}
if a.X != b.X || a.Y != b.Y {
return false
}
if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) {
return false
}
for i, v := range a.Z {
if b.Z[i] != v {
return false
}
}
for k, v := range a.M {
if b.M[k] != v {
return false
}
}
return true
}
Update: Go 1.18
import (
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
func compare(a, b *T) bool {
if a == b {
return true
}
if a.X != b.X {
return false
}
if a.Y != b.Y {
return false
}
if !slices.Equal(a.Z, b.Z) {
return false
}
return maps.Equal(a.M, b.M)
}

If you intend to use it in tests, since July 2017 you can use cmp.Equal with cmpopts.IgnoreFields option.
func TestPerson(t *testing.T) {
type person struct {
ID int
Name string
}
p1 := person{ID: 1, Name: "john doe"}
p2 := person{ID: 2, Name: "john doe"}
println(cmp.Equal(p1, p2))
println(cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID")))
// Prints:
// false
// true
}

If you're comparing them in unit test, a handy alternative is EqualValues function in testify.

If you want to compare simple one-level structs, the best and simple method is the if statement.
Like this if s1 == s2
Here is a simple example:
type User struct {
name string
email string
}
func main() {
u1 := User{
name: "Iron Man",
email: "ironman#avengers.com",
}
u2 := User{
name: "Iron Man",
email: "ironman#avengers.com",
}
// Comparing 2 structs
if u1 == u2 {
fmt.Println("u1 is equal to u2")
} else {
fmt.Println("u1 is not equal to u2")
}
}
Result: u1 is equal to u2
You can play with this here.

New way to compare maps
This proposal (https://github.com/golang/go/issues/47649) that is part of the future implementation of Go generics introduces a new function to compare two maps, maps.Equal:
// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 constraints.Map[K, V], K, V comparable](m1 M1, m2 M2) bool
Example use
strMapX := map[string]int{
"one": 1,
"two": 2,
}
strMapY := map[string]int{
"one": 1,
"two": 2,
}
equal := maps.Equal(strMapX, strMapY)
// equal is true
maps package is found in golang.org/x/exp/maps. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19
You can see it working in gotip playground
https://gotipplay.golang.org/p/M0T6bCm1_3m

Related

Traverse a Map in decreasing order of values

I'm trying to traverse a map in decreasing order of the values stored against keys. I've tried:
func frequencySort(s string) string {
var frequency map[string]int
chararray := strings.Split(s , "")
var a []int
var arranged map[int]string
for k , v := range frequency {
arranged[v] = k
}
for k := range arranged {
a = append(a , k)
}
sort.Sort(sort.Reverse(sort.IntSlice{a}))
}
Let's say the Map structure is :
"a" : 9
"b" : 7
"c" : 19
"d" : 11
and I'm trying to traverse it such that the output is :
"c" : 19
"d" : 11
"a" : 9
"b" : 7
The two map approach you have in your example will break as soon as you have more than one key in frequency with the same value, say "a":7 and "b":7, then you would lose data in arranged since keys have to be unique.
To avoid this you could create a helper type that will hold the map's contents temporarily, just for sorting purposes. Something like this:
package main
import (
"fmt"
"sort"
)
var m = map[string]int{
"a": 9,
"b": 7,
"c": 19,
"d": 11,
}
type entry struct {
val int
key string
}
type entries []entry
func (s entries) Len() int { return len(s) }
func (s entries) Less(i, j int) bool { return s[i].val < s[j].val }
func (s entries) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
var es entries
for k, v := range m {
es = append(es, entry{val: v, key: k})
}
sort.Sort(sort.Reverse(es))
for _, e := range es {
fmt.Printf("%q : %d\n", e.key, e.val)
}
}
https://play.golang.org/p/TPb0zNCtXO
For example,
package main
import (
"fmt"
"sort"
)
type frequncy struct {
c string
f int
}
func frequencies(s string) []frequncy {
m := make(map[string]int)
for _, r := range s {
m[string(r)]++
}
a := make([]frequncy, 0, len(m))
for c, f := range m {
a = append(a, frequncy{c: c, f: f})
}
sort.Slice(a, func(i, j int) bool { return a[i].f > a[j].f })
return a
}
func main() {
s := "aaaaabcbcbcbzxyyxzzsoaz"
fmt.Println(s)
f := frequencies(s)
fmt.Println(f)
}
Playground: https://play.golang.org/p/d9i3yL1x4K
Output:
aaaaabcbcbcbzxyyxzzsoaz
[{a 6} {b 4} {z 4} {c 3} {x 2} {y 2} {s 1} {o 1}]

How to use join multiple structs into the same one

I'm trying to play around with recursive structs, where when I have multiple I can add them together, creating a new struct with those embedded. However, I'm not sure what the proper way to approach this is.
I've included a code snippet below to further illustrate what I mean.
package main
import "fmt"
type Container struct {
F int
Collection []SubContainer
}
type SubContainer struct {
Key string
Value int
}
func main() {
commits := map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
}
sc := []SubContainer{}
c := []Container{}
count := 0
for k, v := range commits {
sc = append(sc, SubContainer{Key: k, Value: v})
count++
if len(sc) == 2 {
c = append(c, Container{Collection: sc, F: count})
sc = nil
}
}
for _, r := range c {
fmt.Println(r)
}
}
Result:
{2 [{a 1} {b 2}]}
{4 [{c 3} {d 4}]}
Desired result:
{6 {2 [{a 1} {b 2}]} {4 [{c 3} {d 4}]}}
Playground link: https://play.golang.org/p/j6rbhgcOoT
One caveat I'm having trouble wrapping my head around is that the commits length may change (I was initially thinking I could just create a different parent struct). Any suggestions would be appreciated... Is doing this somehow with recursive structs the right approach to accomplish this? Thanks!
I attempted to get close to the desired output without being sure about the exact goal, you will find below a modified version of the snippet you provided.
You can use the String() string method on a collection to customize the format.
package main
import "fmt"
type ContainerCollection struct {
Count int
List []Container
}
func (cc ContainerCollection) String() string {
total := 0
for _, c := range cc.List {
total += c.F
}
return fmt.Sprintf("{%d %v}", total, cc.List)
}
type Container struct {
F int
Collection []SubContainer
}
type SubContainer struct {
Key string
Value int
}
func main() {
commits := map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
}
c := ContainerCollection{Count: 0}
sc := []SubContainer{}
for k, v := range commits {
sc = append(sc, SubContainer{Key: k, Value: v})
c.Count++
if len(sc) == 2 {
c.List = append(c.List, Container{Collection: sc, F: c.Count})
sc = []SubContainer{}
}
}
// Should also cover odd number of commits
if len(sc) != 0 {
c.Count++
c.List = append(c.List, Container{Collection: sc, F: c.Count})
}
// for _, r := range c.List { fmt.Println(r) }
fmt.Println(c)
}
Result:
{6 [{2 [{a 1} {b 2}]} {4 [{c 3} {d 4}]}]}
Playground
Here's something with minimal modification to your code (just added a 'super' container, which is basically a summary struct). One probably need this only if this is being passed to another library/package/over the net etc., otherwise just maintaining the totalCount may be enough.
package main
import "fmt"
type SuperContainer struct {
TotalCount int
Containers []Container
}
type Container struct {
F int
Collection []SubContainer
}
type SubContainer struct {
Key string
Value int
}
func main() {
var totalCount int
commits := map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
}
sc := []SubContainer{}
c := []Container{}
count := 0
for k, v := range commits {
sc = append(sc, SubContainer{Key: k, Value: v})
count++
if len(sc) == 2 {
totalCount += count
c = append(c, Container{Collection: sc, F: count})
sc = nil
}
}
for _, r := range c {
fmt.Println(r)
}
supC := SuperContainer{TotalCount: totalCount, Containers: c}
fmt.Println(supC)
}
Playground: https://play.golang.org/p/yN3N3gHaCX

Add two items at a time to struct in for loop

I'm trying to add 2 items at once to a struct array, then continuously every 2 items create a new struct array and append to the final Container struct. I'm struggling to find the proper way of doing this.
To further illustrate what I mean:
package main
import "fmt"
type Container struct {
Collection []SubContainer
}
type SubContainer struct {
Key string
Value int
}
func main() {
commits := map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
}
sc := []SubContainer{}
c := Container{}
for k, v := range commits {
sc = append(sc, SubContainer{Key: k, Value: v})
}
for _, s := range sc {
c.Collection = append(c.Collection, s)
}
fmt.Println(c)
}
Link: https://play.golang.org/p/OhSntFT7Hp
My desired behaviour is to loop through all the commits, and every time the SubContainer reaches len(2), append to the Container, and create a new SubContainer until the for loop is complete. If there's an uneven number of elements, then the last SubContainer would just hold one element and append to Container like normal.
Does anyone have any suggestions on how to do this? Apologies if this is a obvious answer, very new to Go!
You can "reset" a slice by setting it to nil. Also, you may not be aware that nil-slices work just fine with append:
var sc []SubContainer
c := Container{}
for k, v := range commits {
sc = append(sc, SubContainer{Key: k, Value: v})
if len(sc) == 2 {
c.Collection = append(c.Collection, sc...)
sc = nil
}
}
if len(sc) > 0 {
c.Collection = append(c.Collection, sc...)
}
https://play.golang.org/p/ecj52fkwpO

Idiomatic Splice in Go

I checked an existing answer but it's not similar to my case.
I need to pluck an element at the index and break out of the for loop at runtime based on Compare function.
Issues:
If element to pluck is found at 0 index, index-1 will throw slice bounds of range error and similarly if index+1 is greater than len(elements).
Question: What's the best concise way to achieve the above?
for index, element := range elements {
if element.Compare() == true {
elements = append(elements[:index-1], elements[index+1:]...)
break
}
}
Attempt
for index, element := range elements {
if element.Compare() == true {
if len(elements) > 1 {
elements = append(elements[:index-1], elements[index+1:]...)
} else if len(elements) == 1 {
delete(elements, 0)
}
break
}
}
Attempt 2 Playground any improvements/suggestions?
The idea is to copy the remaining elements from beginning to index and then any elements after.
var elements = []string {"a", "b", "c", "d"}
fmt.Println(elements)
for index, element := range elements {
if element == "c" {
var temp = elements[:index]
for i := index + 1; i<len(elements); i++ {
temp = append(temp, elements[i])
}
elements = temp
break
}
}
fmt.Println(elements)
The high index in a slice expression is exclusive.
This means your example is flawed, and also that no special treatment is required.
The correct slicing expression is:
elements = append(elements[:index], elements[index+1:]...)
If index is the first element (0), then elements[:0] will be an empty slice.
If index is the last element (len-1), then elements[index+1:] will also be an empty slice, as index+1 will be equal to the lenght of the slice. So the solution is simply:
for index, element := range elements {
if element.Compare() {
elements = append(elements[:index], elements[index+1:]...)
break
}
}
To demonstrate it on the Go Playground, let's substitute the Compare() method with a simple index check:
for _, idxToRemove := range []int{0, 2, 4} {
s := []int{0, 1, 2, 3, 4}
for i := range s {
if i == idxToRemove {
s = append(s[:i], s[i+1:]...)
break
}
}
fmt.Println(idxToRemove, ":", s)
}
Output (try it on the Go Playground):
0 : [1 2 3 4]
2 : [0 1 3 4]
4 : [0 1 2 3]
If the slice s is sorted and len(s) is large, find x using a binary search. For example,
package main
import (
"fmt"
"sort"
)
func pluck(s []string, x string) []string {
i := sort.SearchStrings(s, x)
if i >= 0 && i < len(s) && s[i] == x {
s = append(s[:i], s[i+1:]...)
}
return s
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = pluck(s, "b")
fmt.Println(s)
}
Output:
[a b c d]
[a c d]
If the order of slice s does not need to be preserved, switch elements. For example,
package main
import "fmt"
func pluck(s []string, x string) []string {
for i, v := range s {
if v == x {
s[i] = s[len(s)-1]
s = s[:len(s)-1]
break
}
}
return s
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = pluck(s, "b")
fmt.Println(s)
}
Output:
[a b c d]
[a d c]
Otherwise, splice slice s elements. For example,
package main
import "fmt"
func pluck(s []string, x string) []string {
for i, v := range s {
if v == x {
s = append(s[:i], s[i+1:]...)
break
}
}
return s
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = pluck(s, "b")
fmt.Println(s)
}
Output:
[a b c d]
[a c d]
I'm not sure if this is idiomatic, but this works quite well:
package main
import "fmt"
func splice(start, count int, items []string) (ret []string) {
ret = make([]string, len(items)-count)
copy(ret, items[:start])
copy(ret[start:], items[start+count:])
return
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = splice(1, 2, s)
fmt.Println(s)
}
Go Playground: https://play.golang.org/p/UNtdtw77sEQ

Sorting slices of strings by a map of their frequencies

I have a slice of slices of strings, and want to sort them by their frequency, I tried to follow the byAge example from the docs here http://golang.org/pkg/sort/ but was unable how to pass a list of frequencies to it.
Meaning, the outcome of the example would be:
[[a,b] [a,b,c,d] [a,c,d,e]]
Would the approach be to have "a" be represented by a custom struct with frequency as it's own attribute? That seems to be more in line with the byAge example.
func main() {
transactions := [][]string{{"a", "b"}, {"b", "c", "d", "a"}, {"c", "d", "e", "a"}}
frequencies := map[string]int{
"a": 3,
"b": 2,
"c": 2,
"d": 2,
"e": 1,
}
fmt.Println(transactions, frequencies)
}
In case you need more than the data you want to sort in the sorting process, a common way is
to implement your own struct, yes. In your case this would be something like this (on play):
type SortableTransaction struct {
data []string
frequencies map[string]int
}
data would be the slice with strings and frequencies your specific frequency table.
The following implementation could be used for the Sort interface:
func (s SortableTransaction) Len() int { return len(s.data) }
func (s SortableTransaction) Less(i, j int) bool {
return s.frequencies[s.data[i]] > s.frequencies[s.data[j]]
}
func (s SortableTransaction) Swap(i, j int) {
s.data[j], s.data[i] = s.data[i], s.data[j]
}
If your frequency table is constant, you can declare it at package level of course.
In case you want to sort the outer slice as well, you'd have to sort the inner slices
first and then the outer slices.
For example,
package main
import (
"fmt"
"sort"
)
type NameFrequency struct {
Name string
Frequency int
}
func (nf NameFrequency) String() string {
return fmt.Sprintf("%s: %d", nf.Name, nf.Frequency)
}
type ByFrequency []NameFrequency
func (nf ByFrequency) Len() int { return len(nf) }
func (nf ByFrequency) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] }
func (nf ByFrequency) Less(i, j int) bool {
less := nf[i].Frequency > nf[j].Frequency
if nf[i].Frequency == nf[j].Frequency {
less = nf[i].Name < nf[j].Name
}
return less
}
func SortByFrequency(names []string, frequencies map[string]int) []string {
nf := make(ByFrequency, len(names))
for i, name := range names {
nf[i] = NameFrequency{name, frequencies[name]}
}
sort.Sort(ByFrequency(nf))
sortedNames := make([]string, len(names))
for i, nf := range nf {
sortedNames[i] = nf.Name
}
return sortedNames
}
func main() {
transactions := [][]string{{"a", "b"}, {"b", "c", "d", "a"}, {"c", "d", "e", "a"}}
fmt.Println(transactions)
frequencies := map[string]int{
"a": 3,
"b": 2,
"c": 2,
"d": 2,
"e": 1,
}
fmt.Println(frequencies)
sortedTransactions := make([][]string, len(transactions))
for i, transaction := range transactions {
sortedTransactions[i] = SortByFrequency(transaction, frequencies)
}
fmt.Println(sortedTransactions)
}
Output:
[[a b] [b c d a] [c d e a]]
map[a:3 b:2 c:2 d:2 e:1]
[[a b] [a b c d] [a c d e]]

Resources