2

给定一个 typedef:“type ID int”

是否可以将 map[ID]int 转换为 map[int]int?

我试过了:

map[int]int(m)

m.(map[int]int)

似乎都不起作用: http ://play.golang.org/p/oPzkUcgyaR

有什么建议吗?


编辑更多细节,因为有几个人问过。

我想做的是获得联赛冠军。

有一堆“团队”,每个团队都有许多统计数据。对于每个统计数据,最高得分为 10 分,第二为 9 分,以此类推。

我将其建模为:

// Each team's statistics:
type StatLine map[StatID]float64

// The whole league:
map[TeamID]StatLine

// And I want to score that whole league with:
func score(s map[TeamID]StatLine) map[TeamID]int

// Only, is't also handy to score individual players:
func score(s map[PlayerID]StatLine) map[PlayerID]int

最好不要写两次 score() (因为它是相同的逻辑)或复制整个地图。

碰巧 PlayerID 和 TeamID 是整数,因此我很好奇我是否可以只写 score(s map[int]int) 并进行类型转换。然而,SteveMcQwark 清楚地说明了为什么这可能是一个坏主意。

我认为泛型可以解决这个问题,但我知道它们不会立即出现。还有其他想法吗?

谢谢!

4

3 回答 3

2

如果你真的想在 Go 中做泛型,你需要一个接口。该接口将通过两种类型实现,一种用于团队,一种用于玩家。score 函数将这个接口类型的对象作为参数,并在不知道它是与球队还是球员一起工作的情况下实现公共评分逻辑。这就是概述。以下是一些细节:

接口的方法集正是评分函数需要的功能。让我们从它似乎需要的两种方法开始,

type scoreable interface {
    stats(scID) StatLine  // get statLine for ID
    score(scID, int)      // set score for ID
}

和一个通用的可评分 ID 类型,

type scID int

可以实现此接口的类型不是 TeamID 和 PlayerID,而是保存它们的映射的类型。此外,这些类型中的每一种都需要地图、StatLine 和分数地图。结构适用于此:

type teamScores struct {
    stats map[TeamID]StatLine
    scores map[TeamID]int
}

然后实施 scoreable,

func (s *teamScores) stats(id scID) StatLine {
    return s.stats[TeamID(id)]
}

func (s *teamScores) score(id scID, sc int) {
    s.scores[TeamID(id)] = sc
}

等等,你可能会说。scID 到 TeamID 的那种类型转换。那安全吗?我们是否还不如采用甚至没有 TeamID 的低工程方法?好吧,只要明智地使用这些方法,它就是安全的。struct teamScores 将一个 TeamID 映射与另一个 TeamID 映射相关联。我们即将编写的通用函数 score 将此结构作为参数,因此被赋予正确的关联。它不可能混淆 TeamID 和 PlayerID。这很有价值,并且在足够大的程序中可以证明这种技术是合理的。

对 PlayerID 执行相同的操作,定义一个类似的结构并添加两个方法。

写一次 score 函数。这样开始:

func score(s scoreable) {
    for

糟糕,我们需要一些方法来迭代。一个权宜之计的解决方案是获取 ID 列表。让我们添加该方法:

type scoreable interface {
    ids() []scID          // get list of all IDs
    stats(scID) StatLine  // get statLine for ID
    score(scID, int)      // set score for ID
}

以及 teamScores 的实现:

func (s *teamScores) ids() (a []scID) {
    for tid := range s.stats {
        a = append(a, scID(tid))
    }
    return
}

现在我们在哪里?

func score(s scoreable) {
    // lets say we need some intermediate value
    sum := make(map[scID]float64)
    // for each id for which we have a statLine,
    for _, id := range s.ids() { // note method call
        // get the statLine
        stats := s.stats() // method call
        // compute intermediate value
        sum[id] = 0.
        for _, statValue := range stats {
            sum[id] += statValue
        }
    }
    // now compute the final scores
    for id, s := range sum {
        score := int(s) // stub computation
        s.score(id, score) // method call
    }
}

请注意,该函数采用接口类型 scoreable,而不是像 *scoreable 这样的接口指针。一个接口可以保存指针类型 *teamScores。没有额外的指向接口的指针是合适的。

最后,为了调用通用函数,我们需要一个 teamScores 类型的对象。您可能已经拥有联赛统计数据,但可能尚未创建得分图。您可以像这样一次完成所有这些操作:

ts := &teamScores{
    stats:  ...your existing map[TeamID]Statline,
    scores: make(map[TeamID]int),
}

称呼:

score(ts)

并且团队分数将在 ts.scores 中。

于 2012-05-29T18:31:16.903 回答
0

拥有单独的 TeamID 和 PlayerID 类型可能会过度设计。如果你对两者都使用相同的类型,比如 ScoreableID,问题就会消失。在大多数情况下,变量名、函数名或程序上下文可以清楚地表明某事是球员还是球队。注释将适用于任何剩余的模棱两可的代码。

于 2012-05-29T16:32:27.083 回答
0

转换规则是表达性和可维护性之间的权衡。每当您在两种类型之间进行转换时,您就是在赌这些类型在未来将在表示上和逻辑上保持兼容。类型系统的目标之一是最大限度地减少对这种兼容性的依赖性传播。另一方面,能够定义多个可以对同一数据进行操作的方法集非常有用,并且转换允许您根据需要选择适当的方法集来对现有数据进行操作。您只能在您可以合法地换出其他相同类型的方法集的情况下进行转换,这不包括您询问的情况。

知道你正在尝试做什么而不是你如何尝试这样做可能会很有用。我的意思是,如果您只需要对它进行只读访问,则可以将 a 包装map[ID]int在其中,或者您可以使用 a ,但很有可能这是不必要的。func(int)(int, bool)interface { Get(int)(int, bool); Set(int, int); Delete(int) }

于 2012-05-28T20:13:48.313 回答