从地图中获取一个任意的键/项。

huangapple go评论106阅读模式
英文:

Get an arbitrary key/item from a map

问题

我是新手,现在我想从一个map中获取任意项;有什么惯用的方法吗?我只能想到像这样的方法:

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

我之所以想要这样做是因为我正在使用一个map来维护一组作业,通过map我可以在O(1)的时间内获取一个待处理的作业或移除一个已完成的作业。我猜这应该是一个常见的需求,但在Go中如何实现并不明显。

英文:

I am new to Go and now I want to get an arbitrary item from a map; what's the idiomatic way to do that? I can only think of something like this:

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

The reason I want that is I am using a map to maintain a set of jobs, and with a map I can get a pending job or remove a finished job in O(1). I guess this should be a common requirement but it's not obvious how to do it in Go.

答案1

得分: 23

讨论是否从哈希表中获取任意键是一个常见需求。其他语言的映射实现通常缺乏这个功能(例如C#中的Dictionary)。

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

>映射的迭代顺序没有指定,并且不能保证从一次迭代到下一次迭代是相同的。

如果你想更多地控制随机化过程,你还可以并行地维护一个包含在映射中的值(或键)的更新切片,使用你选择的随机化方法(math/randcrypto/rand用于更极端的情况)来获取在切片中随机选择的索引处存储的值。

英文:

Whether getting an arbitrary key from a hash table is a common requirement may be discussed. Other language map implementations often lack this feature (eg. Dictionary in C# )

However, your solution is probably the fastest one, but you will be left with a pseudo-random algorithm that you do not control. And while the current implementation uses a pseudo-random algorithm, the Go Specification doesn't give you any assurance it will actually be random, only that it is not guaranteed to be predictable:

>The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next.

If you want more control of the randomization, you can also in parallel keep an updated slice of values (or keys) contained in the map, using the randomization of your choice (math/rand or crypto/rand for more extreme cases) to get the value stored at an index, selected randomly, in the slice.

答案2

得分: 10

以下是翻译好的内容:

这里是一个更通用的版本,尽管可能不太高效:

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

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

英文:

Here is a more generic version, although it may be less efficient:

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

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

答案3

得分: 7

这是一个包装了map的并发安全且具有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

	// 存储你关心的对象的地方。
	container map[string]interface{}

	// 用于存储上述map中的键的切片。我们将它们放在切片中,以便可以通过选择随机索引来获取随机键。
	keys  []string

	// 存储每个键的索引,以便在删除项时可以快速从上述切片中删除它。
	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()

	// 在map中存储对象
	s.container[key] = item

	// 将map的键添加到键的切片中
	s.keys = append(s.keys, key)

	// 存储map键的索引
	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()

	// 获取键在键切片中的索引
	index, exists := s.sliceKeyIndex[key]
	if !exists {
		// 项不存在
		return
	}

	delete(s.sliceKeyIndex, key)

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

	// 从键的切片中删除键
	s.keys[index] = s.keys[len(s.keys)-1]
	s.keys = s.keys[:len(s.keys)-1]

	// 我们刚刚将最后一个元素交换到另一个位置。
	// 因此,我们需要更新它的索引(如果它不在最后位置)
	if !wasLastIndex {
		otherKey := s.keys[index]
		s.sliceKeyIndex[otherKey] = index
	}

	// 从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)
}

希望对你有帮助!

英文:

Here you go.

Concurrent safe and O(1)

This is a wrapper for a map that adds "random" methods.

Example usage:
<!-- language: go -->

package main
import (
&quot;fmt&quot;
&quot;sync&quot;
&quot;math/rand&quot;
&quot;time&quot;
)
func main() {
rand.Seed(time.Now().UnixNano())
s := NewRandMap()
s.Add(&quot;myKey&quot;, &quot;Item1&quot;)
s.Add(&quot;myKey2&quot;, &quot;Item2&quot;)
s.Add(&quot;myKey3&quot;, &quot;Item3&quot;)
randomItem := s.Random()
myItem := randomItem.(string)
fmt.Println(myItem)
}

Data Structure:
<!-- language: go -->

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 &amp;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&#39;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

得分: 3

这是我找到的更快的方法来实现这个:

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

type ItemType interface{}

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

每个映射的键都采用以下格式:

b := new(big.Int)
rbytes := 一些生成密码安全的随机字节的函数
b.SetBytes(rbytes)

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

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

这个方法非常快速,并且可以很容易地适应任何类型的映射。

英文:

Here is a faster way I found to do this :

In my test, I created the following function

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

The key to each map is in the following format:

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] = &amp;ItemType{}

As a test, I'm getting the first key from my value as I request all keys but there is only one (... MapKeys()[0]).

This is super fast and can be adapted to any kind of map very easily.

答案5

得分: 0

也许你想要的是一个数组,它可以方便地进行随机访问,特别是当容器需要频繁进行随机读取但很少改变时。

英文:

Maybe what you want is a array, which is easy to access randomly, especially
the container is random read heavy but changed infrequently.

答案6

得分: 0

在我的情况下,地图只有一个我需要提取的键,所以你可以这样做:

	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 "", ""
}
英文:

In my case, the map only had one key which I needed to extract so for that you can do:

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

For multiple keys you could do something like,

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
}

While for accessing ith element,

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 &quot;&quot;, &quot;&quot;
}

答案7

得分: 0

通常情况下,强制将API应用于不本质支持该API的数据结构是不明智的。最好的情况是,它会变得缓慢、笨拙、难以测试、难以调试和不稳定。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 // 将键映射到其在切片中的索引
    indexes []int // 切片中的每个索引包含值
    source rand.Source // 用于可测试性、种子和分布的随机数生成器
}

func New(source rand.Source) RandomizedSet {
    return &randomizedset{
        h: make(map[int]int, 0),
        indexes: make([]int, 0),
        source: source,
    }
}

// 辅助函数以适应Delete操作
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
}

// 其余实现在这里

英文:

It is usually not a good idea to force an API on a data-structure that doesn't intrinsically support it. At best it will be slow, hacky, hard-to-test, hard-to-debug and unstable. Go's map natively supports upsert, get, delete, and length but not GetRandom.

Of the two concrete solutions mentioned here

  • iterating over a range and choosing the first one does not guarantee a random item will be chosen or enable any control over the degree of randomness (ie uniform, gaussian, and seeding)
  • reflection is hacky, slow and requires additional memory proportional to the size of the map

The other solutions talk about using additional data structures to help the map support this operation. This is what I think makes the most sense

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 &amp;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

答案8

得分: -4

作为一个“全球”解决方案,由于我是elasticsearch的忠实粉丝,你可以使用另一个映射/数组来存储你的数据,构建一种倒排字典的形式。

英文:

As a "global" solution, as I am a big fan of elasticsearch, you could use another map/array to store your data, to build a kind of an inverted dictionary.

huangapple
  • 本文由 发表于 2014年5月6日 06:13:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/23482786.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定