Skip to content

Instantly share code, notes, and snippets.

@flaviocopes
Last active December 25, 2023 21:03
Show Gist options
  • Save flaviocopes/bed52cc2ac407137b3931bc864b4e31b to your computer and use it in GitHub Desktop.
Save flaviocopes/bed52cc2ac407137b3931bc864b4e31b to your computer and use it in GitHub Desktop.
Go: sort a Map by key #golang
import "sort"
ages := map[string]int{
"a": 1,
"c": 3,
"d": 4,
"b": 2,
}
names := make([]string, 0, len(ages))
for name := range ages {
names = append(names, name)
}
sort.Strings(names) //sort by key
for _, name := range names {
//...
}
@kelein
Copy link

kelein commented Nov 26, 2017

Covert a map into a String Array ?

@zmf
Copy link

zmf commented Nov 26, 2017

  • You cannot sort a map.
  • Iteration of maps is by design random.

So, this code for example:

package main
import(
	"fmt"
)
func main() {
	myRecords := make(map[string][]string)
	myRecords["testKey"] = append(myRecords["testKey"], "a value")
	myRecords["testKey"] = append(myRecords["testKey"], "another value")
	myRecords["testKey"] = append(myRecords["testKey"], "just another value")
	myRecords["newKey"] = append(myRecords["newKey"], "and another value")
	myRecords["newKey"] = append(myRecords["newKey"], "and another")
	myRecords["lastKey"] = append(myRecords["lastKey"], "and another")
	myRecords["lastKey"] = append(myRecords["lastKey"], "and the last one")
	myRecords["lastKey"] = append(myRecords["lastKey"], "and the last one")
	for k, v := range myRecords {
		fmt.Printf("k: %s, v: %v\n", k, v)
	}
}

Results to this output (which will also be most probably different on each run):

$ for i in `seq 1 25` ; do go run gomaprange.go | head -1 ; sleep 0.2 ; done
k: testKey, v: [a value another value just another value]
k: newKey, v: [and another value and another]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: newKey, v: [and another value and another]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: newKey, v: [and another value and another]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: lastKey, v: [and another and the last one and the last one]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: newKey, v: [and another value and another]
k: testKey, v: [a value another value just another value]
k: testKey, v: [a value another value just another value]
k: lastKey, v: [and another and the last one and the last one]
k: testKey, v: [a value another value just another value]

So, the only way AFAIK you can "sort" a map, is to keep track of its keys in an array, or a struct which you can then sort, and do loookups on your map by iterating over this array when you need sorted access, like the code below:

package main

import(
	"fmt"
	"sort"
)

func main() {
	myRecords := make(map[int]string)
	myRecords[0] = "a new test"
	myRecords[1] = "that will show"
	myRecords[2] = "a way of"
	myRecords[3] = "keeping order"
	myRecords[4] = "and avoid"
	myRecords[5] = "CHAOS"

	fmt.Printf("always random:\n")
	for k, v := range myRecords {
		fmt.Printf("k: %s, v: %v\n", k, v)
	}

	//now let's create & sort an array with our map keys
	sortedKeys := make([]int, 0, len(myRecords))

	for k := range myRecords {
		sortedKeys = append(sortedKeys, k)
	}
	sort.Ints(sortedKeys)

	fmt.Printf("always sorted:\n")
	for k := range sortedKeys {
		fmt.Printf("k: %s, v: %v\n", k, myRecords[k])
	}

}

HTH

@vincent6767
Copy link

@zmf
I am wondering what's the reason that the iteration of the maps is by design random.

@ernierasta
Copy link

ernierasta commented Jul 17, 2018

@vincent6767 because devs started to depend on map key order in their apps. And it was not deterministic, so it was decided to randomize keys order to clearly communicate, that key order is not guaranteed.
We can workaround it, but it would be great to have native ordered map in golang ...

@mellorn-anz
Copy link

mellorn-anz commented Feb 9, 2022

@ernierasta Python dicts (cf GoLang maps) have preserved insertion order of keys for several years now. And somehow managed to improve the performance of dicts at the same time.

Java has LinkedhashMap. C# you have to dig around, but there are OrderedDictionary classes that preserve insertion order when you iterate over dictionary keys.

Is there something similar for Go?

Extracting the keys, sorting them and iterating over the sorted list of keys seems a long-winded and inefficient way to do things for many applications.

To take a simple example, imagine building a map into a CSV row and preserving field order in each row. Concrete example: here's some code to build a record to send to InfluxDB. The keys and values are inserted into the metas map, then the keys have to be sorted as a second step because in Go, map key order is undefined.

	influxRow := []string{meta.Measure}
	metas := map[string]string{
		"run":          meta.Run,
		"system":       meta.System,
		"phase":        meta.Phase,
		"build":        meta.Build,
		"summary":      strconv.FormatBool(meta.Summary),
		"step":         meta.Step,
		"label":        meta.Label,
		"responseCode": responseCode,
		"success":      strconv.FormatBool(success),
		"user":         meta.User,
		"xrequestid":   meta.XRequestID,
		"errorMessage": meta.ErrorMessage,
	}

	fieldNames := make([]string, 0, len(metas))
	for k := range metas {
		fieldNames = append(fieldNames, k)
	}
	sort.Strings(fieldNames)

	for _, fieldName := range fieldNames {
		value := metas[fieldName]
		if value != "" {
			influxRow = append(influxRow, influxKvPair(fieldName, value))
		}
	}

	influxRow = append(influxRow, " " + influxKvPair("elapsed", fmt.Sprintf("%d", elapsed.Milliseconds())))

	if responseSize > -1 {
		influxRow = append(influxRow, influxKvPair("responseSize", fmt.Sprintf("%d", responseSize)))
	}

	influxRow = append(influxRow, " " + fmt.Sprintf(  "%d", timestamp.Local().UnixNano()))

	messagesChan <- strings.Join(influxRow, ",")

A better way would be to order the initialiser keys alphabetically and be able to rely on range to iterate over the keys in original insertion order. Then you can remove the O(n log n) sort entirely.

@ernierasta
Copy link

ernierasta commented Feb 10, 2022

@mellorn-anz I completely agree with you, I was just saying how it is and why it is like that. But while there are workarounds (like: https://github.com/elliotchance/orderedmap) I would also love to see native ordered map in go.

@wvh
Copy link

wvh commented Dec 2, 2023

@zmf I am wondering what's the reason that the iteration of the maps is by design random.

I'm guessing it has something to do with garbage collection and the hash backing store scaling up or down. Originally, if I remember correctly, insertion order was mostly predictable, but not always, and because it wasn't guaranteed, the Go devs decided to make sure people wouldn't rely on the insertion order being stable.

I think it was changed again fairly recently to make things like testing easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment