Update a slice in a struct passed to a function - go

Need help in understanding how to update a slice that is contained in a struct and passed to a function.
Function addBookToShelfInLibrary(l *library, shelfid int, b book) - takes as input a library, tries to add to book to the shelf with the id = shelfid (passed as param). The function appends to the books array and also assigns it to books array. What am I missing?
At the end of code run, I expect the books to contain two books, "harrypotter", "bible" but I see only one, i.e. harrypotter. Also, I am passing a pointer to the library but I don't think that matters in this case.
playground code:- https://play.golang.org/p/JrjtLSj-jHI
func main() {
lib := library{
shelves: []shelf{
{
id: 1,
books: []book{
{name: "harrypotter"},
},
},
},
}
addBookToShelfInLibrary(&lib, 1, book{name: "bible"})
fmt.Printf("%v", lib)
}
type library struct {
shelves []shelf
}
type shelf struct {
id int
books []book
}
type book struct {
name string
}
func addBookToShelfInLibrary(l *library, shelfid int, b book) {
for _, s := range (*l).shelves {
if s.id == shelfid {
//found shelf, add book
s.books = append(s.books, b)
}
}
}
Thanks for your answer / explanation in advance.

In this statement:
s := range (*l).shelves
the variable s is a copy of the slice element. The later call to append modifies this copy, the not slice element. Change the code to modify the slice element:
func addBookToShelfInLibrary(l *library, shelfid int, b book) {
for i := range l.shelves {
s := &l.shelves[i]
if s.id == shelfid {
//found shelf, add book
s.books = append(s.books, b)
}
}
}
Another approach is to use a pointer to a shelf:
type library struct {
shelves []*shelf
}
lib := library{
shelves: []*shelf{
{
...
All other code remains the same. Run it on the playground.

Related

Appending to pointer slice

Go is my first programming language and I am trying to learn about pointers by writing a program that organizes information based on taxonomies. I'm having some trouble understanding how to append to a pointer slice.
type List struct {
Taxonomies []Taxonomy
}
func (l *List) Add(t Taxonomy) {
var exists bool
var existing *Taxonomy
for _, taxonomy := range l.Taxonomies {
if taxonomy.Name == t.Name {
exists = true
existing = &taxonomy
}
}
if exists {
for _, term := range t.Data {
termExists := false
for _, existingTerm := range existing.Data {
if existingTerm.Name == term.Name {
termExists = true
break
}
}
if termExists {
continue
}
(*existing).Data = append((*existing).Data, term)
}
} else {
l.Taxonomies = append(l.Taxonomies, t)
}
}
type Taxonomy struct {
Name string
Data []Term
}
type Term struct {
Name, Link string
}
I think the problem is toward the bottom, this line:
(*existing).Data = append((*existing).Data, term)
By following the code in a debugger, I can see that the taxonomy stored in the "existing" variable is being updated when the append occurs, but the data is not updated in the actual List.
Can anyone tell me where I am going wrong?
l.Taxonomies is a []Taxonomy, so the taxonomy value is going to be a copy of the element, and changes to that copy will not be reflected in the original List value.
You can iterate using the index to avoid copying the value
for i := range l.Taxonomies {
if l.Taxonomies[i].Name == t.Name {
exists = true
existing = &l.Taxonomies[i]
}
}
However that still leaves the possibility of copying the data passed to methods like Append. Instead it's probably better to use pointers throughout:
type List struct {
Taxonomies []*Taxonomy
}
func (l *List) Add(t *Taxonomy) {
...

Reduce a large struct into a small one?

Say I make a call to an api, which returns a slice of structs, each with a load of methods and fields, but I only want to use one field per each element of the returned values. How can I do this?
For instance, I call out to an API, and it returns a slice of x elements, each which has 4 values and 13method, but I only want 1 value and 0 methods (the slice of fetus structs). How can I marshall this into my own struct? eg:
func GETApi() []fetus {
//doGet() returns a slice of persons, which are described below
a := doGet() // this gets many detailed persons as a slice, but I just want them as a slice of fetus
/*
type person struct {
id:
age:
height:
width:
}
func (a *person) GetHeight() int { ... }
func (a *person) GetWidth() int { ... }
func (a *person) GetLaught() int { ... }
// I want to return a slice of these ([]fetus)
type fetus struct {
id:
}
var f fetus
f := a // how can I condense said slice of persons into a slice of fetus
return f
*/
Perhaps something like this?
package main
type Person struct {
Id string
Name string
Age string
LotsOfOtherFields string
}
type Fetus struct {
Id string
}
func main() {
persons := []Person{
{Id: "a", Name: "John"},
{Id: "b", Name: "Steve"},
{Id: "c", Name: "Fred"},
}
fetuses := make([]Fetus, len(persons))
for i, p := range persons {
// Create a new fetus struct and pluck the ID from the person struct
fetuses[i] = Fetus{Id: p.Id}
}
}

How to use pointers or indexing on a range to resolve rangeValCopy gocritic message

I'm getting the following output when running https://golangci-lint.run/:
rangeValCopy: each iteration copies 128 bytes (consider pointers or indexing) (gocritic)
for _, v := range products {
Here is a cut down version of the code I am running:
package main
import (
"fmt"
"encoding/json"
)
type Application struct {
ProductData []ProductDatum
}
type ProductDatum struct {
Name string
ProductBrand string
ProductType string
}
type Item struct {
ProductBrand string
ProductName string
ProductType string
}
func main() {
appl := Application{
ProductData: []ProductDatum{
{
Name: "Baz",
ProductBrand: "Foo",
ProductType: "Bar",
},
},
}
products := appl.ProductData
var orderLinesItem []Item
for _, v := range products {
item := []Item{
{
ProductBrand: v.ProductBrand,
ProductName: v.Name,
ProductType: v.ProductType,
},
}
orderLinesItem = append(orderLinesItem, item...)
}
body, _ := json.Marshal(orderLinesItem)
fmt.Println(string(body))
}
Here it is in go playground.
What does this output mean and how can I do what it's asking? I tried to use a pointer on each item but that didn't seem to make a difference.
What the linter is trying to tell you is that, by using range the way you are using it, each time you get a new element v it is not returning an element directly from the collection, rather it's a new copy of that element. The linter suggest two approaches:
Either change the slice to be a slice of pointers to struct, that way each iteration of the for loop will get a reference to an element instead of a full struct copy
var products []*ProductDatum
//fill products slice
var orderLinesItem []Item
for _, v := range products{
//here v is a pointer instead of a full copy of a struct.
//Go dereferences the pointer automatically therefore you don't have to use *v
item := []Item{
{
ProductBrand: v.ProductBrand,
ProductName: v.Name,
ProductType: v.ProductType,
},
}
}
The other suggestion from the linter is using the index value that the range returns on each iteration
for i := range products{
item := []Item{
{
//access elements by index directly
ProductBrand: products[i].ProductBrand,
ProductName: products[i].Name,
ProductType: products[i].ProductType,
},
}
}

get slice of specified field from slice of structs

I have a slice of structs that contains 10 cars like:
type struct car {
engine
window
wheel
}
so the slice cars contains 10 car struct.
I would like to know if a function exist such as:
var engines string[] = cars.Getfield("engine") // engines will contain 10 engines names
There's no library function for this.
You can implement manually using reflect package
Example:
type Cars []Car
func (cars Cars) getFieldString(field string) []string {
var data []string
for _, car := range cars {
r := reflect.ValueOf(car)
f := reflect.Indirect(r).FieldByName(field)
data = append(data, f.String())
}
return data
}
Code in Playground here
There's no generics in Go (not until 2.0 at least), so there's not a lot of helper functions out there.
If you need to use this function often, you can implement it as a method for type engines.
Answering #RodolfoAP question under #Joe-Akanesuvan answer (not enough rep to post as a comment):
Even though generics are a part of Go now, there're no functional programming libs in std, I used 1st one I found on awesome-go, so this is probably isn't production-ready code, but that's generally what it would look like:
package main
import (
"fmt"
fp "github.com/repeale/fp-go"
)
type data struct {
field string
anotherField int
}
func main() {
fmt.Printf(
"result: %+v",
fp.Map(func(d data) string { return d.field })(
[]data{
{
field: "apple",
anotherField: 1,
},
{
field: "orange",
anotherField: 2,
},
{
field: "banana",
anotherField: 3,
},
},
),
)
}
Code output:
result: [apple orange banana]
Code in playground here

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