Как отсортировать map по порядку, указанному в массиве ключей?

Есть map, а точнее map[string]int

По умолчанию, как я понял, Golang сортирует элементы по ключу в алфавитном порядке. А как отсортировать map по ключам в заданном порядке?

Этот код:

package main

import (
    "fmt"
)

func main() {
    output := make(map[string]int)

    basket := map[string]int{
        "orange": 5, 
        "apple": 7,
        "mango": 3,
        "strawberry": 9,
    }

    //нужный порядок
    keys := []string{
        "strawberry",
        "mango",
        "orange",
    }

    for _, k := range keys {
        output[k] = basket[k]
    }

    fmt.Printf("вывод output : %+v \n", output)
}

выдаст с ключами строки в алфавитном порядке:

mango, orange, strawberry

Ответы (2 шт):

Автор решения: CrazyElf

map - это несортированная коллекция вида ключ:значение, порядок вставки не сохраняется, перебор делается по сортированным ключам (но это не точно). Примерно также ведёт себя обычный словарь в Питоне (до какой-то версии, кажется 3.6) и в других языках программирования.

Чтобы сохранить порядок вставки нужно писать либо свой класс, либо использовать готовый пакет с сохранением порядка вставки orderedmap.

→ Ссылка
Автор решения: Pak Uula

Для печати map стандартная библиотека действительно сортирует по ключам. Это делает функция fmtsort.Sort

Но сам перебор ключей/значений в Go умышленно рандомизирован. Каждый раз, как вы итерируете ключи в мапе, может получаться новая последовательность: https://go.dev/play/p/wgHyoYTGC-t

package main

import "fmt"

func main() {
    basket := map[string]int{
        "orange":     5,
        "apple":      7,
        "mango":      3,
        "strawberry": 9,
        "mellon":     1,
        "pineapple":  4,
        "papaya":     1,
    }

    for i := 0; i < 5; i++ {
        fmt.Println("Попытка №", i)
        for k := range basket {
            fmt.Printf("  ключ: %+v \n", k)
        }
    }
}
Попытка № 0
  ключ: apple
  ключ: mango
  ключ: strawberry
  ключ: mellon
  ключ: pineapple
  ключ: papaya
  ключ: orange
Попытка № 1
  ключ: orange
  ключ: apple
  ключ: mango
  ключ: strawberry
  ключ: mellon
  ключ: pineapple
  ключ: papaya
Попытка № 2
  ключ: strawberry
  ключ: mellon
  ключ: pineapple
  ключ: papaya
  ключ: orange
  ключ: apple
  ключ: mango
.
.
.

Для сортировки мапы нужно создать массив пар ключ-значение и сортировать его: https://go.dev/play/p/8SbeIbCTleJ

package main

import (
    "fmt"
    "slices"
    "strings"
)

type Pair[K any, V any] struct {
    Key   K
    Value V
}

type PairList[K any, V any] []Pair[K, V]

type PairComparator[K any, V any] func(Pair[K, V], Pair[K, V]) int

func SortMap[K comparable, V any](m map[K]V, cf PairComparator[K, V]) PairList[K, V] {
    result := make(PairList[K, V], len(m))
    i := 0
    for k, v := range m {
        result[i] = Pair[K, V]{k, v}
        i += 1
    }
    slices.SortFunc(result, func(a Pair[K, V], b Pair[K, V]) int { return cf(a, b) })
    return result
}

func SortMapKeys[K comparable, V any](m map[K]V, cf PairComparator[K, V]) []K {
    result := make([]K, len(m))
    i := 0
    for k := range m {
        result[i] = k
        i += 1
    }
    slices.SortFunc(result, func(a K, b K) int { return cf(Pair[K, V]{a, m[a]}, Pair[K, V]{b, m[b]}) })
    return result
}

func main() {
    basket := map[string]int{
        "orange":     5,
        "apple":      7,
        "mango":      3,
        "strawberry": 9,
        "mellon":     1,
        "pineapple":  4,
        "papaya":     1,
    }

    sortedBasket := SortMap(basket, basketSorter)
    for _, pair := range sortedBasket {
        fmt.Println("sorted basket: ", pair.Key, pair.Value)
    }
}

func basketSorter(p1, p2 Pair[string, int]) int {
    if p1.Value < p2.Value {
        return -1
    }
    if p1.Value > p2.Value {
        return 1
    }
    return strings.Compare(p1.Key, p2.Key)
}

Результат:

sorted basket:  mellon 1
sorted basket:  papaya 1
sorted basket:  mango 3
sorted basket:  pineapple 4
sorted basket:  orange 5
sorted basket:  apple 7
sorted basket:  strawberry 9
→ Ссылка