Сортувати значення карти Go за клавішами


94

Під час ітерації через повернуту карту в коді, повернену функцією теми, клавіші відображаються не в порядку.

Як я можу привести ключі в порядок / відсортувати карту так, щоб ключі були в порядку, а значення відповідали?

Ось код .


Відповіді:


157

Перейти до блогу: Go карти в дії має чудове пояснення.

При ітерації по карті з циклом діапазону порядок ітерацій не вказується і не гарантується однаковість від однієї ітерації до наступної. Оскільки Go 1 час виконання рандомізує порядок ітерацій карти, оскільки програмісти покладалися на стабільний порядок ітерацій попередньої реалізації. Якщо вам потрібен стабільний порядок ітерацій, ви повинні підтримувати окрему структуру даних, яка визначає цей порядок.

Ось моя змінена версія прикладу коду: http://play.golang.org/p/dvqcGPYy3-

package main

import (
    "fmt"
    "sort"
)

func main() {
    // To create a map as input
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    // To store the keys in slice in sorted order
    keys := make([]int, len(m))
    i := 0
    for k := range m {
        keys[i] = k
        i++
    }
    sort.Ints(keys)

    // To perform the opertion you want
    for _, k := range keys {
        fmt.Println("Key:", k, "Value:", m[k])
    }
}

Вихід:

Key: 0 Value: b
Key: 1 Value: a
Key: 2 Value: c

38
Це можна покращити, keys := make([]int, len(m))а потім вставити за індексом keys[i] = kзамістьappend
jpillora

18

Відповідно до специфікації Go , порядок ітерацій на карті не визначений і може різнитися залежно від запуску програми. На практиці він не тільки не визначений, він насправді навмисно рандомізований. Це тому, що раніше це було передбачувано, і розробники мови Go не хотіли, щоб люди покладались на невизначену поведінку, тому вони навмисно рандомізували це так, що покладатися на цю поведінку було неможливо.

Тоді вам потрібно буде втягнути клавіші в зріз, відсортувати їх, а потім перенести по зрізу так:

var m map[keyType]valueType
keys := sliceOfKeys(m) // you'll have to implement this
for _, k := range keys {
    v := m[k]
    // k is the key and v is the value; do your computation here
}

зрізи очікування - це частини масиву. Як зробити фрагмент лише ключів на карті?
gramme.ninja

1
У Go слово "зріз" відноситься до структури даних, яка по суті є аналогом масивів Java. Це просто питання термінології. Що стосується отримання ключів, то вам слід чітко прокласти
joshlf

Гаразд Дякую, що навчили мене. Зараз він друкує все навіть тоді, як не дивно. play.golang.org/p/K2y3m4Zzqd Як я можу змусити його чергуватись, щоб він був у порядку?
gramme.ninja

1
Вам потрібно буде відсортувати отриманий фрагмент (або, як варіант, відсортувати його у mapKeys перед поверненням). Ви захочете перевірити пакет сортування .
joshlf

14

Всі відповіді тут містять стару поведінку карт. У Go 1.12+ ви можете просто надрукувати значення карти, і воно буде автоматично відсортовано за ключем. Це було додано, оскільки дозволяє легко перевіряти значення карт.

func main() {
    m := map[int]int{3: 5, 2: 4, 1: 3}
    fmt.Println(m)

    // In Go 1.12+
    // Output: map[1:3 2:4 3:5]

    // Before Go 1.12 (the order was undefined)
    // map[3:5 2:4 1:3]
}

Карти тепер друкуються в порядку сортування ключів, щоб полегшити тестування. Правила впорядкування:

  • За необхідності, нуль порівняно з низьким
  • ints, floats та strings упорядкувати за <
  • NaN порівнює менше, ніж не-NaN
  • bool порівнює false перед true
  • Комплекс порівнює реальні, то уявні
  • Покажчики порівнюють за адресою машини
  • Порівняння значень каналів за адресою машини
  • Структури по черзі порівнюють кожне поле
  • Масиви порівнюють кожен елемент по черзі
  • Значення інтерфейсу порівнюються спочатку за допомогою відображення. Тип, що описує конкретний тип, а потім за конкретним значенням, як описано в попередніх правилах.

Під час друку карт раніше невідбивні значення ключів, такі як NaN, раніше відображалися як <nil>. Станом на цей випуск друкуються правильні значення.

Детальніше читайте тут .


8
Здається, це стосується лише пакету fmt та друку. Питання задає питання, як сортувати карту, а не як друкувати відсортовану карту?
Тім

1
Він поділився посиланням на дитячий майданчик. Там він просто друкує карту.
Inanc Gumus

2

Якщо, як і я, ви виявите, що хочете, по суті, один і той же код сортування більш ніж в одному місці або просто хочете зменшити складність коду, ви можете абстрагувати саму сортування до окремої функції, якій ви передаєте функцію, яка робить фактичну роботу, яку ви хочете (яка, звичайно, буде різною на кожному сайті виклику).

Враховуючи карту з типом ключа Kта типом значення V, представленими як <K>і <V>нижче, загальна функція сортування може виглядати приблизно так, як цей шаблон Go-code (який Go версія 1 не підтримує як є):

/* Go apparently doesn't support/allow 'interface{}' as the value (or
/* key) of a map such that any arbitrary type can be substituted at
/* run time, so several of these nearly-identical functions might be
/* needed for different key/value type combinations. */
func sortedMap<K><T>(m map[<K>]<V>, f func(k <K>, v <V>)) {
    var keys []<K>
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)  # or sort.Ints(keys), sort.Sort(...), etc., per <K>
    for _, k := range keys {
        v := m[k]
        f(k, v)
    }
}

Потім зателефонуйте йому з картою введення та функцією (беручи (k <K>, v <V>) за аргументи введення), яка викликається над елементами карти в порядку сортування ключів.

Отже, версія коду у відповіді, опублікованій Mingu, може виглядати так:

package main

import (
    "fmt"
    "sort"
)

func sortedMapIntString(m map[int]string, f func(k int, v string)) {
    var keys []int
    for k, _ := range m {
        keys = append(keys, k)
    }
    sort.Ints(keys)
    for _, k := range keys {
        f(k, m[k])
    }
}

func main() {
    // Create a map for processing
    m := make(map[int]string)
    m[1] = "a"
    m[2] = "c"
    m[0] = "b"

    sortedMapIntString(m,
        func(k int, v string) { fmt.Println("Key:", k, "Value:", v) })
}

sortedMapIntString()Функція може бути повторно використана для будь-якогоmap[int]string (припускаючи , що той же порядок сортування по бажанню), зберігаючи кожне використання до двох рядків коду.

Мінуси включають:

  • Це важче читати людям, які не звикли використовувати функції як першокласні
  • Це може бути повільніше (я не робив порівняння продуктивності)

Інші мови мають різні рішення:

  • Якщо використання <K>і<V> (для позначення типів ключа та значення) виглядає дещо звичним, цей шаблон коду не страшно відрізняється від шаблонів С ++.
  • Clojure та інші мови підтримують відсортовані карти як основні типи даних.
  • Хоча я не знаю жодного способу, як Go робить rangeпершокласний тип таким, що його можна було б замінити користувацьким ordered-range(замість rangeоригінального коду), я думаю, що деякі інші мови надають ітератори, які є досить потужними, щоб виконати те саме річ.

2
Для початківців, можливо, варто зазначити, що синтаксис <K>, <V> не підтримується в Go.
justinhj

2

У відповідь на Джеймса Крейга Берлі відповідь . Для того, щоб зробити чистий і багаторазовий дизайн, можна вибрати більш об’єктно-орієнтований підхід. Таким чином методи можна безпечно прив'язати до типів зазначеної карти. Для мене такий підхід здається чистішим та організованішим.

Приклад:

package main

import (
    "fmt"
    "sort"
)

type myIntMap map[int]string

func (m myIntMap) sort() (index []int) {
    for k, _ := range m {
        index = append(index, k)
    }
    sort.Ints(index)
    return
}

func main() {
    m := myIntMap{
        1:  "one",
        11: "eleven",
        3:  "three",
    }
    for _, k := range m.sort() {
        fmt.Println(m[k])
    }
}

Приклад розширеного дитячого майданчика з декількома типами карт.

Важлива примітка

У всіх випадках карта та відсортований фрагмент роз’єднуються з моменту закінчення forциклу над картою range. Це означає, що якщо карта змінюється після логіки сортування, але перед тим, як використовувати її, ви можете потрапити в неприємності. (Не обробляти потоки / переходити в безпечний режим). Якщо відбувається зміна паралельного доступу до запису Map, вам потрібно буде використовувати мьютекс навколо записів та відсортованого forциклу.

mutex.Lock()
for _, k := range m.sort() {
    fmt.Println(m[k])
}
mutex.Unlock()

0

Це надає вам приклад коду на сортувальній карті. В основному це те, що вони пропонують:

var keys []int
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark1-8      2863149           374 ns/op         152 B/op          5 allocs/op

і ось що я б запропонував використовувати замість цього :

keys := make([]int, 0, len(myMap))
for k := range myMap {
    keys = append(keys, k)
}
sort.Ints(keys)

// Benchmark2-8      5320446           230 ns/op          80 B/op          2 allocs/op

Повний код можна знайти на цьому майданчику Go .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.