如何在Go中为哈希映射创建复合键

21

首先,我的复合键定义是指两个或更多值组合成键。不要与数据库中的复合键混淆。

我的目标是在哈希表中保存计算出的 pow(x, y) 值,其中xy是整数。这就是我需要想法来创建一个键,以便在给定xy时,可以在哈希表中查找它,找到pow(x,y)的地方。

例如:

pow(2, 3) => {key(2,3):8}

我想要弄清楚的是如何获得键为(2,3)的映射表键,即生成一个由多个值组成的键,并在哈希表中使用它的最佳方法。


你可以定义一个类型,创建该类型的值并将其分配给映射。例如:type myKey struct {x,y int}; m := map[myKey]int{myKey{2,3}:8,};//... - user4466350
3个回答

37

最简单和最灵活的方法是使用 struct 作为键类型,其中包括您想要成为键的所有数据,在您的情况下:

type Key struct {
    X, Y int
}

就是这样。使用它:

m := map[Key]int{}
m[Key{2, 2}] = 4
m[Key{2, 3}] = 8

fmt.Println("2^2 = ", m[Key{2, 2}])
fmt.Println("2^3 = ", m[Key{2, 3}])

输出结果(可以在Go Playground上尝试):

2^2 =  4
2^3 =  8

规范:映射类型:您可以使用任何类型作为键,其中比较运算符 == != 完全被定义,并且上述Key结构类型满足此要求。

规范:比较运算符:如果所有字段都是可比较的,则结构值是可比较的。如果它们对应的非空白字段相等,则两个结构值相等。

一个重要的事情:不应将指针用作键类型(例如* Key ),因为比较指针仅比较内存地址,而不是指向的值。

还要注意,您也可以使用数组(而不是切片)作为键类型,但是数组不像结构体那样灵活。您可以在此处阅读更多信息:为什么要在Go中使用数组?

这是使用数组的样子:

type Key [2]int

m := map[Key]int{}
m[Key{2, 2}] = 4
m[Key{2, 3}] = 8

fmt.Println("2^2 = ", m[Key{2, 2}])
fmt.Println("2^3 = ", m[Key{2, 3}])

输出结果相同。在Go Playground上尝试一下。


1
顺便说一下,如果你想避免在调用中使用键文字面量,你可以将该映射作为具有方法的命名类型:https://play.golang.org/p/TLsewvbwsc7 - Kaedys

8

Go无法为int切片创建哈希。因此,我的方法是将一个结构映射到一个数字。

以下是如何实现的示例:

package main

import (
    "fmt"
)

type Nums struct {
    num1 int
    num2 int
}

func main() {
    powers := make(map[Nums]int)
    numbers := Nums{num1: 2, num2: 4}

    powers[numbers] = 6

    fmt.Printf("%v", powers[input])
}

我希望你能够得到帮助。

0

其他答案已经很好地解决了您的具体问题。我想再添加一个额外的技巧,在某些特殊情况下可能会有用。

考虑到映射键必须是可比较的,您还可以使用接口。如果它们的动态值是可比较的,则接口是可比较的。

这使您可以基本上将映射分区,即在同一数据结构中使用多种类型的键。例如,如果您想在映射中存储n元组(它不适用于数组,因为数组长度是类型的一部分)。

这个想法是定义一个带有虚拟方法的接口(但它肯定不是虚拟的),并将其用作映射键:

type CompKey interface {
    isCompositeKey() bool
}

var m map[CompKey]string

此时,您可以拥有任意类型来实现接口,可以显式地实现,也可以通过嵌入来实现。

在这个例子中,想法是使接口方法未导出,以便其他结构体可以仅嵌入接口而无需提供实际实现 - 该方法无法从其包外部调用。它只会表明该结构体可用作复合映射键。

type AbsoluteCoords struct {
    CompKey
    x, y int
}

type RelativeCoords struct {
    CompKey
    x, y int
}

func foo() {
    p := AbsoluteCoords{x: 1, y: 2}
    r := RelativeCoords{x: 10, y: 20}

    m[p] = "foo"
    m[r] = "bar"

    fmt.Println(m[AbsoluteCoords{x: 10, y: 20}]) // "" (empty, types don't match)
    
    fmt.Println(m[RelativeCoords{x: 10, y: 20}]) // "bar" (matches, key present)
}

当然,你可以在接口上声明实际的方法,在遍历映射键时可能会很有用。

这个接口键的缺点是现在你有责任确保实现类型是可比较的。例如,这个映射键将会引发恐慌:

type BadKey struct {
    CompKey
    nonComparableSliceField []int
}

b := BadKey{nil, []int{1,2}}
m[b] = "bad!" // panic: runtime error: hash of unhashable type main.BadKey

总的来说,当您需要在同一个映射中保留两组K/V对时,这可能是一种有趣的方法,例如在函数签名中保持一些清晰度或避免定义具有N个非常相似的映射字段的结构体。
示例代码 https://play.golang.org/p/0t7fcvSWdy7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接