This function's objective is very simple. It takes array
and checks if val
is a value inside of this array. It returns whether val
exists and which is the index inside the array that contains val
.
Could this be coded in a better way? How would I implement this to work with any kind of array (the code below works only with arrays of strings)?
package main
import "fmt"
func in_array(val string, array []string) (exists bool, index int) {
exists = false
index = -1;
for i, v := range array {
if val == v {
index = i
exists = true
return
}
}
return
}
func main() {
names := []string{ "Mary", "Anna", "Beth", "Johnny", "Beth" }
fmt.Println( in_array("Jack", names) )
}
Go probably provides a similar function, but this is for study purposes only.
3 Answers 3
Rather than tie yourself to only one type (string), you could use the reflect
package as well as interfaces to make it somewhat type indifferent. The following is my reworking of your code:
package main
import "fmt"
import "reflect"
func in_array(val interface{}, array interface{}) (exists bool, index int) {
exists = false
index = -1
switch reflect.TypeOf(array).Kind() {
case reflect.Slice:
s := reflect.ValueOf(array)
for i := 0; i < s.Len(); i++ {
if reflect.DeepEqual(val, s.Index(i).Interface()) == true {
index = i
exists = true
return
}
}
}
return
}
Note that we now import the reflect
package. We also changed the types of val
and array
to interface{}
so that we may pass any type in. We then use the reflect.Typeof()
to glean the reflection reflect.Type
of the value in the array interface{}
. We then glean the type with Kind()
, and use a case to fall into our inner code if its a slice (can add more cases to extend this).
In our inner code, we get the value of the array
argument, and store it in s
. We then iterate over the length of s
, and compare val
to s
at the index i
declared as an interface
with Interface()
and check for truthiness. If its true, we exit with a true and the index.
Running the main function with both a slice of strings and a slice of integers, as follows, works:
func main() {
names := []string{"Mary", "Anna", "Beth", "Johnny", "Beth"}
fmt.Println(in_array("Anna", names))
fmt.Println(in_array("Jon", names))
ints := []int{1, 4, 3, 2, 6}
fmt.Println(in_array(3, ints))
fmt.Println(in_array(95, ints))
}
The above example gets us:
true 1
false -1
true 2
false -1
EDIT: June 2021 - refactored the above code as follows:
func inArray(val interface{}, array interface{}) (index int) {
values := reflect.ValueOf(array)
if reflect.TypeOf(array).Kind() == reflect.Slice || values.Len() > 0 {
for i := 0; i < values.Len(); i++ {
if reflect.DeepEqual(val, values.Index(i).Interface()) {
return i
}
}
}
return -1
}
with the runner function:
func main() {
name := "Mary Anna"
fmt.Println(inArray("Anna", name))
var empty []string
fmt.Println(inArray("Anna", empty))
names := []string{"Mary", "Anna", "Beth", "Johnny", "Beth"}
fmt.Println(inArray("Anna", names))
fmt.Println(inArray("Jon", names))
ints := []int{1, 4, 3, 2, 6}
fmt.Println(inArray(3, ints))
fmt.Println(inArray(95, ints))
}
-
\$\begingroup\$ Few things to point out. 1. boolean's zero value is false, so you don't need to set exits. 2. you don't need the
== true
part in the check. 3. using reflect for that simple task is an extremely slow overkill way of doing it. \$\endgroup\$OneOfOne– OneOfOne2014年08月18日 00:17:54 +00:00Commented Aug 18, 2014 at 0:17 -
2\$\begingroup\$ Points 1 and 2 are good calls. Point three however ignores OP's question "How would I implement this to work with any kind of array?" - you need reflection for that. \$\endgroup\$jsanc623– jsanc6232014年08月18日 03:16:13 +00:00Commented Aug 18, 2014 at 3:16
-
1\$\begingroup\$ Great answer. I know it's OP's request, but I do think a single return (just returning the index) is enough \$\endgroup\$evilReiko– evilReiko2021年06月16日 13:51:10 +00:00Commented Jun 16, 2021 at 13:51
-
1\$\begingroup\$ Very good point @evilReiko - I've refactored to only return index, as well as to no longer rely on the Switch statement \$\endgroup\$jsanc623– jsanc6232021年06月19日 05:41:40 +00:00Commented Jun 19, 2021 at 5:41
Now with go 1.18 and generics you can have a type-safe version of this:
// Index returns the index of the first occurrence of v in s, or -1 if not present.
func Index[E comparable](s []E, v E) int {
for i, vs := range s {
if v == vs {
return i
}
}
return -1
}
Couple of notes here:
- You can simplify the interface by returning -1 instead of (ok, val) tuple. This will also make sure the code that uses the function is error-prone and does not use the value w/o checking.
- In your case you may actually want to consider using a set (
map[string]struct{}
) to get a O(1) runtime. - nit: naming-wise you do not want to have underscores in the function.
Please note that golang.org/x/exp/slices has this exact function among with lots of useful generic code.
(削除) Using reflection for that simple task is a slow and an inefficient way of doing it, your first approach was fine, you could trim it a little, for example: (削除ここまで)
func in_array(val string, array []string) (ok bool, i int) {
for i = range array {
if ok = array[i] == val; ok {
return
}
}
return
}
Generic version using interfaces:
func in_array(v interface{}, in interface{}) (ok bool, i int) {
val := reflect.Indirect(reflect.ValueOf(in))
switch val.Kind() {
case reflect.Slice, reflect.Array:
for ; i < val.Len(); i++ {
if ok = v == val.Index(i).Interface(); ok {
return
}
}
}
return
}
-
\$\begingroup\$ Advising not to use reflection ignores OP's question "How would I implement this to work with any kind of array?" - you need reflection for that. \$\endgroup\$jsanc623– jsanc6232014年08月18日 03:16:43 +00:00Commented Aug 18, 2014 at 3:16
-
\$\begingroup\$ @jsanc623 I completely missed that. \$\endgroup\$OneOfOne– OneOfOne2014年08月18日 03:25:51 +00:00Commented Aug 18, 2014 at 3:25
-
1\$\begingroup\$ Your use of 'v' and 'in' is rather disconcerting as the next programmer to come along to your code cannot instantly discern what those values are. Code should be self documenting, and as such, variables should be descriptive. \$\endgroup\$jsanc623– jsanc6232014年08月18日 05:17:59 +00:00Commented Aug 18, 2014 at 5:17
ok bool
anderr error
are usually the second parameter. See e.g.val, exists := mymap[key]
. \$\endgroup\$