Ruby:想要一个保存顺序的类似Set的对象

时间:2009-04-21 16:11:08

标签: ruby arrays set

...或者是一个防止重复输入的数组。

Ruby中是否存在某种对象:

  • 回复[],[] =和<<
  • 默默删除重复的条目
  • 是可枚举的(或者至少支持find_all)
  • 保留插入条目的顺序

据我所知,数组支持第1,3和4点;而Set则支持1,2和3(但不支持4)。并且SortedSet不会这样做,因为我的条目没有实现< =>。

5 个答案:

答案 0 :(得分:12)

从Ruby 1.9开始,内置的Hash对象保留了插入顺序。例如:

h = {}
h[:z] = 1
h[:b] = 2
h[:a] = 3
h[:x] = 0
p h.keys     #=> [:z, :b, :a, :x]

h.delete :b
p h.keys     #=> [:z, :a, :x]

h[:b] = 1
p h.keys     #=> [:z, :a, :x, :b]

因此,您可以为任何键设置任何值(如简单true),现在您有了一个有序集。您可以使用h.key?(obj)来测试密钥,或者如果您始终将每个密钥设置为真值,则只需h[obj]。要删除密钥,请使用h.delete(obj)。要将有序集转换为数组,请使用h.keys

由于Ruby 1.9 Set library恰好是基于Hash构建的,因此您当前可以将Set用作有序集。 (例如,to_a方法的实现仅为@hash.keys。)但请注意,该库此行为无法保证,并且可能改变未来。

require 'set'
s = Set[ :f, :o, :o, :b, :a, :r ]  #=> #<Set: {:f, :o, :b, :a, :r}>
s << :z                            #=> #<Set: {:f, :o, :b, :a, :r, :z}>
s.delete :o                        #=> #<Set: {:f, :b, :a, :r, :z}>
s << :o                            #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s << :o                            #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s << :f                            #=> #<Set: {:f, :b, :a, :r, :z, :o}>
s.to_a                             #=> [:f, :b, :a, :r, :z, :o]

答案 1 :(得分:6)

据我所知,没有一个,并且其数学性质的Set意味着无序(或至少,在实现上,意味着不保证顺序 - 事实上它通常被实现为哈希表,所以它确实陷入困境)。

但是,直接扩展数组或将其子类化以实现此目的并不困难。我刚试了一下,这很有效:

class UniqueArray < Array
  def initialize(*args)
    if args.size == 1 and args[0].is_a? Array then
      super(args[0].uniq)
    else
      super(*args)
    end
  end

  def insert(i, v)
    super(i, v) unless include?(v)
  end

  def <<(v)
    super(v) unless include?(v)
  end

  def []=(*args)
    # note: could just call super(*args) then uniq!, but this is faster

    # there are three different versions of this call:
    # 1. start, length, value
    # 2. index, value
    # 3. range, value
    # We just need to get the value
    v = case args.size
      when 3 then args[2]
      when 2 then args[1]
      else nil
    end

    super(*args) if v.nil? or not include?(v)
  end
end

似乎涵盖了所有基础。我使用了OReilly的方便的Ruby Cookbook作为参考 - 他们有一个“确保排序数组保持排序”的配方,类似。

答案 2 :(得分:6)

我喜欢这个解决方案,虽然它需要active_support的OrderedHash

require 'active_support/ordered_hash'

class OrderedSet < Set

  def initialize enum = nil, &block
    @hash = ActiveSupport::OrderedHash.new
    super
  end

end

=)

答案 3 :(得分:1)

您可以使用哈希来存储值,并在每个哈希对的值中存储增量值。然后,您可以通过按值访问对象,以有条理的方式访问集合。

我稍后会尝试添加一些代码以进一步解释。

我知道通过值访问比按键要慢得多。

更新1:在Ruby 1.9中,Hash元素按其插入顺序进行迭代。

答案 4 :(得分:0)

不是我所知道的,但是推出自己的并不难。只需子类Array并使用Set来维护唯一性约束。

关于沉默掉落的一个问题。这将如何影响#[] =?如果我试图用已经存储在其他地方的东西覆盖现有条目,那么它是否应该删除想要删除的元素?我认为无论哪种方式都可以提供令人讨厌的惊喜。