golang sort.Sort random output and is wrong - sorting

I have a custom Sort function applied to a struct. The full code is here on play.golang.org.
type Stmt struct {
Name string
After []string
}
func sortStmts(stmts []Stmt) []Stmt {
sort.Sort(ByAfter(stmts))
return stmts
}
type ByAfter []Stmt
func (a ByAfter) Len() int { return len(a) }
func (a ByAfter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAfter) Less(i, j int) bool {
isLess := true
//fmt.Printf("%s.%+v is being compared with %s.%+v\n", a[i].Name, a[i].After, a[j].Name, a[j].After)
for _, v := range a[i].After {
if a[j].Name == v {
isLess = false
break
}
}
if isLess {
//fmt.Printf("%s.%+v is Before %s.%+v\n", a[i].Name, a[i].After, a[j].Name, a[j].After)
} else {
//fmt.Printf("%s.%+v is After %s.%+v\n", a[i].Name, a[i].After, a[j].Name, a[j].After)
}
return isLess
}
My purpose is to automatically create a set of sql create statements ordered properly so that dependent tables come first.
So if Stmt{Name: "user_role", After: []string{"user", "role"} } is present, then in the ordered list user_role should be after both user and role.
This seemed to work fine for a bit until we started adding more values. Only then did I go in and check and realize that I might have just got accidentally lucky the first time, but there really isn't any consistency.
Is there something I'm doing wrong in the sort function that the results are random. I'm particularly interested in seeing why the "role" item is not coming before the "user_role" item even though I've designated it user_role as coming after role.

Your "Less" function is not transitive. That is, if A < B and B < C, then it must also hold that A < C.
You can't specify a partial order with a regular sort function and get sorted output. Instead, you need to implement a topological sort.
Here's a simple implementation on your data (except that I removed the duplicate "passwords" entry).
package main
import "fmt"
type Stmt struct {
Name string
After []string
}
func topSort(ss []Stmt) []string {
after := map[string][]string{} // things that must come after
counts := map[string]int{} // number unsatified preconditions
zc := map[string]struct{}{} // things with zero count
for _, s := range ss {
for _, a := range s.After {
after[a] = append(after[a], s.Name)
}
counts[s.Name] = len(s.After)
if len(s.After) == 0 {
zc[s.Name] = struct{}{}
}
}
r := []string{}
for len(zc) > 0 {
for n := range zc {
r = append(r, n)
for _, a := range after[n] {
counts[a]--
if counts[a] == 0 {
zc[a] = struct{}{}
}
}
delete(zc, n)
}
}
return r
}
func main() {
stmts := []Stmt{
{Name: "app", After: []string{"app_user"}},
{Name: "billingplan", After: []string{}},
{Name: "campaign", After: []string{"app_user"}},
{Name: "campaign_app", After: []string{"campaign", "app"}},
{Name: "campaign_ip", After: []string{"campaign", "ip"}},
{Name: "campaign_operator", After: []string{"campaign", "operator"}},
{Name: "campaign_sponsor", After: []string{"campaign", "sponsor"}},
{Name: "campaign_subscriberfilter", After: []string{"campaign", "subscriber_filters"}},
{Name: "campaign_url", After: []string{"campaign", "url"}},
{Name: "contentpartner", After: []string{"app_user"}},
{Name: "filter_criteria", After: []string{"campaign", "subscriber_filters"}},
{Name: "ip", After: []string{"app_user"}},
{Name: "mobile_registered", After: []string{"campaign", "app"}},
{Name: "operator", After: []string{}},
{Name: "passwords", After: []string{"app_user"}},
{Name: "publish_package", After: []string{}},
{Name: "role", After: []string{}},
{Name: "sponsor", After: []string{"app_user"}},
{Name: "subscriber_dbs", After: []string{}},
{Name: "subscriber_filters", After: []string{"subscriber_dbs"}},
{Name: "timezone", After: []string{}},
{Name: "url", After: []string{"app_user"}},
{Name: "app_user", After: []string{}},
{Name: "user_role", After: []string{"app_user", "role"}},
}
r := topSort(stmts)
for _, s := range r {
fmt.Println(s)
}
}

As mentioned by Anonymous, you need a topological sort for this. Tarjan's strongly connected components algorithm has the property that SCCs are returned in reverse topological sort order. This means it can be used as a topological sort algorithm.
Here is an implementation of Tarjan's algorithm (runnable here and originally posted by me to the golang-nuts list) based on the pseudocode at wikipedia (more generally implemented here but using essentially the same underlying code):
package main
import (
"fmt"
"log"
)
type Stmt struct {
Name string
After []string
}
func main() {
stmts := []Stmt{
{Name: "app", After: []string{"app_user"}},
{Name: "billingplan", After: []string{}},
{Name: "campaign", After: []string{"app_user"}},
{Name: "campaign_app", After: []string{"campaign", "app"}},
{Name: "campaign_ip", After: []string{"campaign", "ip"}},
{Name: "campaign_operator", After: []string{"campaign", "operator"}},
{Name: "campaign_sponsor", After: []string{"campaign", "sponsor"}},
{Name: "campaign_subscriberfilter", After: []string{"campaign", "subscriber_filters"}},
{Name: "campaign_url", After: []string{"campaign", "url"}},
{Name: "contentpartner", After: []string{"app_user"}},
{Name: "filter_criteria", After: []string{"campaign", "subscriber_filters"}},
{Name: "ip", After: []string{"app_user"}},
{Name: "mobile_registered", After: []string{"campaign", "app"}},
{Name: "operator", After: []string{}},
{Name: "passwords", After: []string{"app_user"}},
{Name: "publish_package", After: []string{}},
{Name: "role", After: []string{}},
{Name: "passwords", After: []string{"app_user"}},
{Name: "sponsor", After: []string{"app_user"}},
{Name: "subscriber_dbs", After: []string{}},
{Name: "subscriber_filters", After: []string{"subscriber_dbs"}},
{Name: "timezone", After: []string{}},
{Name: "url", After: []string{"app_user"}},
{Name: "app_user", After: []string{}},
{Name: "user_role", After: []string{"app_user", "role"}},
}
g := make(graph)
for _, s := range stmts {
g[s.Name] = after(s.After)
}
sorted, err := topoSort(g)
if err != nil {
log.Fatalf("could not sort: %v", err)
}
for _, s := range sorted {
fmt.Println(s)
}
}
func topoSort(g graph) ([]string, error) {
sccs := tarjanSCC(g)
sorted := make([]string, len(sccs))
for i, s := range sccs {
if len(s) != 1 {
return nil, fmt.Errorf("found directed cycle: %q", s)
}
sorted[i] = s[0]
}
return sorted, nil
}
// graph is an edge list representation of a directed graph.
type graph map[string]set
// set is an string set.
type set map[string]struct{}
func after(i []string) set {
if len(i) == 0 {
return nil
}
s := make(set)
for _, v := range i {
s[v] = struct{}{}
}
return s
}
// tarjanSCC returns a the strongly connected components of the
// directed graph g.
func tarjanSCC(g graph) [][]string {
t := tarjan{
g: g,
indexTable: make(map[string]int, len(g)),
lowLink: make(map[string]int, len(g)),
onStack: make(map[string]bool, len(g)),
}
for v := range t.g {
if t.indexTable[v] == 0 {
t.strongconnect(v)
}
}
return t.sccs
}
// tarjan implements Tarjan's strongly connected component finding
// algorithm. The implementation is from the pseudocode at
//
// http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
//
type tarjan struct {
g graph
index int
indexTable map[string]int
lowLink map[string]int
onStack map[string]bool
stack []string
sccs [][]string
}
// strongconnect is the strongconnect function described in the
// wikipedia article.
func (t *tarjan) strongconnect(v string) {
// Set the depth index for v to the smallest unused index.
t.index++
t.indexTable[v] = t.index
t.lowLink[v] = t.index
t.stack = append(t.stack, v)
t.onStack[v] = true
// Consider successors of v.
for w := range t.g[v] {
if t.indexTable[w] == 0 {
// Successor w has not yet been visited; recur on it.
t.strongconnect(w)
t.lowLink[v] = min(t.lowLink[v], t.lowLink[w])
} else if t.onStack[w] {
// Successor w is in stack s and hence in the current SCC.
t.lowLink[v] = min(t.lowLink[v], t.indexTable[w])
}
}
// If v is a root node, pop the stack and generate an SCC.
if t.lowLink[v] == t.indexTable[v] {
// Start a new strongly connected component.
var (
scc []string
w string
)
for {
w, t.stack = t.stack[len(t.stack)-1], t.stack[:len(t.stack)-1]
t.onStack[w] = false
// Add w to current strongly connected component.
scc = append(scc, w)
if w == v {
break
}
}
// Output the current strongly connected component.
t.sccs = append(t.sccs, scc)
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
Note that running this code repeatedly will not result in the same strict ordering of output since a number of the paths are not definitively orderable relative to each other (this won't show up in the playground since the results are cached - you can see this though by wrapping the call to tarjanSCC).
Although it may be easier to directly implement a topological sort, by using Tarjan's SCC algorithm, we are able to find the cause of a sort failure, for example here (cf with the same data here).

This question was answered here: https://groups.google.com/forum/#!topic/golang-nuts/C_7JY1f3cSc. It was a more detailed and specific answer that I was able to immediately use. Requested the person to update it here but he/she hasn't ... so putting it myself.
Since I had a tarjan lying around. Here is the topological sort for that
data:
http://play.golang.org/p/SHagFMvuhl
On Thu, 2015-03-12 at 22:47 -0700, Sathish VJ wrote:
In my case, I did check and there is that transitive nature in the
dependencies.
One specific thing I'm noting when I print out the debug statements is
that some comparisons are never happening. For example, user_role is
never getting compared to role as of now. Though at one point when
there were lesser elements, it was.
sort.Sort will not make all comparisons. That would result in O(n^2)
time.

Related

Generating combinatorial string from map

I have a map as such:
// map[int] position in string
// map[rune]bool characters possible at said position
func generateString(in map[int]map[rune]bool) []string {
// example: {0: {'A':true, 'C': true}, 1: {'E': true}, 2: {'I': true, 'X': true}}
result := []string{"AEI", "AEX", "CEI", "CEX"} // should generate these
return result
}
The difference with all possible permutations is that we are specifying which permutations are possible by index and I think that's the real head-breaker here.
First, we need to convert map[int]map[rune]bool to []map[rune]bool since map iteration isn't guaranteed to be sorted by key's
After that, this is a recursive approach
var res []string
func dfs(curString string, index int, in []map[rune]bool) {
if index == len(in) {
res = append(res, curString)
return
}
for ch, is := range in[index] {
if !is { // I assume booleans can be false
return
}
dfs(curString+string(ch), index+1, in)
}
}
and we can call it with dfs("", 0, arr) where arr is given map converted to slice and answer will be in res variable

Checking if map has duplicate values in Go

Consider the following map
mymap := make(map[string]string)
mymap["a"] = "one"
mymap["b"] = "two"
mymap["c"] = "one"
How to determine if the values are unique?
One strategy is to iterate through the map, create a slice of the values. Then iterate through the slice to find duplicates. Is there a better way?
If you just need true/false of whether there are dupes, without needing to know which values are dupes or how many dupes there are, the most efficient structure to use to track existing values is a map with empty struct values.
See here (pasted below for convenience):
package main
import (
"fmt"
)
func hasDupes(m map[string]string) bool {
x := make(map[string]struct{})
for _, v := range m {
if _, has := x[v]; has {
return true
}
x[v] = struct{}{}
}
return false
}
func main() {
mapWithDupes := make(map[string]string)
mapWithDupes["a"] = "one"
mapWithDupes["b"] = "two"
mapWithDupes["c"] = "one"
fmt.Println(hasDupes(mapWithDupes)) // prints true
mapWithoutDupes := make(map[string]string)
mapWithoutDupes["a"] = "one"
mapWithoutDupes["b"] = "two"
mapWithoutDupes["c"] = "three"
fmt.Println(hasDupes(mapWithoutDupes)) // prints false
}
func main() {
m := map[int]int{
1: 100,
2: 200,
3: 100,
4: 400,
6: 200,
7: 700,
}
mNew := make(map[int]int)
for k, v := range m {
if val, has := mNew[v]; !has {
mNew[v] = k
} else {
fmt.Println(k, m[k], ",", val, m[val])
}
}
swap the map key & value with a new map
Second map wont insert duplicate key, so you can find the values

How to group by then merge slices with duplicated values in Go

Excuse me, this is my first Stackoverflow question, so, any tips/advice on what I can do to improve it would be wonderful, in addition to some help.
The problem:
I have a slice that I am trying to group into smaller slices by certain criteria. I then need to merge the newly created slices with each other if they contain any of the same values in the slice. (Essentially, appending slices together that have "overlapping" values).
Some additional notes about the problem:
The number of items in the original slice will likely be between 1-50, in most cases, with outliers rarely exceeding 100.
Once gropued, the size of the 'inside' slices will be between 1-10 values.
Performance is a factor, as this operation will be run as part of a webservice where a single request will perform this operation 20+ times, and there can be many (hundreds - thousands) of requests per minute at peak times. However, clarity of code is also important.
My implementation is using ints, the final implementation would have more complex structs, though I was considering making a map and then use the implementation shown below based upon the keys. Is this a good idea?
I have broken the problem down into a few steps:
Create a 2D slice with groupings of values, based up criteria (the initial grouping phase)
Attempt to merge slices in place if they include a duplicated value.
I am running into two problems:
First, I think my implementation might not scale super well, as it tends to have some nested loops (however, these loops will be iterating on small slices, so that might be ok)
Second, my implementation is requiring an extra step at the end to remove duplicated values, ideally we should remove it.
Input: [ 100, 150, 300, 350, 600, 700 ]
Expected Output: [[100 150 300 350] [600 700]]
This is with the 'selection criteria' of grouping values that are within 150 units of at least one other value in the slice.
And the code (Go Playground link) :
package main
import (
"fmt"
"sort"
)
func filter(vs []int, f func(int) bool) []int {
vsf := make([]int, 0)
for _, v := range vs {
if f(v) {
vsf = append(vsf, v)
}
}
return vsf
}
func unique(intSlice []int) []int {
keys := make(map[int]bool)
list := []int{}
for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}
func contains(intSlice []int, searchInt int) bool {
for _, value := range intSlice {
if value == searchInt {
return true
}
}
return false
}
func compare(a, b []int) bool {
if len(a) != len(b) {
return false
}
if (a == nil) != (b == nil) {
return false
}
b = b[:len(a)]
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func main() {
fmt.Println("phase 1 - initial grouping")
s := []int{100, 150, 300, 350, 600, 700}
g := make([][]int, 0)
// phase 1
for _, v := range s {
t := filter(s, func(i int) bool { return i - v >= -150 && i - v <= 150 })
for _, v1 := range t {
t1 := filter(s, func(i int) bool { return i - v1 >= -150 && i - v1 <= 150})
t = unique(append(t, t1...))
sort.Ints(t)
}
g = append(g, t)
fmt.Println(g)
}
// phase 2
fmt.Println("phase 2 - merge in place")
for i, tf := range g {
for _, death := range tf {
if i < len(g) - 1 && contains(g[i+1], death) {
g[i+1] = unique(append(g[i], g[i+1]...))
g = g[i+1:]
} else if i == len(g) - 1 {
fmt.Println(g[i], g[i-1])
// do some cleanup to make sure the last two items of the array don't include duplicates
if compare(g[i-1], g[i]) {
g = g[:i]
}
}
}
fmt.Println(i, g)
}
}
Not sure what you are actually asking, and the problem isn't fully defined.
So here's a version that is more efficient
If input is not sorted and output order matters, then this is a bad solution.
Here it is (on Play)
package main
import (
"fmt"
)
// Input: [ 100, 150, 300, 350, 600, 700 ] Expected Output: [[100 150 300 350] [600 700]]
func main() {
input := []int{100, 150, 300, 350, 600, 700}
fmt.Println("Input:", input)
fmt.Println("Output:", groupWithin150(input))
}
func groupWithin150(ints []int) [][]int {
var ret [][]int
// Your example input was sorted, if the inputs aren't actually sorted, then uncomment this
// sort.Ints(ints)
var group []int
for idx, i := range ints {
if idx > 0 && i-150 > group[len(group)-1] {
ret = append(ret, group)
group = make([]int, 0)
}
group = append(group, i)
}
if len(group) > 0 {
ret = append(ret, group)
}
return ret
}

How to get intersection of two slice in golang?

Is there any efficient way to get intersection of two slices in Go?
I want to avoid nested for loop like solution
slice1 := []string{"foo", "bar","hello"}
slice2 := []string{"foo", "bar"}
intersection(slice1, slice2)
=> ["foo", "bar"]
order of string does not matter
How do I get the intersection between two arrays as a new array?
Simple Intersection: Compare each element in A to each in B (O(n^2))
Hash Intersection: Put them into a hash table (O(n))
Sorted Intersection: Sort A and do an optimized intersection (O(n*log(n)))
All of which are implemented here
https://github.com/juliangruber/go-intersect
simple, generic and mutiple slices ! (Go 1.18)
Time Complexity : may be linear
func interSection[T constraints.Ordered](pS ...[]T) []T {
hash := make(map[T]*int) // value, counter
result := make([]T, 0)
for _, slice := range pS {
duplicationHash := make(map[T]bool) // duplication checking for individual slice
for _, value := range slice {
if _, isDup := duplicationHash[value]; !isDup { // is not duplicated in slice
if counter := hash[value]; counter != nil { // is found in hash counter map
if *counter++; *counter >= len(pS) { // is found in every slice
result = append(result, value)
}
} else { // not found in hash counter map
i := 1
hash[value] = &i
}
duplicationHash[value] = true
}
}
}
return result
}
func main() {
slice1 := []string{"foo", "bar", "hello"}
slice2 := []string{"foo", "bar"}
fmt.Println(interSection(slice1, slice2))
// [foo bar]
ints1 := []int{1, 2, 3, 9, 8}
ints2 := []int{10, 4, 2, 4, 8, 9} // have duplicated values
ints3 := []int{2, 4, 8, 1}
fmt.Println(interSection(ints1, ints2, ints3))
// [2 8]
}
playground : https://go.dev/play/p/lE79D0kOznZ
It's a best method for intersection two slice. Time complexity is too low.
Time Complexity : O(m+n)
m = length of first slice.
n = length of second slice.
func intersection(s1, s2 []string) (inter []string) {
hash := make(map[string]bool)
for _, e := range s1 {
hash[e] = true
}
for _, e := range s2 {
// If elements present in the hashmap then append intersection list.
if hash[e] {
inter = append(inter, e)
}
}
//Remove dups from slice.
inter = removeDups(inter)
return
}
//Remove dups from slice.
func removeDups(elements []string)(nodups []string) {
encountered := make(map[string]bool)
for _, element := range elements {
if !encountered[element] {
nodups = append(nodups, element)
encountered[element] = true
}
}
return
}
if there exists no blank in your []string, maybe you need this simple code:
func filter(src []string) (res []string) {
for _, s := range src {
newStr := strings.Join(res, " ")
if !strings.Contains(newStr, s) {
res = append(res, s)
}
}
return
}
func intersections(section1, section2 []string) (intersection []string) {
str1 := strings.Join(filter(section1), " ")
for _, s := range filter(section2) {
if strings.Contains(str1, s) {
intersection = append(intersection, s)
}
}
return
}
Try it
https://go.dev/play/p/eGGcyIlZD6y
first := []string{"one", "two", "three", "four"}
second := []string{"two", "four"}
result := intersection(first, second) // or intersection(second, first)
func intersection(first, second []string) []string {
out := []string{}
bucket := map[string]bool{}
for _, i := range first {
for _, j := range second {
if i == j && !bucket[i] {
out = append(out, i)
bucket[i] = true
}
}
}
return out
}
https://github.com/viant/toolbox/blob/a46fd679bbc5d07294b1d1b646aeacd44e2c7d50/collections.go#L869-L920
Another O(m+n) Time Complexity solution that uses a hashmap.
It has two differences compared to the other solutions discussed here.
Passing the target slice as a parameter instead of new slice returned
Faster to use for commonly used types like string/int instead of reflection for all
Yes there are a few different ways to go about it.. Here's an example that can be optimized.
package main
import "fmt"
func intersection(a []string, b []string) (inter []string) {
// interacting on the smallest list first can potentailly be faster...but not by much, worse case is the same
low, high := a, b
if len(a) > len(b) {
low = b
high = a
}
done := false
for i, l := range low {
for j, h := range high {
// get future index values
f1 := i + 1
f2 := j + 1
if l == h {
inter = append(inter, h)
if f1 < len(low) && f2 < len(high) {
// if the future values aren't the same then that's the end of the intersection
if low[f1] != high[f2] {
done = true
}
}
// we don't want to interate on the entire list everytime, so remove the parts we already looped on will make it faster each pass
high = high[:j+copy(high[j:], high[j+1:])]
break
}
}
// nothing in the future so we are done
if done {
break
}
}
return
}
func main() {
slice1 := []string{"foo", "bar", "hello", "bar"}
slice2 := []string{"foo", "bar"}
fmt.Printf("%+v\n", intersection(slice1, slice2))
}
Now the intersection method defined above will only operate on slices of strings, like your example.. You can in theory create a definition that looks like this func intersection(a []interface, b []interface) (inter []interface), however you would be relying on reflection and type casting so that you can compare, which will add latency and make your code harder to read. It's probably easier to maintain and read to write a separate function for each type you care about.
func intersectionString(a []string, b []string) (inter []string),
func intersectionInt(a []int, b []int) (inter []int),
func intersectionFloat64(a []Float64, b []Float64) (inter []Float64), ..ect
You can then create your own package and reuse once you settle how you want to implement it.
package intersection
func String(a []string, b []string) (inter []string)
func Int(a []int, b []int) (inter []int)
func Float64(a []Float64, b []Float64) (inter []Float64)

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

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

Resources