Ruby中是否存在兼具Set集合运算能力和Hash成员访问能力的集合类?
读完Peter Jones所著《Effective Ruby》中的Collections章节后,我发现Set类似乎是Hash的一个不错的替代选择。关于Set类,Jones是这样描述的:
它和数学中的集合概念非常相似,是无序、元素唯一的集合,支持并集、交集、子集/超集判断等操作。
我唯一的顾虑是Set类可能不支持直接访问内部存储的成员对象。
目前我正尝试使用Set(或类似Set的结构)实现一个简易的OptionSet API,OptionSet中的选项可以是Option或ValueOption类型,其中后者是Option的子类。该API需要访问每个OptionSet对应集合中存储的对象,无论这些对象是存放在独立的Hash、Array或其他容器中,还是通过继承(比如以Set作为父类)的方式存放在OptionSet内部。
我希望在OptionSet内部使用Set,或者直接让OptionSet继承自Set。但如果Set在存储元素后无法直接访问成员对象,只能遍历所有成员查找匹配项(比如查找集合中Option.name(通常为symbol类型)相等的Option或ValueOption对象),是否存在更高效的替代方案?
我本想提供示例代码,但目前OptionSet的实现还无法运行。伪代码层面,我需要实现OptionSet#getopt(name)方法,返回存储在OptionSet中指定名称的Option对象的值,若不存在对应选项则返回false。
#getopt方法会调用受保护方法self.getoptObj(name),该方法需要访问集合中指定名称的Option对象本身。除了这部分实现需求外,Set本身完全可以满足其他使用要求。
类似Scheme类语言中的AssociativeList类,Ruby标准库本身似乎没有提供对应的实现?简单来说,我想知道是否存在类似Set的类——即拥有Set的集合论操作方法——同时具备Hash的成员访问能力?
更新
我已经尝试在伪代码层面实现MappedSet类,通过一个实例级Hash存储通用“键”和成员对象的映射关系。但我认为这和Set的内部存储逻辑冗余,或许我应该直接扩展Hash类?
Ruby标准库确实没有内置同时具备Set集合操作能力与Hash按键快速查找能力的通用类,你可以根据自身需求从以下两种成熟方案中选择:
方案1:基于Hash扩展,无冗余存储
如果你需要的集合操作不多,希望完全避免存储冗余,可以直接扩展Hash类,按需实现需要的集合操作即可,查找效率为O(1),实现逻辑如下:
class KeyedSet < Hash # 初始化可指定提取键的属性,默认用name def initialize(key_attr = :name, elements = []) super() @key_attr = key_attr elements.each { |elem| add(elem) } end def add(element) key = element.send(@key_attr) self[key] = element end alias << add # 并集操作 def union(other) raise ArgumentError, "参数必须为KeyedSet实例" unless other.is_a?(KeyedSet) self.class.new(@key_attr, self.values + other.values) end alias | union # 交集操作 def intersection(other) raise ArgumentError, "参数必须为KeyedSet实例" unless other.is_a?(KeyedSet) common_keys = self.keys & other.keys self.class.new(@key_attr, common_keys.map { |k| self[k] }) end alias & intersection # 子集判断 def subset?(other) raise ArgumentError, "参数必须为KeyedSet实例" unless other.is_a?(KeyedSet) self.keys.all? { |k| other.key?(k) } end # 需求API实现 def getopt(name) key?(name) ? self[name].value : false end protected def getoptObj(name) self[name] end # 其他需要的集合操作(差集、超集判断等)都可以按相同逻辑扩展 end
这个方案的优势是没有任何存储冗余,性能最优,灵活性最高。
方案2:继承标准库Set类,复用现有集合操作
如果你需要用到Set的所有集合操作,不想自己重复实现,可以直接继承Set类,额外维护一个属性到元素的索引映射即可:
require 'set' class OptionSet < Set def initialize(elements = []) super() @name_index = {} elements.each { |elem| add(elem) } end def add(element) super(element) @name_index[element.name] = element end alias << add def delete(element) super(element) @name_index.delete(element.name) end def getopt(name) @name_index.key?(name) ? @name_index[name].value : false end protected def getoptObj(name) @name_index[name] end end
这个方案虽然多维护了一个Hash索引,但对于选项集合这种量级的数据,性能损耗完全可以忽略,同时你可以直接使用Set原生的所有集合操作,不需要自己实现,开发成本最低。
内容的提问来源于stack exchange,提问作者Sean Champ

