【问题标题】:Check if an element in the x position of an array is equal to something [duplicate]检查数组x位置的元素是否等于某物[重复]
【发布时间】:2015-12-11 18:37:43
【问题描述】:

我正在尝试创建一个将字符串作为输入的方法,如果字符串包含onetwothreefourfivesixseveneightnine 将它们计为整数并将它们相加,例如:

secret_code("one on one") #=> 2
secret_code("five monkeys in three farms") #=> 8
secret_code("") #=> 0
secret_code("fivethreeone") #=> 0

我想将给定的字符串按单词拆分并将它们放入一个数组中,以便我可以对其进行迭代并检查位置i 处的元素是否等于一个数字,并将其对应的值添加到一个变量中。到目前为止,这是我的代码:

def secret_code(a)
  arr = a.split(/\W+/)
  i = 0
  l = arr.size
  num = 0
  puts arr
  if !arr.any?
    0
  end
  while i < l do 
    case arr
      when arr[i] == "one"
        num += 1
        exit
      when arr[i] == "two"
        num += 2
        exit
      when arr[i] == "three"
        num += 3
        exit
      when arr[i] == "four"
        num += 4
        exit
      when arr[i] == "five"
        num += 5
        exit
      when arr[i] == "six"
        num += 6
        exit
      when arr[i] == "seven"
        num += 7
        exit
      when arr[i] == "eight"
        num += 8
        exit
      when arr[i] == "nine"
        num += 9
        exit
    end
    i+=1
  end
  puts num
end

secret_code("one on one") #=> 2
secret_code("five monkeys in three farms") #=> 8
secret_code("") #=> 0
secret_code("fivethreeone") #=> 0

我不太确定如何遍历数组。现在它为所有内容打印0

【问题讨论】:

  • 您的 case 语句似乎有一些缺陷 - 看看这个 - How to write case statement in Ruby - 不要使用 exit - 它会过早退出程序。如果你修复你的case 并摆脱exit,你会更正输出
  • 为什么急于选择答案?这样做可能会阻止其他有趣的答案,而且在您做出选择时,对于仍在研究他们的答案的读者来说,这是不体谅的。许多 SO 成员至少要等几个小时——有时甚至更长——才能做出选择。
  • 很多人要等几天。好的答案需要一段时间才能创建,而且回答的人分布在全球各地,或者每隔几天才上一次 Stack Overflow。
  • 基本问题在重复问题的选择答案中得到回答。这个问题的其余部分是如何匹配单词来决定什么是“数字词”,什么不是,这很容易用一个散列或可接受值的数组来完成。

标签: arrays ruby string loops


【解决方案1】:

我会这样做:

NUMBERS = {
  'one'   => 1, 'two'   => 2, 'three' => 3,
  'four'  => 4, 'five'  => 5, 'six'   => 6,
  'seven' => 7, 'eight' => 8, 'nine'  => 9
}

def secret_code(string)
  words = string.split(/\W+/)

  words.inject(0) do |sum, word|
    sum + NUMBERS.fetch(word, 0)
  end
end

puts secret_code('one on one')
#=> 2
puts secret_code('five monkeys in three farms')
#=> 8
puts secret_code('')
#=> 0
puts secret_code('fivethreeone')
#=> 0

NUMBERS.fetch(word, 0) 尝试从 NUMBERS 哈希中获取值,如果键 word 不存在,则返回 0。它基本上与以下内容相同:

if NUMBERS.key?(word)
  0
else
  NUMBERS[word]
end

inject(0) do |sum, word| 被初始化为0。它遍历数组中的每个元素并将当前值作为sum 传递给块。块的结果(sum + 单词的值)在下一次迭代中用作sum 的新值。

【讨论】:

    【解决方案2】:

    您的 case 语句是错误的,因为它包含 exit,在 Ruby 中会导致正在运行的脚本立即终止。

    您应该检查 case 语句的正确语法。

    此外,您可以使用scan 快速扫描您的字符串以查找匹配项。

    "five monkeys in three farms".scan(/five|three|four/)
     => ["five", "three"]
    

    然后您可以将匹配项转换为相应的值(例如,将映射存储在哈希中)并计算结果。

    numbers = {
        "three" => 3,
        "four" => 4,
        "five" => 5,
    }
    
    results = "five monkeys in three farms".scan(/#{numbers.keys.join("|")}/)
     => ["five", "three"]
    
    results.inject(0) { |total, result| total += numbers[result] }
    # => 8
    

    此解决方案有一个警告。它将匹配部分单词,因此如果适用,您将需要调整它以使用适当的边界考虑完整的单词(因此,如果有人可以发送您在示例中提供的最后一个字符串)。

    【讨论】:

      【解决方案3】:

      其他人已经回答了您的代码问题。这是另一种选择。

      str = "five monkeys in three farms"
      
      h = { "one=>1", "two"=>2, "three"=>3, "four"=>4, "five"=>5,
            "six"=>6, "seven"=>7, "eight"=>8, "nine"=>9 }
      
      str.gsub(/\w+/,h).split.map(&:to_i).reduce(:+)
        #=> 8
      

      这使用了Hash.gsub 的形式,它使用了一个散列。步骤:

      s = str.gsub(/\w+/,h)
        #=> "5   3 "
      

      不是h 的键的匹配被转换为空字符串。空格与\w+/ 不匹配,因此不受影响。

      t = s.split
        #=> ["5", "3"] 
      u = t.map(&:to_i)
        #=> [5, 3] 
      u.reduce(:+)
        #=> 8 
      

      【讨论】:

      • 对于初学者来说,这不是最容易解决的问题。你能补充一些解释吗?
      • 我同意它对于初学者来说可能很难阅读,并且详细说明它会有所帮助,但我投票 +1,因为它是一个非常聪明和好的实现。但是,重要的是要注意,它并不能真正回答用户遇到代码问题的原因。 ;)
      • 我总是尝试提供解释,但有时,就像这里一样,我单独展示代码,然后编辑以提供解释。
      【解决方案4】:

      问题出在这里:

      case arr
        when arr[i] == "one"
          num += 1
        # ...
      end
      

      case 语句中,您在每个when 之后给出的值将与您在case 之后给出的值进行比较。换句话说,您的case 语句将首先执行(arr[i] == "one") === arr,然后是(arr[i] == "two") === arr,依此类推。由于arr[i] == "one" 返回truefalse,并且true === arrfalse === arr 永远不会为真,因此您的case 语句中的任何条件都不会满足。

      最简单的解决方法是:

      case arr[i]
        when "one"
          num += 1
        when "two"
          num += 2
        when "three"
          num += 3
        # ...
      end
      

      此代码的作用是将arr[i] 的值与"one" 进行比较,然后是"two",依此类推,从而产生预期的行为。

      还要注意我删除了exit。在 Ruby 中,exit 退出程序。我猜你正试图像在其他编程语言中使用 break 一样使用它。这里不需要,因为 Ruby case 语句没有像 switch 在某些语言中那样的“失败”。在case 语句中只有一个when 匹配。

      更多改进

      我对您的代码还有一些注意事项:

      1. 这段代码没有做任何事情:

        if !arr.any?
          0
        end
        

        你不会对那个0 做任何事情,所以它会消失在虚空中。您可能打算使用return 0 尽早从该方法返回。一个更好的成语应该是这样的:

        return 0 if arr.empty?
        
      2. while 在这里是不必要的 在 Ruby 中,更常见的是使用 Enumerable#each 来遍历数组的元素:

        arr.each do |word|
          case word
            when "one"
              # ...
            # ...
          end
        end
        
      3. 您可以通过将case 语句视为返回值的表达式来使您的语句更加简洁:

        num += case arr[i]
          when "one"   then 1
          when "two"   then 2
          when "three" then 3
          # ...
          else 0
        end
        
      4. 您仍然可以通过使用 Hash 而不是 case 语句来使其更简洁:

        word_values = {
          "one"   => 1, "two"   => 2, "three" => 3,
          "four"  => 4, "five"  => 5, "six"   => 6,
          "seven" => 7, "eight" => 8, "nine"  => 9
        }
        word_values.default = 0
        
        arr.each do |word|
          num += word_values[word]
        end
        

        这会遍历单词数组并在word_values 哈希中查找每个单词。如果单词不在哈希中,它将返回默认值0

      5. 使用word_values 哈希,您还可以执行以下操作:

        values = arr.map {|word| word_values[word] }
        puts values.reduce(:+)
        

        这使用map 将单词数组转换为值数组,例如"five monkeys in three farms" 变为[5, 0, 0, 3, 0]。然后它使用reduce(也称为inject)来计算值的总和。

      现在都在一起

      将所有这些改进放在一起,我们就有了:

      WORD_VALUES = {
        "one"   => 1, "two"   => 2, "three" => 3,
        "four"  => 4, "five"  => 5, "six"   => 6,
        "seven" => 7, "eight" => 8, "nine"  => 9
      }
      WORD_VALUES.default = 0
      
      def secret_code(str)
        str.split(/\W+/)
          .map {|word| WORD_VALUES[word] }
          .reduce(:+) || 0
      end
      
      puts secret_code("one on one") # => 2
      puts secret_code("five monkeys in three farms") # => 8
      puts secret_code("") # => 0
      puts secret_code("fivethreeone") # => 0
      

      (在 Ruby 2.3 中,您实际上可以使用 .map(&amp;WORD_VALUES),这非常简洁。)

      这是另一种方法,效率略高:

      def secret_code(str)
        str.split(/\W+/).reduce(0) {|sum, word| sum + WORD_VALUES[word] }
      end
      

      它使用块形式的reduce 来查找散列中的每个值并将其添加到正在运行的sum

      另一种选择

      这有点“高级”,但展示了一些其他 Ruby 功能:

      WORD_VALUES = {
        "one"   => 1, "two"   => 2, "three" => 3,
        "four"  => 4, "five"  => 5, "six"   => 6,
        "seven" => 7, "eight" => 8, "nine"  => 9
      }
      
      WORD_EXPR = /\b#{Regexp.union(WORD_VALUES.keys)}\b/
      
      def secret_code(str)
        str.scan(WORD_EXPR).reduce(0) {|sum, word| sum + WORD_VALUES[word] }
      end
      

      这使用WORD_VALUES 数组的键来构建一个看起来(或多或少)如下所示的正则表达式:

      WORD_EXPR = /\b(one|two|three|four|five|six|seven|eight|nine)\b/
      

      然后它使用String#scan,它返回所有匹配正则表达式的子字符串,例如"five monkeys in three farms".scan(WORD_EXPR) 返回["five", "three"]。然后它使用上面的reduce 来查找它们的值并求和。

      【讨论】:

        【解决方案5】:

        Ruby 中有很多functions to search strings。您不必手动执行此操作,而且您很少会想要这样做。为此,您可以使用 String#scan 来查找单词。

        str.scan(/(\w+)/) { |word|
            ... do something with word[0] ...
        }
        

        然后你需要把这些词变成数字。 “Convert string numbers( in word format) to integer ruby”中提供了几种方法。对于生产代码,您应该use a gem,但由于这是一个扫描字符串的练习,最简单的方法是使用一个小表格。

        str = "one two five and zero"
        
        # Map words to their numbers.
        word2num = {
            "one" => 1, "two" => 2, "three" => 3, "four" => 4, "five" => 5,
            "six" => 6, "seven" => 7, "eight" => 8, "nine" => 9, "ten" => 10,
            "eleven" => 11, "twelve" => 12
        };
        # Because there are words which are not numbers, set the default
        # for the hash to 0.  If you try to put in a word that's not a
        # number you'll get 0 instead of nil (which would cause an error).
        word2num.default = 0
        
        number = 0;
        str.scan(/(\w+)/) { |word|
            number += word2num[word[0]]
        }
        puts number
        

        【讨论】:

        • 如果字符串是"eighteen monkeys",应该返回零?
        • @CarySwoveland 就像我说的,在真正的代码中你会使用 gem。这不是关于添加数字,我已经链接到另一个问题,它是关于教如何在 Ruby 中扫描字符串。
        猜你喜欢
        • 2021-06-20
        • 2022-08-22
        • 2023-04-02
        • 2016-05-19
        • 2021-02-03
        • 1970-01-01
        • 2014-12-19
        • 1970-01-01
        • 2015-03-26
        相关资源
        最近更新 更多