【问题标题】:Get an arbitrary key/item from a map从地图中获取任意键/项目
【发布时间】:2014-05-05 22:13:04
【问题描述】:

我是 Go 新手,现在我想从地图中获取任意项目;这样做的惯用方法是什么?我只能想到这样的事情:

func get_some_key(m map[int]int) int {
    for k := range m {
        return k
    }
    return 0
}

我想要这样做的原因是我正在使用地图来维护一组作业,并且使用地图我可以获得待处理的作业或在 O(1) 中删除已完成的作业。我想这应该是一个常见的要求,但在 Go 中如何做到这一点并不明显。

【问题讨论】:

  • 您是否知道您尝试获取或设置的密钥的值,或者您是否尝试找到一个随机密钥或您事先不知道的某个密钥?
  • 您是否在寻找具有特定值的键?如果您只是在寻找与键关联的值,则只需 m[i]
  • 你的方法看起来不错。如果 map 可以同时被两个 goroutine 访问,请使用 sync.Mutex 保护检索/删除操作,这样两个 goroutine 就不会抓取相同的工作(并且因为 map 出于速度考虑,本身不是线程安全的)。
  • @dethtron5000 我不需要随机密钥或某事,我只需要给我一个地图中任何元素的密钥(如果有)
  • @twotwotwo 是的,我从maps in action 文章中看到了这一点,并且要清楚,我使用的是sync.RWMutex。

标签: dictionary go


【解决方案1】:

可以讨论从哈希表中获取任意键是否是常见要求。其他语言映射实现通常缺少此功能(例如Dictionary in C#

但是,您的解决方案可能是最快的解决方案,但您将得到一个您无法控制的伪随机算法。虽然当前的实现使用伪随机算法,但Go Specification 并不能保证它实际上是随机的,只是不能保证它是可预测的:

未指定映射的迭代顺序,也不保证从一次迭代到下一次迭代顺序相同。

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

【讨论】:

  • 感谢您的回答,请参阅我上面的评论,我不需要任何随机控制,我只想要一种“只要给我地图中的元素”(如果有的话)的方式。
  • @chuchao333 那你的解决方案很好。如果您想内联,另一种类似的方法是var k, v int; for k, v = range m { break }。为了确保你得到一个值,你可以这样做:var k, v int; var ok bool; for k, v = range m { ok = true; break }
  • 最后一句中的“从切片中获取索引”是什么意思?
  • @jochen:可能选词不当。我的意思是获取存储在切片中特定索引处的值。例如:slice[rand.Intn(len(slice))]
  • @ANisus:但问题是关于地图,而不是切片?而且我看不到一个简单的方法来获取所有键或类似的切片。
【解决方案2】:

这是一个更通用的版本,虽然它可能效率较低:

    keys := reflect.ValueOf(mapI).MapKeys()
    return keys[rand.Intn(len(keys))].Interface()

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

【讨论】:

  • 它的运行速度比 Xeoncross 建议的方法慢了大约 5 倍。 (虽然我喜欢这个版本,因为它更简单。)我在这里做了一个测试代码:play.golang.org/p/L2z5kGJ7WsW
  • 这更可怕!这是 O(N) 运行时和内存!
【解决方案3】:

给你。

并发安全和 O(1)

这是一个添加“随机”方法的地图包装器。

示例用法:

package main

import (
    "fmt"
    "sync"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    
    s := NewRandMap()

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

    randomItem := s.Random()
    myItem := randomItem.(string)

    fmt.Println(myItem)
}

数据结构:

type RandMap struct {
    m sync.RWMutex

    // Where the objects you care about are stored.
    container map[string]interface{}

    // 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  []string

    // 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[string]int
}

func NewRandMap() *RandMap {
    return &RandMap{
        container: make(map[string]interface{}),
        sliceKeyIndex: make(map[string]int),
    }
}

func (s *RandMap) Add(key string, item interface{}) {
    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) Get(key string) interface{} {
    s.m.RLock()
    defer s.m.RUnlock()

    return s.container[key]
}

func (s *RandMap) Remove(key string) {
    s.m.Lock()
    defer s.m.Unlock()

    // 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) Random() interface{} {

    if s.Len() == 0 {
        return nil
    }

    s.m.RLock()
    defer s.m.RUnlock()

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

    return s.container[key]
}

func (s *RandMap) PopRandom() interface{} {

    if s.Len() == 0 {
        return nil
    }

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

    item := s.container[key]
    s.m.RUnlock()

    s.Remove(key)

    return item
}

func (s *RandMap) Len() int {
    s.m.RLock()
    defer s.m.RUnlock()

    return len(s.container)
}

【讨论】:

    【解决方案4】:

    也许你想要的是一个数组,它很容易随机访问,尤其是 容器是随机读取重但不经常更改。

    【讨论】:

      【解决方案5】:

      在我的情况下,地图只有一个我需要提取的密钥,因此您可以这样做:

          var key string
          var val string
          for k, v := range myMap {
              key = k
              val = v
              break
          }
      

      对于多个键,您可以执行类似的操作,

      func split_map(myMap map[string]string, idx int) (string[], string[]) {
          keys := make([]string, len(myMap))
          values := make([]string, len(myMap))
          count := 0
          for k, v := range myMap {
              keys[count] = k
              values[count] = v
              count = count + 1
          }
          return keys, values
      }
      

      在访问第 i 个元素时,

      func get_ith(myMap map[string]string, idx int) (string, string) {
          count := 0
          for k, v := range myMap {
              if idx == count {
                  return k, v
              }
              count = count + 1
          }
          return "", ""
      }
      

      【讨论】:

        【解决方案6】:

        在本质上不支持它的数据结构上强制使用 API 通常不是一个好主意。充其量它会很慢,hacky,难以测试,难以调试和不稳定。 Go 的 map 原生支持 upsertgetdeletelength,但不支持 GetRandom

        这里提到的两个具体解决方案

        • 在一个范围内迭代并选择第一个并不能保证会选择随机项目或启用对随机程度的任何控制(即均匀、高斯和播种)
        • 反射很麻烦,速度很慢,并且需要与地图大小成比例的额外内存

        其他解决方案谈论使用额外的数据结构来帮助地图支持此操作。这是我认为最有意义的事情

        type RandomizedSet interface {
            Delete(key int) // O(1)
            Get(key int) int // O(1)
            GetRandomKey() int // O(1)
            Len() int // O(1)
            Upsert(key int, val int) // O(1)
        }
        
        type randomizedset struct {
            h map[int]int // map key to its index in the slice
            indexes []int // each index in the slice contains the value
            source rand.Source // rng for testability, seeding, and distribution
        }
        
        func New(source rand.Source) RandomizedSet {
            return &randomizedset{
                h: make(map[int]int, 0),
                indexes: make([]int, 0),
                source: source,
            }
        }
        
        // helper to accomodate Delete operation
        func (r *randomizedset) swap(i, j int) {
            r.indexes[i], r.indexes[j] = r.indexes[j], r.indexes[i]
            r.h[r.indexes[i]] = i
            r.h[r.indexes[j]] = j
        }
        
        // remainder of implementations here
        
        

        【讨论】:

          【解决方案7】:

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

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

          type ItemType interface{}
          
          func getMapItemRandKey(m map[string]ItemType) string {
              return reflect.ValueOf(m).MapKeys()[0].String()
          }
          

          每张地图的key格式如下:

          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{}
          
          
          

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

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

          【讨论】:

            【解决方案8】:

            作为“全局”解决方案,因为我是 elasticsearch 的忠实粉丝,您可以使用另一个地图/数组来存储您的数据,构建一种倒排字典。

            【讨论】:

              猜你喜欢
              • 2021-06-21
              • 1970-01-01
              • 1970-01-01
              • 2012-05-05
              • 1970-01-01
              • 1970-01-01
              • 2016-07-17
              • 1970-01-01
              • 2023-03-17
              相关资源
              最近更新 更多