Function to sort list (array/slice) fields an any struct variable passed as input - sorting

I want to write a function that takes any struct variable as input and sorts the list fields in it. The list fields can be of simple type (int/string) or complex (structs) in which case they must implement some comparable interface which has getValue function returning an int and defines its order to be used for sorting.
Example -
type comparable interface {
getValue() int
}
type innerStruct struct {
x int
y string
}
func (s innerStruct) getValue() int {
return s.x
}
type outerStruct struct {
a []innerStruct
b []int
}
func sortListFields(in interface{}) {
// Implement this
}
func main() {
val := outerStruct{
a: []innerStruct{
{1, "abc"},
{3, "pqr"},
{2, "xyz"},
},
b: []int{9, 7, 8},
}
fmt.Println(sortListFields(val))
// Should print - {[{1 abc} {2 xyz} {3 pqr}] [7 8 9]}
}
I have been trying to implement it using reflection, but haven't been able to figure out how to sort the field, once I have determined its a slice/array.
func sortListFields(in interface{}) {
v := reflect.ValueOf(in)
for i := 0; i < v.NumField(); i++ {
fk := v.Field(i).Type().Kind()
if fk == reflect.Array || fk == reflect.Slice {
fmt.Printf("%v field is a list\n", v.Type().Field(i).Name)
// How to sort this field ???
}
}
}
I have following questions with respect to this.
Firstly (as clear from the post), how to implement sorting for the fields ?
Are there any other simpler (or may be not so simple) ways with/without reflection of achieving the same ?

Related

Is there a nice way to get length of slice elem in map regardless of its concrete type?

For example, I have two maps, namely map[string][]structAand map[string][]int.
I want to iterate the map and print out length of every slice, can I implement this in a single function?
I tried to define an interface and implement it in two kinds of slices, but the compiler just cannot do the type cast.
type Container interface{
Len() int
}
type Slice1 []int
func (s Slice1) Len() int {
return len(s)
}
type Slice2 []StructA
func (s Slice2) Len() int {
return len(s)
}
func iterate(input map[string]Container) {
for key, value := range input {
log.Printf("key:%s, lenght:%d", key, value.Len())
}
}
func main() {
// cannot do the type cast
iterate(map[string]Slice1{})
iterate(map[string]Slice2{})
}
"can I implement this in a single function?"
Yes, but the way you called the function should be changed. Convert your maps to map[string]Container and then call the iterate
func main() {
// cannot do the type cast
map1 := map[string][]int{
`key`: []int{1,2,3},
}
map1Container := make(map[string]Container)
for key, value := range map1 {
map1Container[key] = Slice1(value)
}
map2 := map[string][]StructA{
`key1`: []StructA{{}, {}},
}
map2Container := make(map[string]Container)
for key, value := range map2 {
map2Container[key] = Slice2(value)
}
iterate(map1Container)
iterate(map2Container)
}
Output:
2021/07/06 12:15:25 key:key, lenght:3
2021/07/06 12:15:25 key:key1, lenght:2
iterate function expected map[string]Container type parameter. So you can initiate that with Container type and inside the map with different keys, you can include different Container's implementations.
func main() {
// cannot do the type cast
iterate(map[string]Container{
`ke1`: Slice1([]int{
1, 2, 3,
}),
`ke2`: Slice2([]StructA{
{},
{},
}),
})
}
Output:
2021/07/06 11:57:55 key:ke1, lenght:3
2021/07/06 11:57:55 key:ke2, lenght:2

How to Sort a slice of empty Interface that have a field in common?

I have multiple struct :
type FooStruct struct {
ID int
Field1 int
CommonID int
}
type BarStruct struct {
ID int
Field2 int
CommonID int
}
type FighterStruct struct {
ID int
Field3 int
CommonID int
}
1- They are all held into different slices : sliceOfFooStruct , sliceOfBarStruct , sliceofFighterStruct
2- i'm iterating over each slice and inserting them , unsorted, into a commun var commonSlice []interface{} slice
3- Then i need to sort them by CommonID into that slice and that's where i kind of get stuck.
i'm trying to do :
sort.Slice(commonSlice, func(i, j int) bool { return commonSlice[i].CommonID > commonSlice[j].CommonID })
but i'm getting the error
commonSlice[i].CommonID undefined (type interface {} is interface with no methods)
I've tried to cast a type aswell doing commonSlice[i].CommonID.(int) but it doesn't work either.
Tried something like this too with an anonymous struct and the CommonID field but it didn't work.
I would assume it could work if i directly cast the type of the actual struct being compared but hardcoding the type would defeat the whole purpose of the "commonSlice" .
How is that done ? Should i do things differently ?
Since the element type of your commonSlice is interface{} you can't access any fields of the values as that allows storing any values, even values that are not structs.
One discouraged way would be to use reflection to access the CommonID field, but that's ugly and slow. For reference, here's how it would look like:
all := []interface{}{
FooStruct{11, 22, 31},
BarStruct{21, 32, 33},
FighterStruct{21, 32, 32},
}
sort.Slice(all, func(i, j int) bool {
commonID1 := reflect.ValueOf(all[i]).FieldByName("CommonID").Int()
commonID2 := reflect.ValueOf(all[j]).FieldByName("CommonID").Int()
return commonID1 > commonID2
})
fmt.Println(all)
This outputs (try it on the Go Playground):
[{21 32 33} {21 32 32} {11 22 31}]
But instead create an interface that describes a way to access the CommonID field:
type HasCommonID interface {
GetCommonID() int
}
And make your struts implement this interface:
func (f FooStruct) GetCommonID() int { return f.CommonID }
func (b BarStruct) GetCommonID() int { return b.CommonID }
func (f FighterStruct) GetCommonID() int { return f.CommonID }
And store your values in a slice of this interface:
all := []HasCommonID{
FooStruct{11, 22, 31},
BarStruct{21, 32, 33},
FighterStruct{21, 32, 32},
}
And then you can use GetCommonID() method to access this in the less() function:
sort.Slice(all, func(i, j int) bool {
return all[i].GetCommonID() > all[j].GetCommonID()
})
This will output the same, try it on the Go Playground.
This is much cleaner, faster, extendable.
To avoid repetition, you could "outsource" common fields and methods to a struct and embed that in all your struts:
type Common struct {
CommonID int
}
func (c Common) GetCommonID() int { return c.CommonID }
type FooStruct struct {
ID int
Field1 int
Common
}
type BarStruct struct {
ID int
Field2 int
Common
}
type FighterStruct struct {
ID int
Field3 int
Common
}
Note: GetCommonID() is only defined once, for Common, other types do not need to add it. And then using it:
all := []HasCommonID{
FooStruct{11, 22, Common{31}},
BarStruct{21, 32, Common{33}},
FighterStruct{21, 32, Common{32}},
}
sort.Slice(all, func(i, j int) bool {
return all[i].GetCommonID() > all[j].GetCommonID()
})
Output is the same, try it on the Go Playground.
This also allows you to extend the common field and method list in a singe place (Common) without further repetition.

Use different structs as a value in map golang

Is there any way to create a map into several structs, and then use It?
I have several different structs that implement the same interface and matching input types for each struct.
I want to read data from the different inputs into the structs - without knowing the input type at the compilation time.
type myInput struct {
InputType string
data []bytes
}
// Will get as an input after compeleation
inputs := []myInput{
myInput{InputType: "a", data: []bytes{0x01, 0x02, 0x03}},
myInput{InputType: "b", data: []bytes{0x01, 0x02}},
}
type StructA struct {
A uint16
B uint32
}
func (a StructA) name () {
fmt.Printf("name is: %d %d", a.A, a.B)
}
type StructB struct {
C uint32
}
func (b StructB) name () {
fmt.Printf("name is: %d", b.C)
}
AorB map[string]<???> {
"a": StructA,
"b": StructB,
}
At this point, I don't know what to do. I need to take the correct struct by the input type and initialize the struct using binary.Read.
for _, myInput := range (inputs) {
// ???? :(
myStruct := AtoB[myInput.InputType]{}
reader :=bytes.NewReader(input1)
err := binary.Read(reader, binary.BigEndian, &myStruct)
fmt.Printf(myStruct.name())
}
Thanks!
Define a interface
type Bin interface {
name() string
set([]byte) // maybe returning error
}
You'll be handling Bins only.
type StructA struct { /* your code so far */ }
type StructB struct { /* your code so far */ }
func (a *StructA) set(b []byte) {
a.A = b[0]<<8 + b[1] // get that right, too lazy to code this for you
a.B = b[2]<<24 + b[3]<<16 + ...
}
// same for StructB
So your StructA/B are Bins now.
func makeBin(in myInput) Bin {
var bin Bin
if in.InputType == "a" {
bin = &StructA{}
} else {
bin = &StructB{}
}
bin.set(in.data) // error handling?
return bin
}
If you have more than two types: Use a switch instead if an if or make a tiny type registry (reflect).
First you define an interface for the commonly used func name:
type Namer interface {
name()
}
Then you can create a map to that interface and insert structs:
AorB := map[string] Namer {
"a": StructA{
A: 42,
B: 28,
},
"b": StructB{
C: 12,
},
}
Now you can access all entries:
for _, n := range AorB {
n.name()
}
You can use interface for the same
AorB := map[string]interface{}{
"a": StructA{},
"b": StructB{},
}
When you retrieve value back you can assert into A for type A and B for type B

Append slice to slice of slices

I have data structure:
type PosList []int
type InvertedIndex struct {
Capacity int
Len int
IndexList []PosList
}
I have problem with Add method:
func (ii *InvertedIndex) Add(posList PosList, docId int) {
if ii.Len == ii.Capacity {
newIndexList := make([]PosList, ii.Len, (ii.Capacity+1)*2)
for i := 0; i < ii.Len; i++ {
newIndexList[i] = make([]int, len(ii.IndexList[i]))
copy(newIndexList[i], ii.IndexList[i])
}
ii.IndexList = newIndexList
}
ii.IndexList = ii.IndexList[0 : ii.Len+2]
ii.IndexList[docId] = posList
return
}
Or, i try something like this:
func (ii *InvertedIndex) Add(posList PosList, docId int) {
if ii.Len == ii.Capacity {
newIndexList := make([]PosList, ii.Len, (ii.Capacity+1)*2)
copy(newIndexList, ii.IndexList)
ii.IndexList = newIndexList
}
ii.IndexList = ii.IndexList[0 : ii.Len+2]
ii.IndexList[docId] = posList
return
}
Both of them don't work, may be someone can explain how can i append a slice to structure like this.
You question is confusing. I assume that you are trying to create a typical inverted index. In which case, you probably want to do something like this:
package main
import "fmt"
type DocId int
type Positions []int
type docIndex struct {
docId DocId
positions Positions
}
type InvertedIndex struct {
docIndexes []docIndex
}
func New() *InvertedIndex {
return &InvertedIndex{}
}
func (ii *InvertedIndex) Add(docId DocId, positions Positions) {
for i, di := range (*ii).docIndexes {
if di.docId == docId {
di.positions = append(di.positions, positions...)
(*ii).docIndexes[i] = di
return
}
}
di := docIndex{
docId: docId,
positions: positions,
}
(*ii).docIndexes = append((*ii).docIndexes, di)
}
func main() {
ii := New()
docId := DocId(11)
positions := Positions{42, 7}
ii.Add(docId, positions)
positions = Positions{21, 4}
ii.Add(docId, positions)
docId = DocId(22)
positions = Positions{84, 14}
ii.Add(docId, positions)
fmt.Printf("%+v\n", *ii)
}
Output:
{docIndexes:[{docId:11 positions:[42 7 21 4]} {docId:22 positions:[84 14]}]}
The statement:
di.positions = append(di.positions, positions...)
appends a slice to a slice.
References:
Appending to and copying slices
Arrays, slices (and strings): The mechanics of 'append'
inverted index
I'm not sure I fully understand what you're doing, however something like this should just work fine, replacing the slice with a map :
type PosList []int
type InvertedIndex struct {
Len int
IndexList map[int]PosList
}
func (ii *InvertedIndex) Add(posList PosList, docId int) {
if ii.IndexList == nil {
ii.IndexList = make(map[int]PosList)
}
if _, ok := ii.IndexList[docId]; ok {
ii.IndexList[docId] = append(ii.IndexList[docId], posList...)
} else {
ii.IndexList[docId] = posList
}
ii.Len = len(ii.IndexList)
}

Contains method for a slice

Is there anything similar to a slice.contains(object) method in Go without having to do a search through each element in a slice?
Mostafa has already pointed out that such a method is trivial to write, and mkb gave you a hint to use the binary search from the sort package. But if you are going to do a lot of such contains checks, you might also consider using a map instead.
It's trivial to check if a specific map key exists by using the value, ok := yourmap[key] idiom. Since you aren't interested in the value, you might also create a map[string]struct{} for example. Using an empty struct{} here has the advantage that it doesn't require any additional space and Go's internal map type is optimized for that kind of values. Therefore, map[string] struct{} is a popular choice for sets in the Go world.
No, such method does not exist, but is trivial to write:
func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
You can use a map if that lookup is an important part of your code, but maps have cost too.
Starting with Go 1.18, you can use the slices package – specifically the generic Contains function:
https://pkg.go.dev/golang.org/x/exp/slices#Contains.
go get golang.org/x/exp/slices
import "golang.org/x/exp/slices"
things := []string{"foo", "bar", "baz"}
slices.Contains(things, "foo") // true
Note that since this is outside the stdlib as an experimental package, it is not bound to the Go 1 Compatibility Promise™ and may change before being formally added to the stdlib.
With Go 1.18+ we could use generics.
func Contains[T comparable](s []T, e T) bool {
for _, v := range s {
if v == e {
return true
}
}
return false
}
The sort package provides the building blocks if your slice is sorted or you are willing to sort it.
input := []string{"bird", "apple", "ocean", "fork", "anchor"}
sort.Strings(input)
fmt.Println(contains(input, "apple")) // true
fmt.Println(contains(input, "grow")) // false
...
func contains(s []string, searchterm string) bool {
i := sort.SearchStrings(s, searchterm)
return i < len(s) && s[i] == searchterm
}
SearchString promises to return the index to insert x if x is not present (it could be len(a)), so a check of that reveals whether the string is contained the sorted slice.
Instead of using a slice, map may be a better solution.
simple example:
package main
import "fmt"
func contains(slice []string, item string) bool {
set := make(map[string]struct{}, len(slice))
for _, s := range slice {
set[s] = struct{}{}
}
_, ok := set[item]
return ok
}
func main() {
s := []string{"a", "b"}
s1 := "a"
fmt.Println(contains(s, s1))
}
http://play.golang.org/p/CEG6cu4JTf
If the slice is sorted, there is a binary search implemented in the sort package.
func Contain(target interface{}, list interface{}) (bool, int) {
if reflect.TypeOf(list).Kind() == reflect.Slice || reflect.TypeOf(list).Kind() == reflect.Array {
listvalue := reflect.ValueOf(list)
for i := 0; i < listvalue.Len(); i++ {
if target == listvalue.Index(i).Interface() {
return true, i
}
}
}
if reflect.TypeOf(target).Kind() == reflect.String && reflect.TypeOf(list).Kind() == reflect.String {
return strings.Contains(list.(string), target.(string)), strings.Index(list.(string), target.(string))
}
return false, -1
}
I think map[x]bool is more useful than map[x]struct{}.
Indexing the map for an item that isn't present will return false. so instead of _, ok := m[X], you can just say m[X].
This makes it easy to nest inclusion tests in expressions.
You can use the reflect package to iterate over an interface whose concrete type is a slice:
func HasElem(s interface{}, elem interface{}) bool {
arrV := reflect.ValueOf(s)
if arrV.Kind() == reflect.Slice {
for i := 0; i < arrV.Len(); i++ {
// XXX - panics if slice element points to an unexported struct field
// see https://golang.org/pkg/reflect/#Value.Interface
if arrV.Index(i).Interface() == elem {
return true
}
}
}
return false
}
https://play.golang.org/p/jL5UD7yCNq
Not sure generics are needed here. You just need a contract for your desired behavior. Doing the following is no more than what you would have to do in other languages if you wanted your own objects to behave themselves in collections, by overriding Equals() and GetHashCode() for instance.
type Identifiable interface{
GetIdentity() string
}
func IsIdentical(this Identifiable, that Identifiable) bool{
return (&this == &that) || (this.GetIdentity() == that.GetIdentity())
}
func contains(s []Identifiable, e Identifiable) bool {
for _, a := range s {
if IsIdentical(a,e) {
return true
}
}
return false
}
If it is not feasable to use a map for finding items based on a key, you can consider the goderive tool. Goderive generates a type specific implementation of a contains method, making your code both readable and efficient.
Example;
type Foo struct {
Field1 string
Field2 int
}
func Test(m Foo) bool {
var allItems []Foo
return deriveContainsFoo(allItems, m)
}
To generate the deriveContainsFoo method:
Install goderive with go get -u github.com/awalterschulze/goderive
Run goderive ./... in your workspace folder
This method will be generated for deriveContains:
func deriveContainsFoo(list []Foo, item Foo) bool {
for _, v := range list {
if v == item {
return true
}
}
return false
}
Goderive has support for quite some other useful helper methods to apply a functional programming style in go.
The go style:
func Contains(n int, match func(i int) bool) bool {
for i := 0; i < n; i++ {
if match(i) {
return true
}
}
return false
}
s := []string{"a", "b", "c", "o"}
// test if s contains "o"
ok := Contains(len(s), func(i int) bool {
return s[i] == "o"
})
If you have a byte slice, you can use bytes package:
package main
import "bytes"
func contains(b []byte, sub byte) bool {
return bytes.Contains(b, []byte{sub})
}
func main() {
b := contains([]byte{10, 11, 12, 13, 14}, 13)
println(b)
}
Or suffixarray package:
package main
import "index/suffixarray"
func contains(b []byte, sub byte) bool {
return suffixarray.New(b).Lookup([]byte{sub}, 1) != nil
}
func main() {
b := contains([]byte{10, 11, 12, 13, 14}, 13)
println(b)
}
If you have an int slice, you can use intsets package:
package main
import "golang.org/x/tools/container/intsets"
func main() {
var s intsets.Sparse
for n := 10; n < 20; n++ {
s.Insert(n)
}
b := s.Has(16)
println(b)
}
https://golang.org/pkg/bytes
https://golang.org/pkg/index/suffixarray
https://pkg.go.dev/golang.org/x/tools/container/intsets
I created the following Contains function using reflect package.
This function can be used for various types like int32 or struct etc.
// Contains returns true if an element is present in a slice
func Contains(list interface{}, elem interface{}) bool {
listV := reflect.ValueOf(list)
if listV.Kind() == reflect.Slice {
for i := 0; i < listV.Len(); i++ {
item := listV.Index(i).Interface()
target := reflect.ValueOf(elem).Convert(reflect.TypeOf(item)).Interface()
if ok := reflect.DeepEqual(item, target); ok {
return true
}
}
}
return false
}
Usage of contains function is below
// slice of int32
containsInt32 := Contains([]int32{1, 2, 3, 4, 5}, 3)
fmt.Println("contains int32:", containsInt32)
// slice of float64
containsFloat64 := Contains([]float64{1.1, 2.2, 3.3, 4.4, 5.5}, 4.4)
fmt.Println("contains float64:", containsFloat64)
// slice of struct
type item struct {
ID string
Name string
}
list := []item{
item{
ID: "1",
Name: "test1",
},
item{
ID: "2",
Name: "test2",
},
item{
ID: "3",
Name: "test3",
},
}
target := item{
ID: "2",
Name: "test2",
}
containsStruct := Contains(list, target)
fmt.Println("contains struct:", containsStruct)
// Output:
// contains int32: true
// contains float64: true
// contains struct: true
Please see here for more details:
https://github.com/glassonion1/xgo/blob/main/contains.go
There are several packages that can help, but this one seems promising:
https://github.com/wesovilabs/koazee
var numbers = []int{1, 5, 4, 3, 2, 7, 1, 8, 2, 3}
contains, _ := stream.Contains(7)
fmt.Printf("stream.Contains(7): %v\n", contains)
It might be considered a bit 'hacky' but depending the size and contents of the slice, you can join the slice together and do a string search.
For example you have a slice containing single word values (e.g. "yes", "no", "maybe"). These results are appended to a slice. If you want to check if this slice contains any "maybe" results, you may use
exSlice := ["yes", "no", "yes", "maybe"]
if strings.Contains(strings.Join(exSlice, ","), "maybe") {
fmt.Println("We have a maybe!")
}
How suitable this is really depends on the size of the slice and length of its members. There may be performance or suitability issues for large slices or long values, but for smaller slices of finite size and simple values it is a valid one-liner to achieve the desired result.

Resources