Статическая инициализация в Go?
В настоящее время я работаю над учебником Go Lang, но столкнулся с проблемой с одним из упражнений:
https://tour.golang.org/methods/23
В упражнении я использую шифр ROT13. Я решил реализовать шифр, используя карту от байт до ее повернутого значения, но я не уверен в том, что вы сможете инициализировать эту карту. Я не хочу инициализировать карту, используя литерал, но предпочел бы делать это программно, перейдя через алфавит и установив пары (ключ, значение) в цикле. Мне также хотелось бы, чтобы карта была доступна только из структуры/объекта Rot13Reader и имела все экземпляры (?) Совместно использовать одну и ту же карту (а не одну копию на Rot13Reader).
Здесь моя текущая работающая программа Go:
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)
}
Вот проблемы, которые у меня есть с этим:
- Я не хочу готовить
rot13Map
в main()
- Я не хочу, чтобы
rot13Map
находился в глобальной области.
- Я не хочу, чтобы каждая копия
rot13Reader
имела отдельный rot13Map
Есть ли способ достичь того, что я хочу в Go?
Ответы
Ответ 1
Чтобы сделать это, я сделал бы пакет rot13. Вы можете программно создать карту в функции init() и предоставить ее в качестве глобального уровня пакетов для всех ваших декодеров rot13. Функция init запускается при импортировании вашего пакета.
Поскольку Rot13Reader является единственным типом в пакете, он единственный, кто имеет доступ к вашей карте.
ПРЕДУПРЕЖДЕНИЕ: весь код не проверен.
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
}
Очевидно, что вы не можете сделать еще один пакет в туре. Вы застряли в том, что rot13Map доступен по умолчанию. Вам нужно будет запустить Go локально, чтобы получить желаемое разделение.
Ответ 2
Для полноты: для работы с инициализацией помимо функции init
в пакете есть sync.Once
, который запускает только один раз.
Вы создаете объект Once и вызываете Do
с помощью своей функции. Пока состояние Once
объект не будет изменен, функция, которая будет отправлена, будет вызываться только один раз.
Пример:
import "sync"
var readerInitOnce sync.Once
func (rotr *rot13Reader) Read(p []byte) (int, error) {
readerInitOnce.Do(initRot13Map)
...
}
Ответ 3
Я бы упростил ваш код и использовал функцию init
. Например,
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)
}
Вывод:
You cracked the code!