summaryrefslogtreecommitdiff
path: root/caesar.go
blob: 9e36d76e65f23a18bc27c015e87805f46d7eaa25 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Package caesar implements the Caesar cipher.
//
// The Caesar cipher is a basic monoalphabetic substitution cipher where each
// letter in the plaintext is shifted by a fixed distance to form the
// ciphertext. A ciphertext can be decrypted by reversing the shift.
package caesar

import (
	"strings"
)

// A RuneRange represents a range of runes that are to be shifted.
//
// Since runes encompass the entire UTF-8 space, we don't necessarily want to
// apply the Caesar shift to all runes in a plaintext lest we end up with
// unprintable or otherwise difficult characters.
type RuneRange struct {
	// Start represents the beginning of the range (inclusive).
	Start rune
	// End represents the end of the range (inclusive).
	End rune
}

// contains checks whether r is in the rune range.
func (rr RuneRange) contains(r rune) bool {
	return r >= rr.Start && r <= rr.End
}

// size calculates the size of the range.
func (rr RuneRange) size() int {
	return int(rr.End-rr.Start) + 1
}

// shift shifts r by d within the range, modulo the size of the range.
func (rr RuneRange) shift(r rune, d int) rune {
	pos := int(r - rr.Start)
	for d < 0 {
		d = rr.size() + d
	}
	newPos := (pos + d) % rr.size()
	return rr.Start + rune(newPos)
}

// A Coder encodes and decodes Caesar cipher messages according to its key and
// valid rune ranges.
//
// Its zero value doesn't do any shifting, so both Encode and Decode are simply
// the identity function.
type Coder struct {
	// Key determines the shift (+Key for encoding, -Key for decoding).
	Key int
	// Ranges determine which rune ranges should be shifted.
	Ranges []RuneRange
}

// Encode takes the message and returns a Caesar shifted version.
func (c Coder) Encode(msg string) string {
	return strings.Map(c.shifter(c.Key), msg)
}

// Decode takes a shifted message and returns a plaintext version.
func (c Coder) Decode(msg string) string {
	return strings.Map(c.shifter(-c.Key), msg)
}

type shiftFunc func(rune) rune

// shifter returns a shiftFunc for the coder's rune ranges.
func (c Coder) shifter(delta int) shiftFunc {
	return func(r rune) rune {
		for _, rr := range c.Ranges {
			if rr.contains(r) {
				return rr.shift(r, delta)
			}
		}
		return r
	}
}

// DefaultCoder is a Coder which uses the historical key of 3 and encodes
// uppercase and lowercase characters a-z and the digits 0-9.
//
// DefaultCoder is used by Encode and Decode.
//
// Define your own Coder to use custom rune ranges or keys.
var DefaultCoder = Coder{
	Key: 3,
	Ranges: []RuneRange{
		{Start: 'a', End: 'z'},
		{Start: 'A', End: 'Z'},
		{Start: '0', End: '9'},
	},
}

// Encode encodes msg using DefaultCoder.
func Encode(msg string) string {
	return DefaultCoder.Encode(msg)
}

// Decode decodes msg using DefaultCoder.
func Decode(msg string) string {
	return DefaultCoder.Decode(msg)
}