Recursively index arbitrarily nested slice/array - go

I'm looking for the go equivalent of something like the below python snippet
indexes = [0,4]
nestedArray = [[1,2,3,4,5],[6,7,8]] # could be [][][]string, [][][][]float.. etc
def getNestedIndex(nestedArray, indexes):
curr = nestedArray
while indexes {
curr = nestedArray[indexes.pop(0)]
}
return curr # should be 5
I tried something like this
func nestedArrayIndex(slice []interface{}, indexes []int) interface{} {
if len(indexes) == 1 {
return slice[indexes[0]]
}
var newSlice []interface{}
newSlice = slice[indexes[0]]
return nestedArrayIndex(newSlice, indexes[1:])
}
but two things occur here - []Type != []interface{} and [][]..Type definitely won't work here.
Wondering if there's a way to achieve this in go?

You can do that with reflect. reflect.Value has a method Index for getting the item of the index in the slice underlying the Value.
func nestedArrayIndex(slice interface{}, indexes []int) interface{} {
v := reflect.ValueOf(slice)
for _, i := range indexes {
v = v.Index(i)
}
return v.Interface()
}
Playground: https://play.golang.org/p/Tmdomx18qPg

Related

Add data to slice when use range with go

I wanna append some data when range the slice, like this:
package main
import "fmt"
func main() {
slices := []string{"item-1", "item-2", "item-3"}
for _, item := range slices {
if item == "item-2" {
slices = append(slices, "item-5")
}
fmt.Println(item)
}
}
the code output:
item-1
item-2
item-3
I expect:
item-1
item-2
item-3
item-5
Similar to this syntax in python:
slices = ["item-1", "item-2", "item-3"]
for item in slices[::]:
if item == "item-2":
slices.append("item-5")
print(item)
How it should be implemented in Go?Thanks
i try to search in this website and google, use the Add data to slice when use range with go keyword.
Instead of using range, iterate explicitly with a counter
func main() {
slices := []string{"item-1", "item-2", "item-3"}
for i := 0; i < len(slices); i++ {
item := slices[i]
if item == "item-2" {
slices = append(slices, "item-5")
}
fmt.Println(item)
}
}
Because you re-assign slices in the loop, you need to re-check the len every iteration to see how long it is currently. The built-in range only iterates over the initial value of slices; it doesn't see any updates to the slice definition that happen during iteration.

Check if all array lengths match in Go

I have a function which checks if array lengths are the same:
func checkArrayLengthsMatch(desiredLength int, arrays ...[]any) bool {
for _, array := range arrays {
if len(array) != desiredLength {
return false
}
}
return true
}
But this doesn't work because of the []any.
array1 := []string{"1", "2"}
array2 := []int{1, 2}
checkArrayLengthsMatch(2, array1, array2)
gives the error cannot use array1 (variable of type []string) as type []any in argument to checkArrayLengthsMatch
Is there any way to achieve this functionality without having to write a long if statement? (e.g. if len(array1) != 2 || len(array2) != 2) Generics?
I want to be able to support checking arrays of various types.
Go playground link: https://go.dev/play/p/yKisBIOZktI
If you need to pass different types of arrays to the same function, generics won't work, but reflection will:
func checkArrayLengthsMatch(desiredLength int, arrays ...any) bool {
for _, array := range arrays {
if reflect.ValueOf(array).Len() != desiredLength {
return false
}
}
return true
}
This will also work for maps, channels, and strings.

Go: A function that would consume maps with different types of values

In my code, I need a function that would return an ordered slice of keys from a map.
m1 := make(map[string]string)
m2 := make(map[string]int)
And now I need to call a function passing both types of maps:
keys1 := sortedKeys(m1)
keys2 := sortedKeys(m1)
Problem: I have to write two functions because the function should consume maps of two different types. At the same time, the body of the function will be the same in both cases.
Question: How can I use a single implementation for two maps? Or is there any other way of solving the problem in an elegant way?
My first idea was to use map[string]interface{} as an argument type, but you can't assign neither map[string]string, nor map[string]int to it.
My code:
func sortedKeys(m map[string]string) []string {
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
I would have to repeat the same code but for map[string]int.
You can use interface{} and use reflection for achieving this.
You can write two functions for the same but it is just not scalable, say, you are supporting string and int now but you wish to support int64, float64, bool or struct in the future. Having a common function using map[string]interface{} and using reflection is the way to go.
Suggested Code :
package main
import (
"fmt"
"reflect"
)
func main() {
m1 := make(map[string]string)
m2 := make(map[string]int)
m1["a"] = "b"
m1["b"] = "c"
m2["a"] = 1
m2["b"] = 2
fmt.Println(sortedKeys(m1))
fmt.Println(sortedKeys(m2))
}
// Returns slice of values in the type which is sent to it
func sortedKeys(m interface{}) interface{} {
if m == nil {
return nil
}
if reflect.TypeOf(m).Kind() != reflect.Map {
return nil
}
mapIter := reflect.ValueOf(m).MapRange()
mapVal := reflect.ValueOf(m).Interface()
typ := reflect.TypeOf(mapVal).Elem()
outputSlice := reflect.MakeSlice(reflect.SliceOf(typ), 0, 0)
for mapIter.Next() {
outputSlice = reflect.Append(outputSlice, mapIter.Value())
}
return outputSlice.Interface()
}
Output :
[b c]
[1 2]
https://play.golang.org/p/2fkpydH9idG

Delete element in a slice and returning the removed one and the remaining

I just want a function that having a slice of a struct type "t", returns the returns the element I'm looking for and the remaining, I tried with the partial solution for my problem like pointed out here:
Delete element in a slice
But for a weird reason, it does not work as expected
https://play.golang.org/p/tvJwkF5c_tj
func main() {
var names = []string{"john", "julio", "pepito","carlos"}
fmt.Println(getMe("john", names))
}
func getMe(me string, names []string) (string, []string, bool) {
for i := range names {
if names[i] == me {
return names[i], append(names[:i], names[i+1:]...), true
}
}
return "", nil, false
}
but the result gives me:
julio [julio pepito carlos] true
UPDATE:
https://play.golang.org/p/1xbu01rOiMg
Taking the answer from #Ullaakut
If I do: append(names[:i], names[i+1:]...), it changes the original slice, so this does not work for me, I do not want my slice to change, because I will be using it later on
Simply use the range to get both the value and the index, instead of accessing the value by using the index.
package main
import (
"fmt"
)
func main() {
var names = []string{"john", "julio", "pepito", "carlos"}
name, newNames, _ := getMe("john", names)
fmt.Println("extracted name:\t\t\t\t", name)
fmt.Println("new slice without extracted name:\t", newNames)
fmt.Println("old slice still intact:\t\t\t", names)
}
func getMe(me string, names []string) (string, []string, bool) {
var newSlice []string
for i := 0; i < len(names); i++ {
if names[i] == me {
newSlice = append(newSlice, names[:i]...)
newSlice = append(newSlice, names[i+1:]...)
return names[i], newSlice, true
}
}
return "", nil, false
}
Outputs
extracted name: john
new slice without extracted name: [julio pepito carlos]
old slice still intact: [john julio pepito carlos]
See playground example
Edit after request for a faster version: Using the manual for instead of the range loop is much faster. Since you need to create a new slice without the element, it's necessary to build a new slice within the function, which is always going to take some processing power.

Is there a way to write generic code to find out whether a slice contains specific element in Go?

I want to know is there a generic way to write code to judge whether a slice contains an element, I find it will frequently useful since there is a lot of logic to fist judge whether specific elem is already in a slice and then decide what to do next. But there seemed not a built-in method for that(For God's sake, why?)
I try to use interface{} to do that like:
func sliceContains(slice []interface{}, elem interface{}) bool {
for _, item := range slice {
if item == elem {
return true
}
}
return false
}
I thought interface{} is sort of like Object of Java, but apparently, I was wrong. Should I write this every time meet with a new struct of slice? Isn't there a generic way to do this?
You can do it with reflect, but it will be MUCH SLOWER than a non-generic equivalent function:
func Contains(slice, elem interface{}) bool {
sv := reflect.ValueOf(slice)
// Check that slice is actually a slice/array.
// you might want to return an error here
if sv.Kind() != reflect.Slice && sv.Kind() != reflect.Array {
return false
}
// iterate the slice
for i := 0; i < sv.Len(); i++ {
// compare elem to the current slice element
if elem == sv.Index(i).Interface() {
return true
}
}
// nothing found
return false
}
func main(){
si := []int {3, 4, 5, 10, 11}
ss := []string {"hello", "world", "foo", "bar"}
fmt.Println(Contains(si, 3))
fmt.Println(Contains(si, 100))
fmt.Println(Contains(ss, "hello"))
fmt.Println(Contains(ss, "baz"))
}
How much slower? about x50-x60 slower:
Benchmarking against a non generic function of the form:
func ContainsNonGeneic(slice []int, elem int) bool {
for _, i := range slice {
if i == elem {
return true
}
}
return false
}
I'm getting:
Generic: N=100000, running time: 73.023214ms 730.23214 ns/op
Non Generic: N=100000, running time: 1.315262ms 13.15262 ns/op
You can make it using the reflect package like that:
func In(s, e interface{}) bool {
slice, elem := reflect.ValueOf(s), reflect.ValueOf(e)
for i := 0; i < slice.Len(); i++ {
if reflect.DeepEqual(slice.Index(i).Interface(), elem.Interface()) {
return true
}
}
return false
}
Playground examples: http://play.golang.org/p/TQrmwIk6B4
Alternatively, you can:
define an interface and make your slices implement it
use maps instead of slices
just write a simple for loop
What way to choose depends on the problem you are solving.
I'm not sure what your specific context is, but you'll probably want to use a map to check if something already exists.
package main
import "fmt"
type PublicClassObjectBuilderFactoryStructure struct {
Tee string
Hee string
}
func main() {
// Empty structs occupy zero bytes.
mymap := map[interface{}]struct{}{}
one := PublicClassObjectBuilderFactoryStructure{Tee: "hi", Hee: "hey"}
two := PublicClassObjectBuilderFactoryStructure{Tee: "hola", Hee: "oye"}
three := PublicClassObjectBuilderFactoryStructure{Tee: "hi", Hee: "again"}
mymap[one] = struct{}{}
mymap[two] = struct{}{}
// The underscore is ignoring the value, which is an empty struct.
if _, exists := mymap[one]; exists {
fmt.Println("one exists")
}
if _, exists := mymap[two]; exists {
fmt.Println("two exists")
}
if _, exists := mymap[three]; exists {
fmt.Println("three exists")
}
}
Another advantage of using maps instead of a slice is that there is a built-in delete function for maps. https://play.golang.org/p/dmSyyryyS8
If you want a rather different solution, you might try the code-generator approach offered by tools such as Gen. Gen writes source code for each concrete class you want to hold in a slice, so it supports type-safe slices that let you search for the first match of an element.
(Gen also offers a few other kinds of collection and allows you to write your own.)

Resources