Tour of Go Exercise: rot13Reader
I am trying to follow the exercise Tour of Go rot13Reader :
Here is my solution:
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func rot13(x byte) byte {
switch {
case x >= 65 && x <= 77:
fallthrough
case x >= 97 && x <= 109:
x = x + 13
case x >= 78 && x <= 90:
fallthrough
case x >= 110 && x >= 122:
x = x - 13
}
return x
}
func (r13 *rot13Reader) Read(b []byte) (int, error) {
n, err := r13.r.Read(b)
for i := 0; i <= n; i++ {
b[i] = rot13(b[i])
}
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
Returns You prnpxrq tur poqr!
what only the first word means "Lbh penpxrq gur pbqr!" cracked How can I crack a whole sentence?
source to share
EDIT:
Basically your solution is good and works, you just typed 1 character:
case x >= 110 && x >= 122:
Change this to:
case x >= 110 && x <= 122:
Your entry and exit:
Lbh penpxrq gur pbqr!
You prnpxrq tur poqr!
There is a change in every word. The problem is not that only the first word is read and decoded, the problem is in your decoding algorithm.
In ROT13, if the offset is outside the range of letters, you must start at the beginning of the alphabet (or at the end). For example, the offset 'N'
will be 'Z'
+ 1, so it will become the 'A'
first letter. Take a look at this simple character mapping:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm
So what you have to do is, after the shift by 13, if the letter goes outside the alphabet, -26
it is by -26
(the number of letters in the English alphabet), which gives the desired effect (so that after the last letter you continue from the first).
Solution example:
func rot13(x byte) byte {
capital := x >= 'A' && x <= 'Z'
if !capital && (x < 'a' || x > 'z') {
return x // Not a letter
}
x += 13
if capital && x > 'Z' || !capital && x > 'z' {
x -= 26
}
return x
}
And its output:
You cracked the code!
Try this on the Go Playground .
source to share
My code also works, it's easier!
func (rd *rot13Reader) Read(b []byte) (int, error) {
m := make(map[byte]byte)
input := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
output := "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"
for idx := range input {
m[input[idx]] = output[idx]
}
n, err := rd.r.Read(b)
for i := 0; i < n; i++ {
if val, ok := m[b[i]]; ok {
b[i] = val
}
}
return n, err
}
source to share
You can use instead:
func rot13(x byte) byte {
input := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
output := []byte("NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm")
match := bytes.Index(input, []byte{x})
if match == -1 {
return x
}
return output[match]
}
source to share
The problem is that your function isn't working the way you want it to. To verify this, just try running your function on "Lbh penpxrq Lbh gur pbqr!"
. As you can see, the first word is decoded (as well as the third). So this means that your function doesn't only run on the first word, but actually runs on all words (it just happens that nothing has changed).
func (rot *rot13Reader) Read(p []byte) (n int, err error) {
n, err = rot.r.Read(p)
for i := 0; i < len(p); i++ {
if (p[i] >= 'A' && p[i] < 'N') || (p[i] >='a' && p[i] < 'n') {
p[i] += 13
} else if (p[i] > 'M' && p[i] <= 'Z') || (p[i] > 'm' && p[i] <= 'z'){
p[i] -= 13
}
}
return
}
And so . The code is taken from here .
source to share
I prefer to manipulate straight integers in the rot13 function
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
const a int = int('a')
const z int = int('z')
const A int = int('A')
const Z int = int('Z')
func rot13(b int) int {
isLower := b >= a && b <= z
isUpper := b >= A && b <= Z
if isLower {
return a + ((b - a + 13) % 26)
}
if isUpper {
return A + ((b - A + 13) % 26)
}
return b
}
func (rot *rot13Reader) Read(b []byte) (int, error) {
n, err := rot.r.Read(b)
if err == io.EOF {
return 0, err
}
for x := range b {
b[x] = byte(rot13(int(b[x])))
}
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
source to share
As an answer to a question from @icza, not hardcoding whether a byte is an alphabet or not, create a little function to determine it. This makes the code much cleaner.
func alphabetic(x byte) bool {
return (x >= 'A' && x <='Z') || (x >= 'a' && x <= 'z')
}
func capital(x byte) bool {
return x >= 'A' && x <= 'Z'
}
func rot13(x byte) byte {
// byte isn't a letter
if !alphabetic(x) {
return x
}
original_is_capital := capital(x)
// apply rot13
x += 13
// the new letter should loop around
if !alphabetic(x) || original_is_capital {
x -= 26
}
return x
}
source to share
It's been two years since the question was asked, but I want to share my code which was very simple compared to.
func (rot rot13Reader) Read(p []byte) (n int, err error) {
n, err = rot.r.Read(p) // read the string into byte array, n holds the length
for i := 0; i < n; i++ {
upper := p[i] >='A'||p[i]<='Z' //new
lower := p[i]>='a'||p[i]<='z' //new
if (upper||lower){ //new
p[i] += 13 // we are simply decoding here
if (upper && p[i] > 'Z') || (lower && p[i] > 'z') { //new
p[i] -= 26 // and semi-circle the table if it goes beyond
}
}
}
return
}
result? You cracked the code.
Of course, it would be better to implement the entire ASCII scale, but it still does most of the work.
EDIT : After a year and a half, editing this post resulted in someone else editing the bug. Editing still keeps the code simple by providing a few additional conditions. Moreover, one can easily turn this code into rot13 encoder. The previous output wasYou-cracked-the-code.
source to share
This is how I solved it:
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func rot13(r byte) byte {
sb := rune(r)
if sb >= 'a' && sb <= 'm' || sb >= 'A' && sb <= 'M' {
sb = sb + 13
}else if sb >= 'n' && sb <= 'z' || sb >= 'N' && sb <= 'Z' {
sb = sb - 13
}
return byte(sb)
}
func (r13 *rot13Reader) Read(b []byte) (i int, e error) {
n, err := r13.r.Read(b)
for i := 0; i <= n; i++ {
b[i] = rot13(b[i])
}
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
source to share
Here are my two cents:
(Note that the ASCII code "Z" is 90 and "h" is 114)
func (r rot13Reader) Read(p []byte) (n int, err error) {
n, err = r.r.Read(p)
if err == nil {
for i := range p {
if c := p[i] + 13; c > 'z' || (c > 'Z' && c < 'h') {
p[i] = c - 26
} else {
p[i] = c
}
}
}
return n, err
}
source to share
My attempt:
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
io.Reader
}
func (r rot13Reader) Read(p []byte) (n int, err error) {
n, _ = r.Reader.Read(p)
for i := 0; i < n; i++ {
if p[i] != byte(' ') {
if p[i] > byte('m') {
p[i] = p[i] - 13
} else {
p[i] = p[i] + 13
}
}
}
err = io.EOF
return
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
_, _ = io.Copy(os.Stdout, &r)
}
source to share
My solution was like this:
func rot13(b byte) byte {
in := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
out := []byte("NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm")
for i, v := range in {
if v == b {
return out[i]
}
}
return b
}
type rot13Reader struct {
r io.Reader
}
func (rot *rot13Reader) Read(b []byte) (int, error) {
n, err := rot.r.Read(b)
for i := range b {
b[i] = rot13(b[i])
}
return n, err
}
source to share