0

所以我对 Ruby 还是很陌生,还在学习习语。我仍然有很强的静态打字思维,所以也许一个问题是我打字过多。反正我的情况是这样的。

我有一个名为 Gene 的对象,带有 :name 和 :id。我有另一个称为基因型的对象,它维护着一个基因数组。

我想检查给定的基因型是否包含给定的基因。我希望能够通过 Genotype.has_gene?基因名称、基因 ID 或实际基因。在前一种情况下,例程将匹配名称或 id,以通过者为准。如果传递了完整的基因,则例程将坚持两个值都匹配。

我的逻辑是检查传递的值是否是 Integer,在这种情况下我假设它是一个 id;否则检查它是否是一个字符串并假设它是一个名称;否则检查它是否是一个基因;否则投诉和保释。

代码如下所示:

def has_gene?( gene )
      if gene.is_a? Integer
        id = gene
        name = ""
      elsif gene.is_a? String
        id = nil
        name = gene
      elsif gene.is_a? Gene
        id = gene.id
        name = gene.name
      else
        raise "Can't intepret passed data as gene information"   
      end
      name_valid = false
      id_valid = false
      @gene_specs.each do |current_gene_spec|
        current_gene = current_gene_spec.gene 
        name_valid = name.empty? || name == current_gene.name
        id_valid = id.nil? || id == current_gene.id
        break if name_valid && id_valid
       end
       return name_valid && id_valid
    end

这里感觉有些不对劲,但我无法确定。它似乎缺乏 Ruby 著名的简洁性:-)

想法?

4

5 回答 5

3

Ruby 具有将值强制转换为基本对象的方法,例如Kernel#Array。我们可以使用它来将一个值强制转换为一个合理的数组:

Array(nil) # => []
Array(10) # => [10]
Array("hello") # => ["hello"]
Array([1, 2, 3]) # => [1, 2, 3]

这使我们能够编写非常 Ruby 式的方法,这些方法在输入类型方面非常灵活,而无需花费大量空间检查类型。考虑这个人为的例子:

def say_hello(people)
  people = [people] if people.is_a?(Person)

  people.each { |p| puts "Hello, #{p.name}" }
end

def say_hello(people)
  Array(people).each { |p| puts "Hello, #{p.name}" }
end

对于您的情况,我建议添加一个类方法来进行强制,而不是添加一个Gene方法:Kernel

class Gene
  def self.coerce(geneish)
    case geneish
    when Gene
      geneish
    when Integer
      new(id: geneish)
    when String
      new(name: geneish)
    else
      raise ArgumentError, "Can't coerce #{geneish.inspect} into a Gene"
    end
  end
end

def has_gene?(gene)
  gene = Gene(gene)

  name_valid = false
  id_valid = false
  @gene_specs.each do |current_gene_spec|
    current_gene = current_gene_spec.gene 
    name_valid = gene.name.empty? || gene.name == current_gene.name
    id_valid = gene.id.nil? || gene.id == current_gene.id
    break if name_valid && id_valid
  end
  return name_valid && id_valid
end

has_gene?尽管这里的其他几个答案对使用某些Enumerable方法来清理它有很好的建议,但我已经完整地保留了您方法的其余部分。

于 2013-12-10T20:32:37.683 回答
1

这是我将如何简化它。如果你愿意,你也可以使用鸭式打字,但我认为这会使代码更复杂。

def genes
  @gene_specs.collect &:gene
end

def has_gene?(x)
  case x
  when Integer
    genes.any? { |g| g.id == x }
  when String
    genes.any? { |g| g.name == x }
  when Gene
    genes.include?(x)   # assumes that Gene#== is defined well
  else
    raise ArgumentError, "Can't intepret passed data as gene information" 
  end
end

默认情况下,Ruby 将按身份比较对象(即它们在内存中的位置),但是对于 Gene 类,您可能想要做一些不同的事情,如下所示:

class Gene
  def ==(other)
    return false unless other.class == Gene
    id == other.id
  end
end

花一些时间研究Ruby 的 Enumerable 模块中的方法是值得的。

于 2013-04-17T15:25:59.540 回答
1

尽管 ruby​​ 不强制方法参数的类型,但除非有充分的理由,否则允许一个参数具有多种类型仍然是一种不好的做法。如果您提供三种不同的方法会更清楚:

def has_gene?( gene)
  ...
end

def has_gene_with_id?( id)
  ...
end

def has_gene_with_name?( name)
  ...
end
于 2013-04-17T15:17:37.983 回答
0

Ruby 不仅是动态类型的,而且它遵循鸭子类型范式,其中指出:

当我看到一只像鸭子一样走路、像鸭子一样游泳、像鸭子一样叫的鸟时,我称那只鸟为鸭子。

这是什么意思?这意味着在 Ruby 中,您不应该真正关心(除非在特定情况下) Object 是否属于确切的X类。你应该关心它是否表现得像一个X对象。

你如何检查它?检查特定对象的行为是否如您所愿的最著名的方法是使用#respond_to?. 此方法将检查是否可以在对象上调用方法。

#to_x从那里您可以检查对象是否以类的名称(甚至自定义类)的形式响应方法,x然后调用它以将任何类型转换为您需要的类。因此,例如,如果您希望在方法中只使用一个字符串,您可以这样做:

def a_method( string )
    unless (string.respond_to? :to_str) // trigger error
    string = string.to_str
    // use string
end

这样,如果我要定义一种特殊类型Duck,例如:

class Duck

    def to_str
        // internally convert Duck to String
    end

    ...

end

我可以将它传递给您的函数:

obj = Duck.new
a_method( obj )

它会像我和 的设计师所期望的那样工作a_method,甚至彼此都不认识。

于 2013-04-17T15:19:58.680 回答
0

您可以编写一个接受块的方法,允许该类的用户指定要查找的内容:

Gene = Struct.new(:name, :id)
class Genotype
  def initialize
    @arr=[]
  end
  def add(gene)
    @arr << gene
  end
  def any?(&block)
    @arr.any?(&block)
  end
end

gt = Genotype.new
gt.add Gene.new('a',0)
gt.add Gene.new('b',1)
p gt.any?{|g| g.name == "john"} #false
p gt.any?{|g| g.values == ["b",1]} #true
于 2013-04-17T16:23:50.063 回答