Question

I'm currently working on the Go Lang tutorial, but ran into problem with one of the exercises:

https://tour.golang.org/methods/23

The exercise has me implement a ROT13 cipher. I decided to implement the cipher using a map from a byte to its rotated value but I'm not sure of the best way to initialize this map. I don't want to initialize the map using a literal, but would prefer to do it programmatically by looping through an alphabet and setting (key, value) pairs within the loop. I would also like the map to only be accessible from Rot13Reader struct/object and have all instances(?) share the same map (rather than one copy per Rot13Reader).

Here's my current working Go program:

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

var rot13Map = map[byte]byte{}

func (rotr *rot13Reader) Read(p []byte) (int, error) {
    n, err := rotr.r.Read(p)
    for i := 0; i < n; i++ {
        if sub := rot13Map[p[i]]; sub != byte(0) {
            p[i] = sub
        }
    }
    return n, err
}

func main() {
    func() {
        var uppers = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
        var lowers = []byte("abcdefghijklmnopqrstuvwxyz")

        var init = func (alphabet []byte) {
            for i, char := range alphabet {
                rot13_i := (i + 13) % 26
                rot13Map[char] = alphabet[rot13_i]
            }
        }

        init(uppers)
        init(lowers)
    }()

    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

Here are the problems I have with this:

  • I don't want to have to prepare rot13Map in main()
  • I don't want rot13Map to be in global scope.
  • I don't want each copy of a rot13Reader to have a separate rot13Map

Is there a way to achieve what I want in Go?

Was it helpful?

Solution

In order to do this, I would make a rot13 package. You can programmatically create the map in an init() function and provide it as a package level global to all your rot13 decoders. The init function runs when your package is imported.

Because Rot13Reader is the only type in the package, it is the only one able to access your map.

WARNING: All code untested.

package rot13

import (
    "io"
)

var rot13Map = map[byte]byte{}

func init() {
    var uppers = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    var lowers = []byte("abcdefghijklmnopqrstuvwxyz")

    var init = func(alphabet []byte) {
        for i, char := range alphabet {
            rot13_i := (i + 13) % 26
            rot13Map[char] = alphabet[rot13_i]
        }
    }

    init(uppers)
    init(lowers)
}

type Reader struct {
    r io.Reader
}

func (rotr Reader) Read(p []byte) (int, error) {
    n, err := rotr.r.Read(p)
    for i := 0; i < n; i++ {
        if sub := rot13Map[p[i]]; sub != byte(0) {
            p[i] = sub
        }
    }
    return n, err
}

Obviously, you can't make another package in the go tour. You are stuck with rot13Map being accessible by main. You will need to run Go locally to get the separation you want.

OTHER TIPS

For the sake of completeness: For initialization work besides of the init function in a package there is sync.Once, which runs a supplied function only once.

You create an Once object and call Do with your function on it. As long as the state of the Once object is not changed, the supplied function will only be called once.

Example:

import "sync"

var readerInitOnce sync.Once

func (rotr *rot13Reader) Read(p []byte) (int, error) {
    readerInitOnce.Do(initRot13Map)
    ...
}

I would simplify your code and use an init function. For example,

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func newRot13Map() map[byte]byte {
    n := byte('Z' - 'A' + 1)
    rot13 := make(map[byte]byte, 2*n)
    for ltr := byte(0); ltr < n; ltr++ {
        sub := (ltr + 13) % n
        rot13[ltr+'A'] = sub + 'A'
        rot13[ltr+'a'] = sub + 'a'
    }
    return rot13
}

var rot13Map map[byte]byte

func init() {
    rot13Map = newRot13Map()
}

func (rotr *rot13Reader) Read(p []byte) (int, error) {
    n, err := rotr.r.Read(p)
    for i, ltr := range p[:n] {
        if sub, ok := rot13Map[ltr]; ok {
            p[i] = sub
        }
    }
    return n, err
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

Output:

You cracked the code!
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top