How to make array of objects that contains key-values in golang? - go

Let's say, I am iterating some data in go for loop.
for _, job := range orderJobs {}
for each iteration, I want a new object to be added in array and that object should contain the key value pairs.
So the final output should be something like
[
{
"order_id":"123"
"job_name":"JOB1"
}
{
"order_id":"456"
"job_name":"JOB2"
}
]
Should I declare and use go maps in this case ? If yes then how exactly I should declare ?
I tried declaring
Jobs := make(map[string]interface{})
and inserting key value pairs like below inside loop iteration
Jobs["order_id"] = "123"
it's not serving the purpose of creating array of objects.

Declare jobs as a slice:
var jobs []map[string]any
Append values to the slice in the for loop:
jobs = append(jobs, map[string]any{"order_id": "123", "job_name":"JOB1"})

Related

Is there a bug in handling slices with references in Go?

I'm trying to build a new list of structs that contains references to items that exist in another slice. It's easier to understand if you see it, so I've prepared a snippet that you can run.
I have a list (dummylist) of two points (Cartesian coordinates) that I want to parse to build a new list (mylist) with items having some features (in the example, X > 80). I've defined two points: {X:90.0, Y:50.0} and {X:20.0 , Y:30.0}. I expect that mylist will contain {X:90.0, Y:50.0}, instead at the end there is {X:20.0 , Y:30.0}. With some print here and there I can verify that the algorithm is working fine (it enters in the "if" condition in the right case), but, at the end, "mylist" contains the wrong element.
package main
import(
"fmt"
)
func main() {
type point struct {
X float64
Y float64
}
type pointsList []point
type pointContainer struct {
Point *point
}
type pointContainerList []pointContainer
// Prepare a slice with two elements
dummylist := new(pointsList)
*dummylist = append(*dummylist, point{X:90.0, Y:50.0})
*dummylist = append(*dummylist, point{X:20.0 , Y:30.0})
// My empty list
mylist := new(pointContainerList)
fmt.Println(fmt.Sprintf("---- At the beginning, mylist contains %d points", len(*mylist)))
// Filter the initial list to take only elements
for _, pt := range *dummylist {
fmt.Println("\n---- Evaluating point ", pt)
if pt.X > 80 {
fmt.Println("Appending", pt)
*mylist = append(*mylist, pointContainer{Point: &pt})
fmt.Println("Inserted point:", (*mylist)[0].Point, "len = ", len(*mylist))
}
}
// mylist should contain {X:90.0, Y:50.0}, instead...
fmt.Println(fmt.Sprintf("\n---- At the end, mylist contains %d points", len(*mylist)))
fmt.Println("Content of mylist:", (*mylist)[0].Point)
}
Here you can run the code:
https://play.golang.org/p/AvrC3JJBLdT
Some helpful consideration:
I've seen through multiple tests that, at the end, mylist contains the last parsed item in the loop. I think there is a problem with references. It's like if the inserted item in the list (in the first iteration) is dependent on the "pt" of other iterations. Instead, if I use indexes (for i, pt := range *dummylist and (*dummylist)[i]), everything works fine.
Before talking about bugs in Golang... am I missing something?
Yes, you're missing something. On this line:
*mylist = append(*mylist, pointContainer{Point: &pt})
you're putting the address of the loop variable &pt into your structure. As the loop continues, the value of pt changes. (Or to put it another way, &pt will be the same pointer for each iteration of the loop).
From the go language specification:
...
The iteration values are assigned to the respective iteration
variables as in an assignment statement.
The iteration variables may be declared by the "range" clause using a
form of short variable declaration (:=). In this case their types are
set to the types of the respective iteration values and their scope is
the block of the "for" statement; they are re-used in each iteration.
If the iteration variables are declared outside the "for" statement,
after execution their values will be those of the last iteration.
One solution would be to create a new value, but I'm not sure what you're gaining from so many pointers: []point would probably be more effective (and less error-prone) than a pointer to a slice of structs of pointers to points.

Is there a way to delete first element from map?

Can I delete the first element in map? It is possible with slices slice = append(slice, slice[1:]...), but can I do something like this with maps?
Maps being hashtables don't have a specified order, so there's no way to delete keys in a defined order, unless you track keys in a separate slice, in the order you're adding them, something like:
type orderedMap struct {
data map[string]int
keys []string
mu *sync.RWMutex
}
func (o *orderedMap) Shift() (int, error) {
o.mu.Lock()
defer o.mu.Unlock()
if len(o.keys) == 0 {
return 0, ErrMapEmpty
}
i := o.data[o.keys[0]]
delete(o.data, o.keys[0])
o.keys = o.keys[1:]
return i, nil
}
Just to be unequivocal about why you can't really delete the "first" element from a map, let me reference the spec:
A map is an unordered group of elements of one type, called the element type, indexed by a set of unique keys of another type, called the key type. The value of an uninitialized map is nil.
Added the emphasis on the fact that map items are unordered
Using a slice to preserve some notion of the order of keys is, fundamentally, flawed, though. Given operations like this:
foo := map[string]int{
"foo": 1,
"bar": 2,
}
// a bit later:
foo["foo"] = 3
Is the index/key foo now updated, or reassigned? Should it be treated as a new entry, appended to the slice if keys, or is it an in-place update? Things get muddled really quickly. The simple fact of the matter is that the map type doesn't contain an "order" of things, trying to make it have an order quickly devolves in a labour intensive task where you'll end up writing your own type.
As I said earlier: it's a hashtable. Elements within get reshuffled behind the scenes if the hashing algorithm used for the keys produces collisions, for example. This question has the feel of an X-Y problem: why do you need the values in the map to be ordered? Maybe a map simply isn't the right approach for your particular problem.

Why do I get 6 entries in an array created from a map with only 3 entities?

I have a seemingly simple issue with my understanding of map types in Go. If I create a simple map such as
var thisMap = map[string]string {
"8f14e45fceea167a5a36dedd4bea2543": "Charming",
"1679091c5a880faf6fb5e6087eb1b2dc": "Citi",
"e4da3b7fbbce2345d7772b0674a318d5": "Chase",
}
Populate the keys into an array
keys := make([]string, len(supportedCards))
for k := range supportedCards {
keys = append(keys, k)
}
Then try to join these keys into a comma separated value that I can append to any string
fmt.Println(strings.Join(keys,","))
I expect the result to be
8f14e45fceea167a5a36dedd4bea2543,1679091c5a880faf6fb5e6087eb1b2dc,e4da3b7fbbce2345d7772b0674a318d5
But what I really see is
,,,8f14e45fceea167a5a36dedd4bea2543,1679091c5a880faf6fb5e6087eb1b2dc,e4da3b7fbbce2345d7772b0674a318d5
Why does iterating through the map create 6 entries instead of just 3?
https://play.golang.org/p/Ou67K1Kfvsf
With
keys := make([]string, len(supportedCards))
you create a []string with three empty elements. You can assign them using their indicies.
When you append it later, new entries are added at the end, producing your result with a length of 6, where the first three are empty.
In addition to #xarantolus's answer, instead of using indices you can keep your for-range loop unchanged by:
keys := make([]string, 0, len(supportedCards))
The third argument is capacity of the slide. Ref: https://tour.golang.org/moretypes/13

Go error: non-constant array bound

I'm trying to calculate the necessary length for an array in a merge sort implementation I'm writing in go. It looks like this:
func merge(array []int, start, middle, end int) {
leftLength := middle - start + 1
rightLength := end - middle
var left [leftLength]int
var right [rightLength]int
//...
}
I then get this complaint when running go test:
./mergesort.go:6: non-constant array bound leftLength
./mergesort.go:7: non-constant array bound rightLength
I assume go does not enjoy users instantiating an Array's length with a calculated value. It only accepts constants. Should I just give up and use a slice instead? I expect a slice is a dynamic array meaning it's either a linked list or copies into a larger array when it gets full.
You can't instantiate an array like that with a value calculated at runtime. Instead use make to initialize a slice with the desired length. It would look like this;
left := make([]int, leftLength)

Pattern for lookup in Go array

Go has convenient syntax to define array lookup tables:
var myTable = [...]string{
'a': "aaaa",
'b': "bbbb",
'z': "zoro",
}
In some cases (where keys are in known and not too big range) this is more efficient way to make table, than map. However, it is easy to make lookup in map and find if key not in it. But to do lookup by index in this array I have to do:
if index < len(myTable) {
if val := myTable[index]; val != "" {
// here I know index exists in array and val is its value
}
}
Is there simpler / more common pattern or library function to do this?
I don't think there is any special builtin syntax to remove the need for a bounds check here. One option would be to wrap the code in a custom type. For example:
type StringTable []string
func (st StringTable) Get(i int) string {
if i < 0 || i >= len(st) {
return ""
}
return st[i]
}
You can still use the same initialiser syntax with the custom type:
myTable := StringTable{
'a': "aaaa",
'b': "bbbb",
'z': "zoro",
}
fmt.Printf("%#v\n", myTable.Get('a'))
fmt.Printf("%#v\n", myTable.Get(-5))
fmt.Printf("%#v\n", myTable.Get('~')) // greater than 'z'
You can play around with this example here: http://play.golang.org/p/nhti2dVE8B
In some cases (where keys are in known and not too big range) this is more efficient way to make table, than map
Yes. You want to translate your key into an offset in the array. Then you can do the lookup in constant time.
Let's say you know all your keys will be in the set A-Z. So you create an array of 26 entries. When a key comes in, you subtract the ASCII value of "A" to get the index into your table. Boom, constant-time lookups, which will be much faster than a map lookup.
Note that you don't actually store the key anywhere, it's implicit. If you want to print out your table, you'd need to generate all keys (A-Z) yourself.

Resources