英文:
Type conversion of maps in go
问题
给定一个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分,依此类推。
我将其建模为:
// 每个队伍的统计数据:
type StatLine map[StatID]float64
// 整个联赛:
map[TeamID]StatLine
// 我想用以下函数对整个联赛进行评分:
func score(s map[TeamID]StatLine) map[TeamID]int
// 只是,对于评分个别球员也很方便:
func score(s map[PlayerID]StatLine) map[PlayerID]int
而且最好不要重复编写score()(因为逻辑相同)或复制整个map。
碰巧PlayerID和TeamID都是int,所以我很好奇是否可以只编写score(s map[int]int)并进行类型转换。然而,SteveMcQwark明确解释了为什么这可能不是一个好主意。
我认为泛型可以解决这个问题,但我知道它们不会立即出现。还有其他的想法吗?
谢谢!
英文:
Given a typedef of: "type ID int"
Is it possible to convert a map[ID]int to a map[int]int?
I've tried:
map[int]int(m)
as well as
m.(map[int]int)
and neither seems to work:
http://play.golang.org/p/oPzkUcgyaR
Any advice?
Edit more details, since a couple people asked.
What I'm trying to do is score a league.
There are a bunch of "Teams", and each has a number of stats. For each stat, you get 10 points for being highest scoring, 9 for 2nd etc.
I modeled this as:
// 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
And it would be great not to write score() twice (since it's the same logic) or copy the whole map over.
It happens that PlayerID and TeamID are ints, hence I was curious if I could just write score(s map[int]int) and do type conversion. However, SteveMcQwark makes it clear why this might be a bad idea.
I think generics would solve this, but I know they're not immediately forthcoming. Any other ideas?
Thanks!
答案1
得分: 2
如果你真的想在Go中使用泛型,你需要一个接口。这个接口将由两种类型实现,一种是团队类型,一种是球员类型。score函数将接受一个这个接口类型的对象作为参数,并实现通用的计分逻辑,而不需要知道它是与团队还是球员一起工作。这是概述。以下是一些细节:
接口的方法集将恰好是score函数所需的函数。让我们从两个似乎需要的方法开始,
type scoreable interface {
stats(scID) StatLine // 获取ID的StatLine
score(scID, int) // 为ID设置分数
}
和一个泛型的scoreable ID类型,
type scID int
可以实现这个接口的类型不是TeamID和PlayerID,而是持有它们的映射的类型。此外,每个这些类型都需要两个映射,即StatLine和score映射。一个结构体可以满足这个要求:
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这种低效的方法?嗯,只要这些方法被合理使用,就是安全的。结构体teamScores将一个TeamIDs的映射与另一个TeamIDs的映射关联起来。我们即将编写的泛型函数score将这个结构体作为参数,并因此得到了正确的关联。它不可能混淆TeamIDs和PlayerIDs。这是有价值的,在足够大的程序中可以证明这种技术是合理的。
对于PlayerID也做同样的操作,定义一个类似的结构体并添加两个方法。
只需编写一次score函数。像这样开始:
func score(s scoreable) {
for
哎呀,我们需要一种迭代的方式。一个方便的解决方案是获取一个ID列表。让我们添加这个方法:
type scoreable interface {
ids() []scID // 获取所有ID的列表
stats(scID) StatLine // 获取ID的StatLine
score(scID, int) // 为ID设置分数
}
以及teamScores的实现:
func (s *teamScores) ids() (a []scID) {
for tid := range s.stats {
a = append(a, scID(tid))
}
return
}
现在我们在哪里?
func score(s scoreable) {
// 假设我们需要一些中间值
sum := make(map[scID]float64)
// 对于每个我们有statLine的id,
for _, id := range s.ids() { // 注意方法调用
// 获取statLine
stats := s.stats() // 方法调用
// 计算中间值
sum[id] = 0.
for _, statValue := range stats {
sum[id] += statValue
}
}
// 现在计算最终分数
for id, s := range sum {
score := int(s) // 假设计算
s.score(id, score) // 方法调用
}
}
注意函数接受的是接口类型scoreable,而不是指向接口的指针类型scoreable。一个接口可以持有指针类型teamScores。不需要额外的指向接口的指针。
最后,要调用泛型函数,我们需要一个teamScores类型的对象。你可能已经有了联赛统计数据,但可能还没有创建score映射。你可以一次性完成所有这些操作,像这样:
ts := &teamScores{
stats: ...你现有的map[TeamID]Statline,
scores: make(map[TeamID]int),
}
调用:
score(ts)
然后团队得分将在ts.scores中。
英文:
If you really want to do generics in Go, you need an interface. This interface will be implemented by two types, one for teams and one for players. The score function will take an object of this interface type as an argument and implement the common scoring logic without knowledge of whether it is working with teams or players. That's the overview. Here are some details:
The method set of the interface will be exactly the functions that the score function will need. Lets start with two methods it would seem to need,
type scoreable interface {
stats(scID) StatLine // get statLine for ID
score(scID, int) // set score for ID
}
and a generic scoreable ID type,
type scID int
The types that can implement this interface are not TeamID and PlayerID, but types that hold maps of them. Further, each of these types will need both maps, the StatLine and score maps. A struct works for this:
type teamScores struct {
stats map[TeamID]StatLine
scores map[TeamID]int
}
Implementing scoreable then,
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
}
Wait, you might say. That type conversion of scID to TeamID. Is that safe? Had we might as well just go with the low-enginering approach of not even having TeamID? Well, it's safe as long as these methods are used sensibly. The struct teamScores associates a map of TeamIDs with another map of TeamIDs. Our soon-to-be-written generic function score takes this struct as an argument and thus is given the correct association. It cannot possibly mix up TeamIDs and PlayerIDs. That's valuable and in a large enough program can justify this technique.
Do the same for PlayerID, define a similar struct and add the two methods.
Write the score function once. Start like this:
func score(s scoreable) {
for
Oops, we need some way to iterate. An expedient solution would be to get a list of IDs. Let's add that method:
type scoreable interface {
ids() []scID // get list of all IDs
stats(scID) StatLine // get statLine for ID
score(scID, int) // set score for ID
}
and an implementation for teamScores:
func (s *teamScores) ids() (a []scID) {
for tid := range s.stats {
a = append(a, scID(tid))
}
return
}
Now where were we?
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
}
}
Notice the function takes the interface type scoreable, not pointer to interface like *scoreable. An interface can hold the pointer type *teamScores. No additional pointer to the interface is appropriate.
Finally, to call the generic function, we need an object of type teamScores. You probably already have the league stats, but might not have created the score map yet. You can do all this at once like this:
ts := &teamScores{
stats: ...your existing map[TeamID]Statline,
scores: make(map[TeamID]int),
}
call:
score(ts)
And the team scores will be in ts.scores.
答案2
得分: 0
转换规则是表达能力和可维护性之间的权衡。每当你在两种类型之间进行转换时,你都在打赌这些类型在未来保持表示上和逻辑上的兼容性。类型系统的目标之一是尽量减少对这种兼容性的依赖。另一方面,能够定义多个可以操作相同数据的方法集非常有用,转换允许你根据需要选择适当的方法集来操作现有数据。只有在你可以合理地在其他方面相同的类型上交换方法集的情况下,才能进行转换,这排除了你所询问的情况。
了解你想要做什么可能比了解你如何做更有用。我的意思是,如果你只需要对一个map[ID]int
进行只读访问,你可以将其包装在一个func(int)(int, bool)
中,或者你可以使用一个interface { Get(int)(int, bool); Set(int, int); Delete(int) }
,但这可能是不必要的。
英文:
The conversion rules are a tradeoff between expressiveness and maintainability. Whenever you convert between two types, you are making a gamble that those types will remain representationally and logically compatible in the future. One of the goals of the type system is to minimize the spread of dependency on this compatibility. On the other hand, it is very useful to be able to define multiple method sets that can operate on the same data, and conversions allow you to select the appropriate method set as needed to operate on existing data. You can only do conversions in cases where you could legitimately be swapping out method sets on otherwise identical types, which excludes the case you are asking about.
It might be useful to know what you're trying to do, rather than how you're trying to do it. I mean, you could wrap a map[ID]int
in a func(int)(int, bool)
if you just need read-only access to it, or you could use an interface { Get(int)(int, bool); Set(int, int); Delete(int) }
, but there's a good chance this is unnecessary.
答案3
得分: 0
这可能是过度工程化,将TeamID和PlayerID作为不同的类型。如果你使用相同的类型,比如ScoreableID,问题就会消失。在大多数情况下,变量名、函数名或程序上下文会清楚地表明某个东西是球员还是球队。在任何仍然存在歧义的代码中,注释是合适的。
英文:
It might be over-engineering to have separate types TeamID and PlayerID. If you used the same type for both, say ScoreableID, the problem would go away. In most cases, a variable name, function name, or the program context would make it clear whether something was a player or team. A comment would be appropriatiate in any remaining ambiguous code.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论