How to create a case insensitive map in Go?

Here is something more robust than just strings.ToLower, you can use the golang.org/x/text/cases package. Example:

package main
import "golang.org/x/text/cases"

func main() {
   s := cases.Fold().String("March")
   println(s == "march")
}

If you want to use something from the standard library, I ran this test:

package main

import (
   "strings"
   "unicode"
)

func main() {
   var (
      lower, upper int
      m = make(map[string]bool)
   )
   for n := '\u0080'; n <= '\u07FF'; n++ {
      q, r := n, n
      for {
         q = unicode.SimpleFold(q)
         if q == n { break }
         for {
            r = unicode.SimpleFold(r)
            if r == n { break }
            s, t := string(q), string(r)
            if m[t + s] { continue }
            if strings.ToLower(s) == strings.ToLower(t) { lower++ }
            if strings.ToUpper(s) == strings.ToUpper(t) { upper++ }
            m[s + t] = true
         }
      }
   }
   println(lower == 951, upper == 989)
}

So as can be seen, ToUpper is the marginally better choice.


Two possiblities:

  1. Convert to uppercase/lowercase if you're input set is guaranteed to be restricted to only characters for which a conversion to uppercase/lowercase will yield correct results (may not be true for some Unicode characters)

  2. Convert to Unicode fold case otherwise:

Use unicode.SimpleFold(rune) to convert a unicode rune to fold case. Obviously this is dramatically more expensive an operation than simple ASCII-style case mapping, but it is also more portable to other languages. See the source code for EqualsFold to see how this is used, including how to extract Unicode runes from your source string.

Obviously you'd abstract this functionality into a separate package instead of re-implementing it everywhere you use the map. This should go without saying, but then you never know.


Edit: My initial code actually still allowed map syntax and thus allowed the methods to be bypassed. This version is safer.

You can "derive" a type. In Go we just say declare. Then you define methods on your type. It just takes a very thin wrapper to provide the functionality you want. Note though, that you must call get and set with ordinary method call syntax. There is no way to keep the index syntax or optional ok result that built in maps have.

package main

import (
    "fmt"
    "strings"
)

type ciMap struct {
    m map[string]bool
}

func newCiMap() ciMap {
    return ciMap{m: make(map[string]bool)}
}

func (m ciMap) set(s string, b bool) {
    m.m[strings.ToLower(s)] = b
}

func (m ciMap) get(s string) (b, ok bool) {
    b, ok = m.m[strings.ToLower(s)]
    return
}

func main() {
    m := newCiMap()
    m.set("key1", true)
    m.set("kEy1", false)
    k := "keY1"
    b, _ := m.get(k)
    fmt.Println(k, "value is", b)
}