<p>Hello,</p>
<p>Go Beginner here. I've just started porting my nodejs twitter bot to Go and came across this problem, that is bugging me. In node I have this one function pickRandom, that selects a random key from an array/dictionary. In Go I put this up as a map, but since the type of the parameters has to be explicit I now have to use two functions with the exact same function body but just different parameters. See for yourself: <a href="https://github.com/tehcyx/twitter-bot-go-markov-chain/blob/master/markov/markov.go#L149-L171" rel="nofollow">https://github.com/tehcyx/twitter-bot-go-markov-chain/blob/master/markov/markov.go#L149-L171</a></p>
<p>Is there any way I can make it one function again? Should I move from using this kind of map to typedef'ing my own thing?</p>
<p>Cheers</p>
<hr/>**评论:**<br/><br/>phlatphrog: <pre><p>I don't see much need to build up the list in a single method, just so you can select a random one from it.</p>
<pre><code>func pickRandom(dict map[string]map[string]int) string {
which := rand.Intn(len(dict))
c := 0
for k, _ := range dict {
if c == which {
return k
}
c++
}
return ""
}
</code></pre>
<p>On the other hand, if you're looking for a way to make your code more expressive, I think it's important to remember that go will let you define methods on any type you define, so:</p>
<pre><code>type MapOne map[string]map[string]int
type MapTwo map[string]int
func (m MapOne) keys() []string {
keys := make([]string, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
return keys
}
func (m MapTwo) keys() []string {
keys := make([]string, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
return keys
}
func pickRandom(keys []string) string {
return keys[rand.Intn(len(keys))]
}
</code></pre>
<p>Yeah, there's copy and paste there, but it's isolated to the particular issue of getting the keys.</p>
<p>With this, you can just do <code>k := pickRandom(myMap.keys())</code> where <code>myMap</code> is either type of map.</p></pre>tehcyx: <pre><p>Interesting. I thought I read the other day, that there's no method overloading. How does this work then?</p></pre>phlatphrog: <pre><p>Yeah, the polymorphism in go is limited (on purpose), but you can definitely define the same method (with exactly the same param list) on multiple receiver types. That's the root of <code>interface</code> definition in go.</p>
<p>Building on my previous example:</p>
<pre><code>type keyPicker interface {
keys() []string
}
func (kp keyPicker) pickRandom() {
keys := kp.keys()
return keys[rand.Intn(len(keys))]
}
</code></pre>
<p>and then you can just say <code>k := myMap.pickRandom()</code> for either type of map.</p>
<p>Yes, there are some, er, limitations in go, but after you get the hang of it, it can be much more flexible, in ways, than some other languages.</p></pre>tehcyx: <pre><p>Good to know, thanks for the explanation.</p></pre>tehcyx: <pre><p>Oh I see, you're defining a function for the type that you created.</p></pre>tehcyx: <pre><p>I even turned it around a little and now I'm able to use these types throughout my code:</p>
<pre><code>// SubDictionary of follow up words and the corresponding factor.
type SubDictionary map[string]int
// Dictionary of start words with follow ups and the corresponding factor.
type Dictionary map[string]SubDictionary
</code></pre></pre>phlatphrog: <pre><p>cool.</p></pre>: <pre><p>[deleted]</p></pre>tehcyx: <pre><p>Oh sweet. I would've just removed the whole map and put interface, that would also get rid of the switch that was suggested on another comment.</p></pre>tehcyx: <pre><p>Guess this doesn't work:</p>
<pre><code>markov/markov.go:71:20: cannot use dict (type map[string]map[string]int) as type map[string]interface {} in argument to pickRandom
markov/markov.go:85:27: cannot use dict[word] (type map[string]int) as type map[string]interface {} in argument to pickRandom
</code></pre></pre>dgryski: <pre><p>Given that you're writing a markov bot and most of your time will be selecting random entries from maps, the proposed solutions below are all very expensive in terms of both time spent and memory used.</p>
<p>Here's a solution that defines <code>smap</code> a string to anything map that tracks the keys that have been inserted and provides O(1) random key retrieval. Then it defines <code>wordmap</code> and <code>dictmap</code> with type-safe wrappers around smap.</p>
<p><a href="https://play.golang.org/p/-0ZyYwJH_ax" rel="nofollow">https://play.golang.org/p/-0ZyYwJH_ax</a></p></pre>tehcyx: <pre><p>While this looks interesting it would still need some tweaking as my implementation will rely on the factor of the word, hence map[string]map[string]int is my original definition. I will have a thorough look at your example later today. Thank you.</p></pre>MaxWayt: <pre><p>You can take an interface{} as parameter and play with <a href="https://golang.org/pkg/reflect/" rel="nofollow">https://golang.org/pkg/reflect/</a> to manipulate your map</p></pre>MaxWayt: <pre><p>Here is an example <a href="https://play.golang.org/p/d1oyvUmW3lg" rel="nofollow">https://play.golang.org/p/d1oyvUmW3lg</a></p></pre>tehcyx: <pre><p>Thanks for the example. Will play around with that one as well.</p></pre>tehcyx: <pre><p>I assume I cannot go ahead and then just "cast" the parameter to the map I'm using? There's probably no casting in Go, right?</p></pre>albatr0s: <pre><p>You can use a type switch: <a href="https://golang.org/doc/effective_go.html#type_switch" rel="nofollow">https://golang.org/doc/effective_go.html#type_switch</a></p></pre>tehcyx: <pre><p>This looks like it will make the code look more complex, but at least I can have it in one function again.</p></pre>MaxWayt: <pre><p>There is cast, but you need to know the exact type to cast with. Which does not resolve your issue here.</p></pre>jerf: <pre><p>There are some various ways that you can slick this up here and there, but generally speaking, yes, Go tends to end up with some copy and paste here.</p>
<p>It is slightly true that programming in a scripting language does encourage you to think of the world as a set of dictionaries, and Go prefers to think of the world in terms of structs, but still, for code like that you're going to have some annoyance compared to a scripting language. That dictionary merge code is going to end up looking like that pretty much no matter what you do in Go.</p>
<p>Depending on your need for randomness, you can abuse Go a bit with:</p>
<pre><code>var randomkey string
for randomkey := range myDict {
break
}
</code></pre>
<p>though I don't know about how random that is. This doesn't seem like a crypto use case, but it's possible it'll still be more biased than you'd like. (Note if you test this on the playground, it either caches the result of the run, or has the randomness removed for the playground specifically; run this locally and you should see it choosing things with at least some randomness. From what I've seen, maps end up with constant orders within themselves for the same keys, but range will start at a random offset. I do not guarantee that is how this works, it's just what I've seen.)</p>
<p>As MaxWayt suggested, it is true that reflect can do this, but I would recommend against opening that door at this point, unless you choose to do it for educational purposes. (Ultimately, the function would not be that long, but figuring out what ~10 lines you need and why they do what they do would probably be a distraction right now.)</p></pre>tehcyx: <pre><p>Thanks for the long insight. Indeed the only reason I'm porting my own nodejs code to Go is that I want to get more comfortable using Go. It seems so far like a very comfortable language to me and packing the binary in a docker container the size of the binary itself can open some pretty interesting doors for some personal projects I would guess at this point.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传