【问题标题】:Merge Ruby arrays合并 Ruby 数组
【发布时间】:2010-11-08 12:48:25
【问题描述】:

我有几个 UserInfo 类的 Ruby 对象数组:

class UserInfo  
    attr_accessor :name, :title, :age
end

如何将这些数组合并为一个数组?用户由其名称标识,因此我不希望有重复的名称。如果姓名、头衔、年龄等相等,我希望在新数组中有 1 个条目。如果名称相同,但任何其他细节不同,我可能希望不同数组中的这 2 个用户手动修复错误。

提前致谢

【问题讨论】:

  • 当您真正的意思是删除重复项时,您使用动词合并。当你询问数组时,你也用hash 标记了它。您可能应该编辑此问题以使其更清楚。
  • @Olly:将标签从“哈希”更改为“数组”。

标签: ruby arrays merge


【解决方案1】:

在您的对象上重新定义相等比较,您可以使用Array#uniq 快速消除实际重复项

class UserInfo
  attr_accessor :name, :title, :age

  def == other
    name==other.name and title==other.title and age==other.age
  end
end

# assuming a and b are arrays of UserInfo objects
c = a | b
# c will only contain one of each UserInfo

然后您可以按名称排序并查找仅名称重复项

d = c.sort{ |p,q| p.name <=> q.name } #sort by name
name = ""
e = []
d.each do |item|
  if item.name == name
    e[-1] = [e[-1],item].flatten 
  else
    e << item
  end
end

【讨论】:

    【解决方案2】:

    一年前的I monkey patchedObject 上的一种神秘的instance_variables_compare。我想你可以使用它。

    class Object
      def instance_variables_compare(o)
        Hash[*self.instance_variables.map {|v|
          self.instance_variable_get(v)!=o.instance_variable_get(v) ? 
          [v,o.instance_variable_get(v)] : []}.flatten]
      end
    end
    

    一个俗气的例子

    require 'Date'
    
    class Cheese
      attr_accessor :name, :weight, :expire_date
      def initialize(name, weight, expire_date)
        @name, @weight, @expire_date = name, weight, expire_date
      end
    end
    
    stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02"))
    gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
    

    irb 是我的首选武器

    >> stilton.instance_variables_compare(gorgonzola)
    => {"@name"=>"Gorgonzola", "@expire_date"=>#<Date: 4910305/2,0,2299161>}
    >> gorgonzola.instance_variables_compare(stilton)
    => {"@name"=>"Stilton", "@expire_date"=>#<Date: 4910275/2,0,2299161>}
    >> stilton.expire_date=gorgonzola.expire_date
    => #<Date: 4910305/2,0,2299161>
    >> stilton.instance_variables_compare(gorgonzola)
    => {"@name"=>"Gorgonzola"}
    >> stilton.instance_variables_compare(stilton)
    => {}
    

    如您所见,如果两个对象具有相同的内容,instance_variables_compare 返回一个空哈希。

    一系列奶酪

    stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02"))
    gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
    
    arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2
    

    一个没有问题,一个有问题

    h={}
    problems=Hash.new([])
    
    arr.each {|c| 
      if h.has_key?(c.name)
        if problems.has_key?(c.name)
          problems[c.name]=problems[c.name]<<c
        elsif h[c.name].instance_variables_compare(c) != {}
          problems[c.name]=problems[c.name]<<c<<h[c.name]
          h.delete(c.name)
        end
      else 
        h[c.name]=c
      end
    }
    

    现在哈希 h 包含没有合并问题的对象,而 problems 哈希包含具有不同实例变量的对象。

    >> h
    => {"Gorgonzola"=>#<Cheese:0xb375e8 @name="Gorgonzola", @weight=250, @expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>}
    
    >> problems
    => {"Stilton"=>[#<Cheese:0xf54c30 @name="Stilton", @weight=210, @expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 @name="Stilton", @weight=250,@expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}    
    

    据我所知,您根本不需要修改此代码来支持 UserInfo 对象数组。

    直接比较属性或覆盖== 可能会快得多。这就是你如何覆盖==

    def ==(other)
      return self.weight == other.weight && self.expire_date == other.expire_date
    end
    

    然后循环变成这个

    arr.each {|c| 
      if h.has_key?(c.name)
        if problems.has_key?(c.name)
          problems[c.name]=problems[c.name]<<c
        elsif h[c.name] != c
          problems[c.name]=problems[c.name]<<c<<h[c.name]
          h.delete(c.name)
        end
      else 
        h[c.name]=c
      end
    }
    

    最后,您可能希望将 Hash 转换回 Array

    result = h.values
    

    【讨论】:

    • 它主要是从旧博客文章中复制和粘贴。我也刚刚纠正了一个错误。
    【解决方案3】:

    这是另一种可能的方式。如果您有一种方法来识别每个 UserInfo,请说一个打印出值的 to_str 方法:

      def to_str()
        return "#{@name}:#{@title}:#{@age}"
      end
    

    你可以使用注入和哈希

    all_users = a + b # collection of users to "merge"    
    res = all_users.inject({})do |h,v|
      h[v.to_str] = v  #save the value indexed on the string output
      h # return h for the next iteration
    end
    
    merged = res.values #the unique users
    

    【讨论】:

      猜你喜欢
      • 2017-01-31
      • 1970-01-01
      • 2015-07-10
      • 1970-01-01
      • 2022-11-16
      • 2011-11-19
      • 1970-01-01
      • 2015-05-07
      • 1970-01-01
      相关资源
      最近更新 更多