如果您需要一个map
和按顺序排列的键,这是两个不同的东西,您需要两种不同的(数据)类型来提供该功能。
带钥匙片
实现此目的的最简单方法是在不同的切片中维护键顺序。每当您将新对放入地图时,首先检查密钥是否已在其中。如果没有,请将新密钥添加到单独的切片中。当您需要按顺序排列元素时,可以使用键切片。当然,当您删除一对时,您也必须将其从切片中删除。
键切片只需包含键(而不是值),因此开销很小。
将这个新功能(地图+键切片)包装到新类型中并为其提供方法,并隐藏地图和切片。那么就不会出现数据错位的情况。
实施示例:
type Key int // Key type
type Value int // Value type
type Map struct {
m map[Key]Value
keys []Key
}
func New() *Map {
return &Map{m: make(map[Key]Value)}
}
func (m *Map) Set(k Key, v Value) {
if _, ok := m.m[k]; !ok {
m.keys = append(m.keys, k)
}
m.m[k] = v
}
func (m *Map) Range() {
for _, k := range m.keys {
fmt.Println(m.m[k])
}
}
使用它:
m := New()
m.Set(1, 11)
m.Set(2, 22)
m.Range()
尝试一下去游乐场 https://play.golang.org/p/wqhstzhBQT.
使用实现链表的值包装器
另一种方法是包装值,并且沿着实际值还存储下一个/上一个键。
例如,假设您想要一张类似的地图map[Key]Value
:
type valueWrapper struct {
value Value
next *Key // Next key
}
每当您将一对添加到地图时,您都会设置一个valueWrapper
作为价值,你必须link这是前一个(最后一个)对。到link,你必须设置next
最后一个包装器的字段指向这个新键。为了轻松实现这一点,建议还存储最后一个密钥(以避免必须搜索它)。
当您想按插入顺序迭代元素时,您从第一个元素开始(您必须存储它)及其关联的valueWrapper
会告诉您下一个键(按插入顺序)。
实施示例:
type Key int // Key type
type Value int // Value type
type valueWrapper struct {
v Value
next *Key
}
type Map struct {
m map[Key]valueWrapper
first, last *Key
}
func New() *Map {
return &Map{m: make(map[Key]valueWrapper)}
}
func (m *Map) Set(k Key, v Value) {
if _, ok := m.m[k]; !ok && m.last != nil {
w2 := m.m[*m.last]
m.m[*m.last] = valueWrapper{w2.v, &k}
}
w := valueWrapper{v: v}
m.m[k] = w
if m.first == nil {
m.first = &k
}
m.last = &k
}
func (m *Map) Range() {
for k := m.first; k != nil; {
w := m.m[*k]
fmt.Println(w.v)
k = w.next
}
}
使用它是一样的。尝试一下去游乐场 https://play.golang.org/p/Ih-ZvaGXsl.
Notes:您可以根据自己的喜好更改一些内容:
您可以像这样声明内部地图m map[Key]*valueWrapper
所以在Set()
你可以改变next
字段,而无需分配新的valueWrapper
.
您可以选择first
and last
字段类型*valueWrapper
您可以选择next
属于类型*valueWrapper
比较
使用额外切片的方法更简单、更干净。但是,如果地图变大,从中删除元素可能会变得很慢,因为我们还必须在切片中找到“未排序”的键,所以它是O(n)
复杂。
值包装器中使用链表的方法可以轻松扩展以支持快速元素删除,即使地图很大,如果您还添加prev
场到valueWrapper
结构。因此,如果您需要删除一个元素,您可以超快速地找到包装器(O(1)
),更新 prev 和 next 包装器(相互指向),并执行一个简单的delete()
操作,就是O(1)
.
请注意,第一个解决方案(使用切片)中的删除仍然可以通过使用 1 个附加映射来加速,该映射将从键映射到切片中键的索引(map[Key]int
),所以删除操作仍然可以在O(1)
,以换取更大的复杂性。加速的另一个选择可能是将映射中的值更改为包装器,它可以保存切片中键的实际值和索引。
查看相关问题:为什么 Go 不能按插入顺序迭代映射? https://stackoverflow.com/questions/28930416/why-cant-go-iterate-maps-in-insertion-order