This question already has answers here:
What is the shortest way to simply sort an array of structs by (arbitrary) field names?
(6 answers)
Closed 26 days ago.
Let's say I have struct SortableStruct with 3 fields A B C I want to implement function that consumes sl []SortableStruct and orderFied string where orderField is one of struct's field. This function should retrun slice sorted by orderField. Is there a way of doing this without huge switch case. It's not ovious for me how to implement sort.Interface when I want compare structs by different fields.
Well, easiest way is to switch field type and assign a SORT function. Here is your code:
package main
import (
"fmt"
"sort"
)
type SortableStruct struct {
A int
B int
C int
}
func sortStruct(arr []SortableStruct, field string) {
var less func(i, j int) bool
switch field {
case "B":
less = func(i, j int) bool {
return arr[i].B < arr[j].B
}
case "C":
less = func(i, j int) bool {
return arr[i].C < arr[j].C
}
default:
less = func(i, j int) bool {
return arr[i].A < arr[j].A
}
}
sort.Slice(arr, less)
}
func main() {
arr := []SortableStruct{
{
A: 1,
B: 5,
C: 3,
},
{
A: 2,
B: 3,
C: 20,
},
{
A: -1,
B: -1,
C: 10,
},
}
sortStruct(arr, "C")
fmt.Println(arr)
}
Another idea would be to have 3 defined types, each of them implementing interface sort.Interface
type SortableStructByA []SortableStruct
type SortableStructByB []SortableStruct
type SortableStructByC []SortableStruct
And then, you will have to cast your slice to the wanted type(depending on sort you want) and to do something like this:
sortableSlice := SortableStructByA(arr)
sort.Sort(sortableSlice)
Related
This question already has answers here:
How to find out element position in slice?
(10 answers)
Closed 2 years ago.
I am trying to find out the equivalent of indexof to get the position of specific element in array golang the purpose for integers in array.
package main
import (
"fmt"
)
func main() {
fmt.Println("what")
arr := []int{1, 2, 3, 4, 2, 2, 3, 5, 4, 4, 1, 6}
i := IndexOf(2, arr)
}
Write a function. Here's an example assuming that IndexOf returns the first index of the number or -1 if none is found.
// IndexOf returns the first index of needle in haystack
// or -1 if needle is not in haystack.
func IndexOf(haystack []int, needle int) int {
for i, v := range haystack {
if v == needle {
return i
}
}
return -1
}
Run this code on the Go Programming Language Playground.
There is no common library function to do this for you in go.
However if you are using a byte slice, you can use IndexByte(b []byte, c byte) int.
Or you can write a quick function which does this for you:
func indexOf(arr []int, val int) int {
for pos, v := range arr {
if v == val {
return pos
}
}
return -1
}
package main
import "fmt"
func IndexOf(arr []int, candidate int) int {
for index, c := range arr {
if c == candidate {
return index
}
}
return -1
}
func main() {
fmt.Println("what")
arr := []int{1, 2, 3, 4, 2, 2, 3, 5, 4, 4, 1, 6}
i := IndexOf(arr, 2)
fmt.Println(i)
}
Add a method IndexOf to search, this is a linear search method.
Ref: https://play.golang.org/p/Hp6Dg--XoIV
There is no equivalent for IndexOf in Go. You need to implement one your self. But if you have have sorted array of Ints, you can use sort.SearchInts as shown below.
package main
import (
"fmt"
"sort"
)
func main() {
fmt.Println(sort.SearchInts([]int{2,3,4,5,9,10,11}, 5))
}
Also from the godoc:
SearchInts searches for x in a sorted slice of ints and returns the index as specified by Search. The return value is the index to insert x if x is not present (it could be len(a)). The slice must be sorted in ascending order.
I'm trying to implement a function that takes an element of any type and a slice of the same type and search the first inside the second, giving it's position as result or -1 otherwise.
I'm not a Go expert, so my first thought was to pass the element to search as interface{} and the slice as []interface{}, but it didn't really work.
Here's what I tried:
package main
import (
"fmt"
)
func IsElementInListWithPos(element interface{}, list []interface{}) int {
for i := range list {
if list[i] == element {
return i
}
}
return -1
}
func main() {
list1 := []int{1, 2, 3, 4, 5, 6}
list2 := []string{"a", "b", "c", "d"}
pos1 := IsElementInListWithPos(3, list1)
pos2 := IsElementInListWithPos("a", list2)
fmt.Println(pos1, pos2)
}
It gives me the following errors:
cannot use list (type []int) as type []interface {} in argument to IsElementInListWithPos
cannot use list2 (type []string) as type []interface {} in argument to IsElementInListWithPos
Any idea how I could solve this issue without actually using two different functions?
Thanks in advance.
The sort package demonstrates how interfaces can be used to implement algorithms in a type-independent way.
Linear search requires two essential operations that depend on the haystack element type, Len and Equal. So we can write the following Haystack interface and a Search function that using it:
type Haystack interface {
Len() int
Equal(int, interface{}) bool
}
func Search(haystack Haystack, needle interface{}) int {
for i := 0; i < haystack.Len(); i++ {
if haystack.Equal(i, needle) {
return i
}
}
return -1
}
This makes writing implementations for Haystack simple, but not type-safe:
type Strings []string
func (s Strings) Len() int { return len(s) }
func (s Strings) Equal(i int, x interface{}) bool { return s[i] == x.(string) }
type Ints []int
func (s Ints) Len() int { return len(s) }
func (s Ints) Equal(i int, x interface{}) bool { return s[i] == x.(int) }
func main() {
strings := []string{"b", "a", "c", "d"}
fmt.Println(Search(Strings(strings), "c")) // 2
fmt.Println(Search(Strings(strings), "e")) // -1
ints := []int{2, 1, 3, 4}
fmt.Println(Search(Ints(ints), 3)) // 2
fmt.Println(Search(Ints(ints), 5)) // -1
}
Note the type assertions in the Equal methods. To make this type-safe we have to get rid of the interface{} argument to Equal:
type Haystack interface {
Len() int
Equal(int) bool
}
func Search(haystack Haystack) int {
for i := 0; i < haystack.Len(); i++ {
if haystack.Equal(i) {
return i
}
}
return -1
}
type Strings struct {
hs []string
needle string
}
func (s Strings) Len() int { return len(s.hs) }
func (s Strings) Equal(i int) bool { return s.hs[i] == s.needle }
type Ints struct {
hs []int
needle int
}
func (s Ints) Len() int { return len(s.hs) }
func (s Ints) Equal(i int) bool { return s.hs[i] == s.needle }
func main() {
strings := []string{"b", "a", "c", "d"}
fmt.Println(Search(Strings{strings, "c"})) // 2
fmt.Println(Search(Strings{strings, "e"})) // -1
ints := []int{2, 1, 3, 4}
fmt.Println(Search(Ints{ints, 3})) // 2
fmt.Println(Search(Ints{ints, 5})) // -1
}
This made both the interface implementations and using the Search function much more complicated.
The moral of the story is that using interfaces this way requires a sufficiently complicated algorithm to be worth the trouble. If writing the interface implementation for a particular type is more work than writing the concrete implementation for the algorithm, well, then just write the concrete functions you need:
func SearchStr(haystack []string, needle string) int {
for i, x := range haystack {
if x == needle {
return i
}
}
return -1
}
func SearchInt(haystack []int, needle int) int {
for i, x := range haystack {
if x == needle {
return i
}
}
return -1
}
func main() {
strings := []string{"b", "a", "c", "d"}
fmt.Println(SearchStr(strings, "c")) // 2
fmt.Println(SearchStr(strings, "e")) // -1
ints := []int{2, 1, 3, 4}
fmt.Println(SearchInt(ints, 3)) // 2
fmt.Println(SearchInt(ints, 5)) // -1
}
Currently, it is not possible to build a solution that respects all your criteria. It will be possible once generics are implemented. Or you could try building one using reflect, but that will yield a complex and potentially slow solution... so I generally advise against using reflect for something as simple as this (see second snippet below).
What you can do right now is to use something like:
func FindFirst(n int, f func(int) bool) int {
for i := 0; i < n; i++ {
if f(i) {
return i
}
}
return -1
}
// in your code (s is the slice, e the value you are searching for)
i := FindFirst(len(s), func(i int) bool {
return s[i] == e
})
if i != -1 {
// i is the index of the element with value e
}
This, as you can imagine, does not make much sense... as it's arguably simpler, faster, and more idiomatic to simply write out the loop explicitly:
// in your code (s is the slice, e the value you are searching for)
for i, v := range s {
if v == e {
_ = i // i is the index of the element with value e
break
}
}
Obviously, this whole approach (linear scan) is only reasonable if the number of elements in the slice is small. If your slice is big and changes rarely, it would arguably make more sense (from a time complexity perspective) to sort it (sort.Slice) first and then do binary searches (sort.Search) on the sorted slice. Or, alternatively, you could use a map instead: in which case (assuming keys are small) lookup would be O(1).
I have the below map:
detail := make(map[string]*Log)
type Log struct {
Id []string
Name []string
Priority int // value could be 1, 2, 3
Message string
}
I want to sort the "detail" map on basis of the value which is a struct in my case. This should be sorted by attribute "Priority".
For example, Log (map of struct) may have values similar to below:
Z : &{[ba60] [XYZ] 3 "I am the boss"}
B : &{[ca50] [ABC] 2 "I am the Junior"}
U : &{[zc20] [PQR] 1 "I am the Newbie"}
I want them to print from increasing Priority order i.e. 1 to 3
U : &{[zc20] [PQR] 1 "I am the Newbie"}
B : &{[ca50] [ABC] 2 "I am the Junior"}
Z : &{[ba60] [XYZ] 3 "I am the boss"}
I tried to use the sort and implemented the Sort interface, but seems like still missing the clue somewhere. So, I implemented the below interface:
type byPriority []*Log
func (d byPriority) Len() int {
return len(d)
}
func (d byPriority) Less(i, j int) bool {
return d[i].Priority < d[j].Priority
}
func (d byPriority) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
But how should I apply sort.Sort() method on this map to get the sorted result. Do I need to add some more code?
The map type in Go is unordered. Regardless of what you do to a map, the next time you iterate over it you will receive the keys in random order. Thus there is no way to "sort" a map.
What you can do is copy the entries of the map into a slice, which is sortable.
package main
import (
"fmt"
"sort"
)
type Log struct {
Id []string
Name []string
Priority int // value could be 1, 2, 3
Message string
}
type Entry struct {
key string
value *Log
}
type byPriority []Entry
func (d byPriority) Len() int {
return len(d)
}
func (d byPriority) Less(i, j int) bool {
return d[i].value.Priority < d[j].value.Priority
}
func (d byPriority) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func printSorted(detail map[string]*Log) {
// Copy entries into a slice.
slice := make(byPriority, 0, len(detail))
for key, value := range detail {
slice = append(slice, Entry{key, value})
}
// Sort the slice.
sort.Sort(slice)
// Iterate and print the entries in sorted order.
for _, entry := range slice {
fmt.Printf("%s : %v\n", entry.key, entry.value)
}
}
func main() {
detail := map[string]*Log{
"Z": &Log{[]string{"ba60"}, []string{"XYZ"}, 3, "I am the boss"},
"B": &Log{[]string{"ca50"}, []string{"ABC"}, 2, "I am the Junior"},
"U": &Log{[]string{"zc20"}, []string{"PQR"}, 1, "I am the Newbie"},
}
printSorted(detail)
}
Okay, so I am very new to Go and i am trying to get myself familiar with sorting by functions. I might have misunderstood something, so please correct me if i'm wrong.
I am trying to create an array of Nodes with fields key and value. I would like to create a custom sorting function that sorts the array of nodes by their keys. Here's my working so far:
package main
import (
"sort"
"fmt"
)
type Node struct {
key, value int
}
type ByKey []Node
func (s ByKey) Len() int {
return len(s)
}
func (s ByKey) Swap(i, j Node) {
temp := Node{key: i.key, value : i.value}
i.key, i.value = j.key, j.value
j.key, j.value = temp.key, temp.value
}
func (s ByKey) Less(i, j Node) bool {
return i.key < j.key
}
func main(){
nodes := []Node{
{ key : 1, value : 100 },
{ key : 2, value : 200 },
{ key : 3, value : 50 },
}
sort.Sort(ByKey(nodes))
fmt.Println(nodes)
}
But I keep getting this error at the line where I am calling Sort:
cannot use ByKey(nodes) (type ByKey) as type sort.Interface in argument to sort.Sort:
ByKey does not implement sort.Interface (wrong type for Less method)
have Less(Node, Node) bool
want Less(int, int) bool
I am not sure what this error is trying to convey. Any help would be appreciated. TIA
These functions take collection indexes, not elements from the collection. You then use those indexes to index into the ByKey array - see the reference for this interface in the sort package.
So then you need to rewrite your functions to take int. The only one you need to change typically is the less function, which in your case will use the key rather than just saying s[i] < s[j] you'd be saying s[i].key < s[j].key. Here is a runnable example: play.golang.org
type ByKey []Node
func (s ByKey) Len() int { return len(s) }
func (s ByKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ByKey) Less(i, j int) bool { return s[i].key < s[j].key }
func main() {
nodes := []Node{
{key: 2, value: 200},
{key: 1, value: 100},
{key: 3, value: 50},
}
sort.Sort(ByKey(nodes))
fmt.Println(nodes)
}
However, in your case since you just want to sort a slice, it might be more convenient to use sort.Slice and forget about the interface and a separate slice type. You can do the sorting then in one line of code in place.
nodes := []Node{
{key: 2, value: 200},
{key: 1, value: 100},
{key: 3, value: 50},
}
sort.Slice(nodes, func(i, j int) bool { return nodes[i].key < nodes[j].key })
I have a map of structs that I am populating by streaming data to a Go program. The way the map is updated is similar to the example below.
Once I have this map of structs populated, what is the best (or good) way to sort this map by the values of the count field in the struct?
package main
type data struct {
count int64
}
func main() {
m := make(map[string]data)
m["x"] = data{0, 0}
if xx, ok := m["x"]; ok {
xx.count = 2
m["x"] = xx
} else {
panic("X isn't in the map")
}
}
This example can be run here: http://play.golang.org/p/OawL6QIXuO
As siritinga already pointed out, the elements of a map isn't ordered, so you cannot sort it.
What you can do is to create a slice and sort the elements using the sort package:
package main
import (
"fmt"
"sort"
)
type dataSlice []*data
type data struct {
count int64
size int64
}
// Len is part of sort.Interface.
func (d dataSlice) Len() int {
return len(d)
}
// Swap is part of sort.Interface.
func (d dataSlice) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
// Less is part of sort.Interface. We use count as the value to sort by
func (d dataSlice) Less(i, j int) bool {
return d[i].count < d[j].count
}
func main() {
m := map[string]*data {
"x": {0, 0},
"y": {2, 9},
"z": {1, 7},
}
s := make(dataSlice, 0, len(m))
for _, d := range m {
s = append(s, d)
}
// We just add 3 to one of our structs
d := m["x"]
d.count += 3
sort.Sort(s)
for _, d := range s {
fmt.Printf("%+v\n", *d)
}
}
Output:
{count:1 size:7}
{count:2 size:9}
{count:3 size:0}
Playground
Edit
Updated the example to use pointers and to include a map so that you can both do lookups and have a slice to sort over.