【问题标题】:Tour of Go exercise #23: rot13Reader围棋练习之旅 #23:rot13Reader
【发布时间】:2021-09-12 16:57:33
【问题描述】:

我正在尝试解决围棋练习rot13Reader

这是我的解决方案:

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

它返回You prnpxrq tur poqr!,这意味着只有“Lbh penpxrq gur pbqr!”的第一个单词已破裂。我怎样才能破解整个句子?

【问题讨论】:

  • 有人能解释一下为什么 Read 函数的第一行需要赋值吗?我使用 for range 循环遍历字节数组,然后尝试返回 len(b), nil 但它给了我一个超时错误。我还在做 r13.r.Read(b),只是没有在任何地方分配它。很困惑有什么区别。

标签: go


【解决方案1】:

编辑:

基本上你的解决方案很好而且有效,你只是输错了 1 个字符:

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

改成:

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

你的输入和输出:

Lbh penpxrq gur pbqr!
You prnpxrq tur poqr!

每个字都有变化。问题不在于只读取和解码第一个单词,问题在于您的解码算法。

ROT13 中,如果移位超出字母范围,则必须从字母表的开头(或结尾)开始。例如移动'N' 将是'Z' + 1,所以它变成'A',第一个字母。看看这个简单的字符映射:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm

所以你应该做的是在移动 13 之后,如果字母超出了字母表,将它移动 -26(英文字母表中的字母数),这具有预期的效果(在最后一个字母之后你从第一个开始)。

一个示例解决方案:

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
}

及其输出:

You cracked the code!

Go Playground 上试试。

【讨论】:

    【解决方案2】:

    您也可以使用:

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

    【讨论】:

    • 这很酷,但由于查找输入列表的时间复杂性,可能应该使用地图实现。但是,仅计算值仍然会更有效,如果您将 rot13 应用于巨大的字节流,则效率很重要。
    【解决方案3】:

    我更喜欢在 rot13 函数中直接操作整数

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

    【讨论】:

      【解决方案4】:

      问题是您的功能无法按您的意愿工作。要验证这一点,只需尝试在 "Lbh penpxrq Lbh gur pbqr!" 上运行您的函数。如您所见,第一个单词被解码(以及第三个单词)。所以这意味着你的函数不仅仅在第一个单词上运行,而是实际上在所有单词上运行(只是碰巧没有其他任何改变)。

      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
      }
      

      这是一个 Playground。代码取自here

      【讨论】:

        【解决方案5】:

        我的代码也可以,更简单!

        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
        }
        

        【讨论】:

          【解决方案6】:
          package main
          
          import (
              "io"
              "os"
              "strings"
          )
          
          type rot13Reader struct {
              r io.Reader
          }
          
          func (rot *rot13Reader) Read(b []byte) (int, error) {
              n, err := rot.r.Read(b)
              if err == io.EOF {
                  return 0, err
              }
          
              for i := range b {
                  var base byte
                  if b[i] >= 'a' && b[i] <= 'z' {
                      base = 'a'
                  } else if b[i] >= 'A' && b[i] <= 'Z' {
                      base = 'A'
                  } else {
                      continue
                  }
                  b[i] = (((b[i] - base) + 13) % 26) + base
              }
              return n, err
          }
          
          func main() {
              s := strings.NewReader("Lbh penpxrq gur pbqr!")
              r := rot13Reader{s}
              io.Copy(os.Stdout, &r)
          }
          
          

          【讨论】:

            【解决方案7】:
            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
                }
            }
            

            【讨论】:

              【解决方案8】:
              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
              }
              

              【讨论】:

              【解决方案9】:

              也投入我的 2 美分:

              https://play.golang.org/p/1Wze47kL-bA

              package main
              
              import (
                  "io"
                  "os"
                  "strings"
                  "unicode"
              )
              
              type rot13Reader struct {
                  r io.Reader
              }
              
              func (r rot13Reader) Read(buf []byte) (int, error) {
                  n, err := r.r.Read(buf)
              
                  if err != nil {
                      return n, err
                  }
              
                  for i := 0; i < n; i++ {
                      r := rune(buf[i])
                      if !unicode.IsLetter(r) {
                          continue
                      }
                      isUpper := unicode.IsUpper(r)
                      r += 13
                      if !unicode.IsLetter(r) || isUpper && r >= 'a' {
                          r -= 26
                      }
                      buf[i] = byte(r)
                  }
                  return n, err
              }
              
              func ExampleRot13Reader() {
                  s := strings.NewReader("Lbh penpxrq gur pbqr!")
                  r := rot13Reader{s}
                  io.Copy(os.Stdout, &r)
                  // Output: You cracked the code!
              }
              

              为诸如“仅提供代码作为答案没有多大帮助”之类的 cmets 做准备……这是提问者要求其他解决方案的特殊情况,这是比较各种方法的一种有趣方式。无需过多解释。

              【讨论】:

              • 嗨,mh-cbon。您没有正确复制我的代码。这就是我在你的操场上看到的。和上面不一样:``` func (r13 *zephaniaRot13Reader) Read(b []byte) (int, error) { size, e := r13.r.Read(b) for i := 0;我 byte('z') { continue } if b[i] > byte('a') { b[i] = byte('a') + (b[i]-byte('a' )+13)%26 } else { b[i] = byte('A') + (b[i]-byte('A')+13)%26 } } 返回大小,e } ``跨度>
              • 例如注意playground版本没有调用unicode.IsUpper()
              • 顺便说一句,这很有趣。我不确定我们是否遵循了典型的 Stack Overflow 最佳实践,但是当你玩得开心时为什么要停下来。
              • 确实,很抱歉我的这个错误。虽然,有趣是一回事,但这篇文章需要注意所有这些不同的方法。我已经更新了操场play.golang.org/p/HGqOFuzIgmu
              【解决方案10】:

              作为@icza 回答的后续行动,与其硬编码检查字节是否为字母,不如创建一个小函数来确定它。它使代码更简洁。

              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
              }
              

              【讨论】:

              • 我认为它会搞砸,因为将 13 添加到“Z”将导致“g”仍然是“字母”,但不是您所期望的(应该是“M”)
              • 这很有趣 - Golang 页面上的测试认为它是正确的,因为它没有对大写字母进行太多测试。我已经在playground 上更新了它
              【解决方案11】:

              我是这样解决的:

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

              【讨论】:

                【解决方案12】:

                我的解决方案是这样的:

                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
                }
                

                【讨论】:

                  【解决方案13】:

                  一个简单的代码 sn-p 改变字母并忽略其他字符

                  func (rot *rot13Reader) Read(b [] byte) (int, error) {
                      size,e := rot.r.Read(b)
                      for i := 0; i < size; i++ {
                          if b[i] < byte('A') || b[i] > byte('z') {
                              continue
                          }
                          if b[i] > byte('a') {
                              b[i] = byte('a') + (b[i] - byte('a') + 13)%26
                          } else {
                              b[i] = byte('A') + (b[i] - byte('A') + 13)%26
                          }
                      }
                      return size, e
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    这是我的两分钱:

                    (注意'Z' ASCII码是90,'h'是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
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      我的尝试:

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

                      【讨论】:

                      • 为什么会有负面反馈?
                      【解决方案16】:

                      自提出问题以来已经过去了两年,但我想分享我的代码,与它相比非常简单。

                      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
                      }
                      

                      结果? You cracked the code. 当然最好实现所有 ascii 规模,但它仍然可以完成大部分工作。


                      编辑:一年半后,由于其他人的错误编辑以及对该错误的批评,我编辑了这篇文章。编辑仍然保持代码的简单性,同时提供一些额外的条件。此外,可以轻松地将这段代码转换为 rot13 编码器。之前的输出是You-cracked-the-code.

                      【讨论】:

                      • 我认为这是最好的,除非有人需要重用 rot13 逻辑。
                      • 在 >Z 和 a 之间输入会失败(例如,对于像“_”这样的字符)。 (它也不编译,大概b应该是p)。
                      • @DaveC,从帖子的最后一句,您可以自己告诉它在您提到的时间间隔内不起作用。对于编译,是的,最后一个编辑器用户在尝试实现和间隔时犯了这个错误。既然你引起了我的注意,我可以实现“仅字母”条件,但它仍然保持优雅
                      • 这是个好方法,但代码不正确。在定义lowerupper 时,|| 应替换为&amp;&amp;
                      猜你喜欢
                      • 2023-03-30
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2020-03-16
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多