How to search for an element in a golang slice

GoStructSlice

Go Problem Overview


I have a slice of structs.

type Config struct {
    Key string
    Value string
}

// I form a slice of the above struct
var myconfig []Config 

// unmarshal a response body into the above slice
if err := json.Unmarshal(respbody, &myconfig); err != nil {
	panic(err)
}

fmt.Println(config)

Here is the output of this:

[{key1 test} {web/key1 test2}]

How can I search this array to get the element where key="key1"?

Go Solutions


Solution 1 - Go

Starting with Go 1.18 which adds generics support, there's a golang.org/x/exp/slices package which contains a generic "find" function named slices.IndexFunc():

> func IndexFunc[E any](s []E, f func(E) bool) int > IndexFunc returns the first index i satisfying f(s[i]), or -1 if none do.

Using that:

idx := slices.IndexFunc(myconfig, func(c Config) bool { return c.Key == "key1" })

Try it on the Go Playground.

Prior to Go 1.18 and for a faster alternative, read on:

With a simple for loop:

for _, v := range myconfig {
	if v.Key == "key1" {
		// Found!
	}
}

Note that since element type of the slice is a struct (not a pointer), this may be inefficient if the struct type is "big" as the loop will copy each visited element into the loop variable.

It would be faster to use a range loop just on the index, this avoids copying the elements:

for i := range myconfig {
	if myconfig[i].Key == "key1" {
		// Found!
	}
}

Notes:

It depends on your case whether multiple configs may exist with the same key, but if not, you should break out of the loop if a match is found (to avoid searching for others).

for i := range myconfig {
	if myconfig[i].Key == "key1" {
		// Found!
		break
	}
}

Also if this is a frequent operation, you should consider building a map from it which you can simply index, e.g.

// Build a config map:
confMap := map[string]string{}
for _, v := range myconfig {
	confMap[v.Key] = v.Value
}

// And then to find values by key:
if v, ok := confMap["key1"]; ok {
	// Found
}

Solution 2 - Go

You can use sort.Slice() plus sort.Search()

type Person struct {
	Name string
}

func main() {
	crowd := []Person{{"Zoey"}, {"Anna"}, {"Benni"}, {"Chris"}}

	sort.Slice(crowd, func(i, j int) bool {
		return crowd[i].Name <= crowd[j].Name
	})

	needle := "Benni"
	idx := sort.Search(len(crowd), func(i int) bool {
		return string(crowd[i].Name) >= needle
	})

	if idx < len(crowd) && crowd[idx].Name == needle {
		fmt.Println("Found:", idx, crowd[idx])
	} else {
		fmt.Println("Found noting: ", idx)
	}
}

See: https://play.golang.org/p/47OPrjKb0g_c

Solution 3 - Go

You can save the struct into a map by matching the struct Key and Value components to their fictive key and value parts on the map:

mapConfig := map[string]string{}
for _, v := range myconfig {
   mapConfig[v.Key] = v.Value
}

Then using the golang comma ok idiom you can test for the key presence:

if v, ok := mapConfig["key1"]; ok {
    fmt.Printf("%s exists", v)
}   

Solution 4 - Go

There is no library function for that. You have to code by your own.

for _, value := range myconfig {
    if value.Key == "key1" {
        // logic
    }
}

Working code: https://play.golang.org/p/IJIhYWROP_

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	type Config struct {
		Key   string
		Value string
	}

	var respbody = []byte(`[
		{"Key":"Key1", "Value":"Value1"},
		{"Key":"Key2", "Value":"Value2"}
	]`)

	var myconfig []Config

	err := json.Unmarshal(respbody, &myconfig)
	if err != nil {
		fmt.Println("error:", err)
	}

	fmt.Printf("%+v\n", myconfig)

	for _, v := range myconfig {
		if v.Key == "Key1" {
			fmt.Println("Value: ", v.Value)
		}
	}

}

Solution 5 - Go

As other guys commented before you can write your own procedure with anonymous function to solve this issue.

I used two ways to solve it:

func Find(slice interface{}, f func(value interface{}) bool) int {
	s := reflect.ValueOf(slice)
	if s.Kind() == reflect.Slice {
		for index := 0; index < s.Len(); index++ {
			if f(s.Index(index).Interface()) {
				return index
			}
		}
	}
	return -1
}

Uses example:

type UserInfo struct {
	UserId      	int
}

func main() {
	var (
		destinationList []UserInfo
		userId		int = 123
	)
	
	destinationList = append(destinationList, UserInfo { 
		UserId      	: 23,
	}) 
	destinationList = append(destinationList, UserInfo { 
		UserId      	: 12,
	}) 
	
	idx := Find(destinationList, func(value interface{}) bool {
		return value.(UserInfo).UserId == userId
	})
	
	if idx < 0 {
		fmt.Println("not found")
	} else {
		fmt.Println(idx)	
	}
}

Second method with less computational cost:

func Search(length int, f func(index int) bool) int {
	for index := 0; index < length; index++ {
		if f(index) {
			return index
		}
	}
	return -1
}

Uses example:

type UserInfo struct {
	UserId      	int
}

func main() {
	var (
		destinationList []UserInfo
		userId		int = 123
	)
	
	destinationList = append(destinationList, UserInfo { 
		UserId      	: 23,
	}) 
	destinationList = append(destinationList, UserInfo { 
		UserId      	: 123,
	}) 
	
	idx := Search(len(destinationList), func(index int) bool {
		return destinationList[index].UserId == userId
	})
	
	if  idx < 0 {
		fmt.Println("not found")
	} else {
		fmt.Println(idx)	
	}
}

Solution 6 - Go

Here is a simple function based on @Tarion's answer.

func findProgram (programs []Program, id uint) (Program, error) {
	sort.Slice(programs, func(i, j int) bool {
		return programs[i].ID <= programs[j].ID
	})

	idx := sort.Search(len(programs), func(i int) bool {
		return programs[i].ID >= id
	})

	if idx < len(programs) && programs[idx].ID == id {
		return programs[idx], nil
	} else {
		return Program{}, fmt.Errorf("program not found")
	}
}

Solution 7 - Go

If anyone is coming from Java or C# like me this is what I ended up doing:

type Person struct {
	Name string
	Age  int
}
// create slice of people
var people []Person = []Person{
	{"Tono", 33},
	{"Regina", 25},
	{"Bob", 40},
}

// find person where its Name equals to Bob <------------------
bob := FirstOrDefault(people, func(p *Person) bool { return p.Name == "Bob" })

if bob != nil {
	fmt.Printf("Found bob: %v \n", *bob)
} else {
	fmt.Println("could not find bob")
}

peopleOlderThan30 := Where(people, func(p *Person) bool { return p.Age > 30 })

fmt.Println("People older than 30 are:")
for _, element := range peopleOlderThan30 {
	fmt.Println(*element)
}

I was able to run that code with the help of these functions:

func FirstOrDefault[T any](slice []T, filter func(*T) bool) (element *T) {

	for i := 0; i < len(slice); i++ {
		if filter(&slice[i]) {
			return &slice[i]
		}
	}

	return nil
}

func Where[T any](slice []T, filter func(*T) bool) []*T {

	var ret []*T = make([]*T, 0)

	for i := 0; i < len(slice); i++ {
		if filter(&slice[i]) {
			ret = append(ret, &slice[i])
		}
	}

	return ret
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncodecView Question on Stackoverflow
Solution 1 - GoiczaView Answer on Stackoverflow
Solution 2 - GoTarionView Answer on Stackoverflow
Solution 3 - GoEndre SimoView Answer on Stackoverflow
Solution 4 - GoPravin MishraView Answer on Stackoverflow
Solution 5 - GoomottoView Answer on Stackoverflow
Solution 6 - GoNate BunneyView Answer on Stackoverflow
Solution 7 - GoTono NamView Answer on Stackoverflow