在数组中搜索不存在的项将返回整个数组



我正在研究一个 ruby 模块,该模块与 2 维数组(从 csv 文件导入)中的第一个项目匹配,并返回第二个项目。这听起来非常简单,我能够让它工作,直到我尝试匹配不在数组中的项目。发生这种情况时,出于某种原因,将返回整个数组。我能够操纵一个变通方法,涉及一个布尔变量"找到",但我想知道为什么这不能像写的那样工作。

require 'csv'
class Nutrition
@list = CSV.read("./lib/list.csv")

def self.carbs(name)
 grams = @list.each do |item|
    if item[0] == name
      return item[1]
    end
  end
  if grams == nil
    grams = "error"
  end
  return grams    
end
end

列表.csv文件如下:

onion,13.75,0,0
carrot,11.375,0,0
cauliflower,19.375,0,0
cabbage,20.125,0,0
sw pepper,20,0,0
leek,7.5,0,0
mushroom,16.375,0,0
celery,33.25,0,0
apple,6.37,0,0
sweet potato,4.875,0,0
broccoli,14.8,0,0
red mill museli,1.52,0,0
mixed nuts,0,0,0.65
B. Sprouts,11,0,0
eggplant,16.66,0,0
quinoa,4.7,0,0
brown rice,4.33,0,0
sesame seed,0,0,4.5
sesame oil,0,0,4.655
pork chop,0,3.84,28.57
chick breast,0,3.22,27.77
lean turkey,0,4,100
ham,0,4.76,26.66

我编辑了我的原始代码,如下所示:

require 'csv'
class Nutrition
    include Enumerable
    @list = CSV.read("./lib/list.csv")

    def self.carbs(name)
      result = @list.detect {|item| item|0| == name}
      if result.nil?
        result = "error"
      end
      result    
    end
end

现在,当我使用以下测试文件运行 rake 测试时:

require './lib/nutrition.rb'
require "test/unit"
require 'csv'

class TestNutrition < Test::Unit::TestCase
  include Enumerable
  def test_carbs()
    assert_equal(Nutrition.carbs('onion'), "13.75")
    assert_equal(Nutrition.carbs('ham'),'0')
    assert_equal(Nutrition.carbs('sawdust'), 'error')
  end
end

我最终收到以下错误消息:

语法错误,意外 == (语法错误) 结果 = @list.检测 {|项| 项|0| == 名称} ^

但是,当我运行以下文件时,一切似乎都正常,我似乎无法通过耙子测试:

require 'csv'
class Nutrition
    include Enumerable
    @list = CSV.read("./lib/list.csv")

    def self.carbs(name)
      result = @list.detect {|item| item[0] == name}
      if result.nil?
        result = "error"
      end
      result    
    end
end
result = Nutrition.carbs('sawdust')
puts result

你应该使用 Enumerable#find,而不是遍历每个数组。如果未找到该元素,则返回值将为 nil。否则,您将通过查找将其返回。

def self.carbs(name)
  grams = @list.find {|item| item[0] == name}
  if grams.nil?
    grams = "error"
  end
  grams
end
即使

找不到该项目,也不会nil grams。但是,如果达到该测试,则可以确定无论如何都未找到该项,因为如果找到某些内容,该函数将已经返回。因此,此时,您可以返回错误并忘记测试grams

这不能按编写方式工作的原因是您将grams分配给在 @list 上调用 each 的返回值:grams = @list.each... 。 然后,您将在最后返回gramsreturn grams

each方法的返回值是原始数组,如文档中所述:http://ruby-doc.org/core-2.3.0/Array.html#method-i-each("返回数组本身。

在您的方法中,如果找到某个项目,则早期返回会阻止最后一行运行:return item[1]

正确缩进代码后,很容易理解为什么总是返回整个数组:

class Nutrition
    def self.carbs(name)
        grams = @list.each do |item|
            if item[0] == name
                return item[1]
            end
        end
        if grams == nil
            grams = "error"
        end
        return grams    
    end
end

如果name从未匹配,那么self.carbs执行的最后一个语句将是return grams,这当然会返回整个数组。

编辑 另外,我建议对您的设计进行一些更改。

@变量是实例变量(这里有一篇很好的SO帖子,解释了大多数Ruby变量之间的差异),因此在类(即静态)方法(self.carbs)中使用@list可能没有意义。您可以将carbs定义为实例方法,也可以list定义为类变量 ( @@list )。

我使用前一个选项进行了一些重构,并将 each 循环替换为 find

class Nutrition
    def initialize
        @list = CSV.read("./lib/list.csv")
    end
    def carbs(name)
        return "error" if @list.nil?
        carb = (@list.find { |x| x[0] == name })
        carb.nil? ? "error" : carb[1]
    end
end

使用后一个选项:

class Nutrition
    @@list = CSV.read("./lib/list.csv")
    def self.carbs(name)
        return "error" if @list.nil?
        carb = (@@list.find { |x| x[0] == name })
        carb.nil? ? "error" : carb[1]
    end
end

最新更新