从地图中获取任意键/项

chu*_*333 39 dictionary go

我是golang的新手,现在我想从地图中获取一个任意项目,这样做的惯用方法是什么?我只能想到如下:

func get_some_key(m map[int]int) int {
    for k := range m {
        return k
    }
    return 0
}
Run Code Online (Sandbox Code Playgroud)

我想要的原因是我使用地图来维护一组作业,并使用地图我可以获得待处理作业或删除O(1)中的已完成作业.我想这应该是一个常见的要求,但在go中做这件事并不明显.

提前致谢!

ANi*_*sus 16

是否可以讨论从散列表中获取任意密钥是否是常见要求.其他语言地图实现通常缺少此功能(例如C#中的字典)

但是,您的解决方案可能是最快的解决方案,但您将使用您无法控制的伪随机算法.虽然目前的实现使用了pseduo-random算法,但Go规范并没有给你任何保证它实际上是随机的,只是它不能保证是可预测的:

未指定地图上的迭代顺序,并且不保证从一次迭代到下一次迭代是相同的.

如果您想要更多地控制随机化,您还可以并行地保留地图中包含的更新的值(或键)片段,使用您选择的随机化(math/randcrypto/rand更极端的情况)来获取存储在索引中的值,在切片中随机选择.

  • @chuchao333 那么你的解决方案很好。另一种类似的方式是`var k, v int; 对于 k,v = range m {break }` 如果你想内联执行。为了确保你得到一个值,你可以这样做:`var k, v int; var 好的布尔;对于 k,v = 范围 m { ok = true; 打破}` (2认同)

squ*_*gun 7

干得好。

并发安全且 O(1)

这是添加“随机”方法的映射的包装器。

用法示例:

package main

func main() {
    s := NewRandMap[string, string]()

    s.Add("myKey", "Item1")
    s.Add("myKey2", "Item2")
    s.Add("myKey3", "Item3")

    randomItem, _ := s.Random()

    fmt.Println(randomItem)
}
Run Code Online (Sandbox Code Playgroud)

数据结构:

type RandMap[K comparable, V any] struct {
    m sync.RWMutex

    // Where the objects you care about are stored.
    container map[K]V

    // A slice of the map keys used in the map above. We put them in a slice
    // so that we can get a random key by choosing a random index.
    keys []K

    // We store the index of each key, so that when we remove an item, we can
    // quickly remove it from the slice above.
    sliceKeyIndex map[K]int
}

func NewRandMap[K comparable, V any]() *RandMap[K, V] {
    return &RandMap[K, V]{
        container:     make(map[K]V),
        sliceKeyIndex: make(map[K]int),
    }
}

func (s *RandMap[K, V]) Add(key K, item V) {
    s.m.Lock()
    defer s.m.Unlock()

    // store object in map
    s.container[key] = item

    // add map key to slice of map keys
    s.keys = append(s.keys, key)

    // store the index of the map key
    index := len(s.keys) - 1
    s.sliceKeyIndex[key] = index
}

func (s *RandMap[K, V]) Get(key K) (val V, ok bool) {
    s.m.RLock()
    defer s.m.RUnlock()

    item, ok := s.container[key]
    if !ok {
        return *new(V), false
    }

    return item, true
}

func (s *RandMap[K, V]) Remove(key K) {
    s.m.Lock()
    defer s.m.Unlock()

    s.remove(key)
}

// caller is responsible for locking
func (s *RandMap[K, V]) remove(key K) {
    // get index in key slice for key
    index, exists := s.sliceKeyIndex[key]
    if !exists {
        // item does not exist
        return
    }

    delete(s.sliceKeyIndex, key)

    wasLastIndex := len(s.keys)-1 == index

    // remove key from slice of keys
    s.keys[index] = s.keys[len(s.keys)-1]
    s.keys = s.keys[:len(s.keys)-1]

    // we just swapped the last element to another position.
    // so we need to update it's index (if it was not in last position)
    if !wasLastIndex {
        otherKey := s.keys[index]
        s.sliceKeyIndex[otherKey] = index
    }

    // remove object from map
    delete(s.container, key)
}

func (s *RandMap[K, V]) Random() (val V, ok bool) {
    s.m.RLock()
    defer s.m.RUnlock()

    if len(s.keys) == 0 {
        return *new(V), false
    }

    randomIndex := rand.Intn(len(s.keys))
    key := s.keys[randomIndex]

    item := s.container[key]

    return item, true
}

func (s *RandMap[K, V]) PopRandom() (val V, ok bool) {
    s.m.Lock()
    defer s.m.Unlock()

    if len(s.container) == 0 {
        return *new(V), false
    }

    randomIndex := rand.Intn(len(s.keys))
    key := s.keys[randomIndex]

    item, ok := s.container[key]
    if !ok {
        return *new(V), false
    }

    s.remove(key)

    return item, true
}

func (s *RandMap[K, V]) Len() int {
    s.m.RLock()
    defer s.m.RUnlock()

    return len(s.container)
}
Run Code Online (Sandbox Code Playgroud)


小智 6

这是一个通用的版本,尽管效率可能较低:

    keys := reflect.ValueOf(mapI).MapKeys()
    return keys[rand.Intn(len(keys))].Interface()
Run Code Online (Sandbox Code Playgroud)

https://play.golang.org/p/0uvpJ0diG4e

  • 它的运行速度比Xeoncross建议的方法慢约5倍。(尽管我喜欢这个版本,因为它要简单得多。)我在这里做了一个测试代码:https://play.golang.org/p/L2z5kGJ7WsW (2认同)
  • 这更加可怕!运行时间和内存都是 O(N)! (2认同)

小智 5

这是我发现的一种更快的方法:

在我的测试中,我创建了以下函数

type ItemType interface{}

func getMapItemRandKey(m map[string]ItemType) string {
    return reflect.ValueOf(m).MapKeys()[0].String()
}
Run Code Online (Sandbox Code Playgroud)

每个地图的密钥采用以下格式:

b := new(big.Int)
rbytes := (some random function to generate cryptographically safe random bytes)
b.SetBytes(rbytes)

key := b.String()
m := map[string]ItemType
m[key] = &ItemType{}


Run Code Online (Sandbox Code Playgroud)

作为测试,当我请求所有密钥时,我从我的值中获取第一个密钥,但只有一个 (... MapKeys()[0])。

这是超级快,可以很容易地适应任何类型的地图。