围棋练习之旅:rot13Reader

I'm trying to solve the Tour of Go exercise 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)
}

It returns You prnpxrq tur poqr!, that means only the first word of "Lbh penpxrq gur pbqr!" is cracked. How can I crack the whole sentence?

EDIT:

Basically your solution is good and works, you just mistyped 1 character:

case x >= 110 && x >= 122:

Change it to:

case x >= 110 && x <= 122:

Your input and output:

Lbh penpxrq gur pbqr!
You prnpxrq tur poqr!

There is 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 shifting goes outside of the letter range, you have to start from the beginning of the alphabet (or at the end). For example shifting 'N' would be 'Z' + 1, so it becomes 'A', the first letter. See this simple character mapping:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm

So what you should do is after shifting by 13, if the letter goes outside of the alphabet, shift it by -26 (number of letters in the English alphabet) which has the desired effect (that after the last letter you continued from the first).

An example solution:

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 it on the Go Playground.

The problem is that your function does not work as you want. To verify this, just try to run your function on "Lbh penpxrq Lbh gur pbqr!". As you see first word is decoded (as well as the third one). So this means that your function does not run only on the first word, but in fact runs on all the words (it just happened that nothing else is 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 here is a Playground. Code is taken from here.

My code also works, it's more simple!

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
}

As a follow up to the answer by @icza, rather than hard-coding the checks for whether the byte is alphabetic or not, create a small function to determine it instead. It makes for much cleaner code.

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
}

You could also 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]
}

I prefer manipulate directly integers in 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)
}
func rot13(x byte) byte {
    if (x >=  'A' && x <= 'M') || (x >=  'a' && x <= 'm') {
        return x + 13
    } else if (x >=  'N' && x <= 'Z') || (x >=  'n' && x <= 'z') {
        return x - 13
    } else {
        return x
    }
}

two years has already passed since the question 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
}

the result? You cracked the code. Of course it would be better to implement all ascii scale, but it still does most of the job.


EDIT: After a year and a half, a bugged edit by someone else, and a critic about that bug led me to edit this post. The edit still keeps the simplicity of the code while providing few extra conditionals. Moreover, one can turn this code into a rot13 encoder easily. Previous output was You-cracked-the-code.

func (reader rot13Reader) Read(bytes []byte) (int, error) {
    n, err := reader.r.Read(bytes)
    for i, val := range bytes[:n] {
        if val >= 'a' && val <= 'z' {
            bytes[i] = (val-'a'+13)%26 + 'a'
        } else if val >= 'A' && val <= 'Z' {
            bytes[i] = (val-'A'+13)%26 + 'A'
        }
    }
    return n, err
}

Here's 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)
}

Here are my two cents:

(Note that 'Z' ASCII code 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
}

My Try:

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)
}

My solution was 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
}