【问题标题】:How to select letters from a string until predefined point?如何从字符串中选择字母直到预定义点?
【发布时间】:2026-01-10 06:55:02
【问题描述】:

我想提取字符串中第一个元音之前的所有辅音,最好不使用正则表达式。例如,如果我有卡车这个词,我想提取“tr”,对于“street”,我想提取“str”。

我尝试了以下但收到错误wrong number of arguments (given 0, expected 1) for the execution of the block in vowels.

有人可以解释错误在哪里或建议更简单的方法吗?

vowels = ["a", "e", "i", "o", "u"]

def vowels(words)
  letters = words.split("")
  consonants = []
  letters.each do |letter|
    consonants << letter until vowels.include?(letter)
  end
  consonants
end

【问题讨论】:

    标签: ruby


    【解决方案1】:

    请注意,您的函数和数组具有相同的名称,vowels。这会引起混淆——您可能认为您在全局 vowels 数组上调用 include?,但实际上,您的代码尝试在没有参数的情况下递归调用您的同名函数,因此出现错误。由于 Ruby 允许不带括号的函数调用,Ruby 处理这一行:

    vowels.include?(letter)
    

    作为

    vowels().include?(letter) # <- wrong number of arguments!
    

    将您的函数名称更改为“元音”以外的名称会导致此错误,这更有意义:

    undefined local variable or method `vowels' for main:Object
    (repl):7:in `block in function_formerly_known_as_vowels'
    (repl):6:in `each'
    (repl):6:in `function_formerly_known_as_vowels'
    (repl):12:in `<main>'
    

    这导致了根本原因:破坏封装。 vowels 函数尝试访问全局范围内的状态。这是不好的做法(并且在此代码中不起作用,因为 vowels 数组实际上不是全局的——它需要在变量名上加上 $ 前缀,或者以其他方式使其在函数范围内可见——详见this answer)。相反,将元音数组作为参数传递给函数(并可能在过程中泛化函数),或者将其硬编码在函数本身内部,因为我们可以假设元音不会改变。

    解决了范围问题后,下一个问题是until 是一个循环。只要letter 是辅音,它就会被反复推入consonants 数组,直到程序内存不足。你的意思可能是unless。即使在这里,您也需要breakreturn 在找到元音后退出循环。

    最后,一些语义建议:vowels 不是一个非常准确的函数名称。它将辅音返回到第一个元音,所以这样命名!参数“words”可能具有误导性,因为它暗示了一个数组或一系列单词,而您的函数似乎只对一个单词(或字符串,一般来说)进行操作。

    这里是重写:

    def front_consonants(word)
      vowels = "aeiou"
      consonants = []
    
      word.each_char do |letter|
        break if vowels.include? letter
        consonants << letter
      end
    
      consonants.join
    end
    
    p front_consonants "stack" # => "st"
    

    【讨论】:

    • 关于作用域,即使数组和方法有不同的名称,代码也不会工作(因为作用域门)
    • 是的,有很多事情阻止了它的工作。试图让他们都在这里。
    • 顺便说一句,使用each_char 而不是chars.each。更好地扩展更多pastebin.com/y8KhCe2S
    • @ggorlen 感谢您详细而清晰的解释!完全有道理!
    【解决方案2】:

    考虑使用Enumerable#chunk

    VOWELS = ["a", "e", "i", "o", "u"]
    word = "truck"
    word.chars.chunk { |e| VOWELS.include? e }.first.last.join
    #=> "tr"
    

    第一部分返回

    word.chars.chunk { |e| VOWELS.include? e }.to_a #=> [[false, ["t", "r"]], [true, ["u"]], [false, ["c", "k"]]]
    

    【讨论】:

    • 如果单词以辅音开头,则有效。当单词以元音开头时获取空字符串:word.chars.chunk { |e| !VOWELS.include? e }.first.then{ |s| s.first ? s.last : [] }.join
    【解决方案3】:

    使用take_while 是语义化的。

    word.chars.take_while { |c| vowels.none? c }.join
    

    【讨论】:

      【解决方案4】:
      consonants << letter until vowels.include?(letter)
      

      最终只会一遍又一遍地推动同一个辅音(无限循环)。

      我会怎么做,reduce 使用 `break。

      如果您不熟悉,我建议您阅读这些内容

      https://apidock.com/ruby/Enumerable/reduce

      How to break out from a ruby block?

      # better to make this a constant
      Vowels = ["a", "e", "i", "o", "u"]
      
      def letters_before_first_vowel(string)
        string.chars.reduce([]) do |result, char|
          break result if Vowels.include?(char)
          result + [char]
        end.join
      end
      
      puts letters_before_first_vowel("truck")
      # => "tr"
      

      【讨论】:

      • 感谢您提供额外的解决方案!我接受了另一个答案,因为它看起来更直接。
      • @starisborn 是的,不用担心。当然有很多方法可以做事。我确实认为使用reduce 之类的东西更符合习惯,但是是的,仅使用each更简单
      【解决方案5】:

      您说“理想情况下不使用正则表达式”。抱歉,但我认为大多数 Ruby 主义者都会同意正则表达式是这里的首选工具:

      "whatchamacallit"[/\A[^aeiou]*/] #=> "wh"
      "apple"[/\A[^aeiou]*/]           #=> ""
      

      正则表达式读取,“匹配字符串的开头 (\A),后跟零个或多个 (*) 不是 (^) 元音的字符”,[^aeiou] 是一个 字符类

      【讨论】: