// Package cipher provides various cipher solutions.
package cipher
import (
"strings"
"unicode"
)
type cipher struct {
shift int
key string
}
const (
alphabetLength = 26 // length of the alphabet
posa = 97 // position of first lowercase char (a)
)
// NewCaesar constructs a Julius Caesar-style cipher.
func NewCaesar() Cipher {
return cipher{shift: 3}
}
// NewShift constructs a rotation cipher with shift set to distance.
func NewShift(distance int) Cipher {
if abs(distance) < 1 || abs(distance) > 25 {
return nil
}
return cipher{shift: distance}
}
// NewVigenere constructs a Vigenère substitution cipher.
func NewVigenere(key string) Cipher {
if len(key) == 0 {
return nil
}
if strings.Count(key, "a") == len(key) {
return nil
}
for _, ch := range key {
if !unicode.IsLower(ch) {
return nil
}
}
return cipher{key: key}
}
func (c cipher) Encode(input string) string {
shift := c.shift
if c.shift < 0 {
shift = alphabetLength + c.shift
}
input = strings.Map(func(r rune) rune {
if unicode.IsSpace(r) || unicode.IsDigit(r) || unicode.IsPunct(r) {
return -1
}
return r
}, input)
input = strings.ToLower(input)
out := strings.Builder{}
for i, ch := range input {
if c.shift == 0 {
shift = int(c.key[i%len(c.key)] - posa)
}
out.WriteRune((ch-posa+rune(shift))%alphabetLength + posa)
}
return out.String()
}
func (c cipher) Decode(code string) string {
shift := alphabetLength - c.shift
out := strings.Builder{}
for i, ch := range code {
if c.shift == 0 {
shift = int(alphabetLength - (c.key[i%len(c.key)] - posa))
}
out.WriteRune((ch-posa+rune(shift))%alphabetLength + posa)
}
return out.String()
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}