User12547645
User12547645

Reputation: 8507

How to write a bidirectional mapping in Go?

I am writing a simple console game and would like to map a player to a symbol. With two players my approach looks like this:

func playerToString(p player) string {
    if p == 0 {
        return "X"
    }
    return "O"
}

func stringToPlayer(s string) player {
    if s == "X" {
        return 0
    }
    return 1
}

Of cause you could also write this as two maps mapping int to string and string to int. Both the above approach and the map approach seem error-prone. Is there a more idiomatic way to write this in go? Maybe some non-iota enum way?

Upvotes: 4

Views: 2925

Answers (4)

Graytonio
Graytonio

Reputation: 1

For anyone that finds this thread down the line I wanted a more ergonomic solution for bi-directional value mapping so I created a small module to do this. If anyone is looking for a ready made solution.

https://github.com/graytonio/go-bidirectional-map

myMap := bidmap.NewMap(map[string]int{
    "foo": 3,
    "bar": 4,
})

fmt.Println(myMap.GetP("foo"))
// Output: 3 true

fmt.Println(myMap.GetS(3))
// Output: foo true

Edit: Added example code

Upvotes: 0

cod3rboy
cod3rboy

Reputation: 892

Assuming you don't need any specific mapping and the player integer values have the sequence 0,1,2,3,...,25, you can generate the players symbols directly without using the maps as shown in following snippet :-

type player int

func ToSymbol(p player) string {
    return fmt.Sprintf("%c", 'A' + p)
}

func ToPlayer(symbol string) player {
    return player([]rune(symbol)[0] - 'A')
}

Upvotes: 0

Ben Hoyt
Ben Hoyt

Reputation: 11044

In addition to Eli's answer, two other changes you could make. You could make the to-symbol function a method of the player type. And because the player values are integers (sequential starting from zero), you can use a slice instead of a map to store the int-to-symbol mapping -- it's a bit more efficient to store and for lookup.

type player int

var playerSymbols = []string{"X", "O", "A", "B", "C", "D", "E", "F", "G", "H"}

func (p player) Symbol() string {
    if int(p) < 0 || int(p) >= len(playerSymbols) {
        return "?" // or panic?
    }
    return playerSymbols[p]
}

This method signature could even be String() string so it's a fmt.Stringer, which can be useful for printing stuff out and debugging.

Upvotes: 1

Eli Bendersky
Eli Bendersky

Reputation: 273834

[I assume your example is just minimal and that your actual mapping has more than two options. I also assume you meant bi-directonal mapping]

I would write one map:

var player2string = map[int]string{
  0: "0",
  1: "X",
  // etc...
}

And then would create a function to populate a different map string2player programmatically. Something like this:

var player2string = map[int]string{
    0: "0",
    1: "X",
    // etc...
}

var string2player map[string]int = convertMap(player2string)

func convertMap(m map[int]string) map[string]int {
    inv := make(map[string]int)
    for k, v := range m {
        inv[v] = k
    }
    return inv

}

func main() {
    fmt.Println(player2string)
    fmt.Println(string2player)
}

Try it on the Go playground

Upvotes: 4

Related Questions