Ruby:如何通过谓词单次迭代拆分集合为两个集合?Hash是否支持?
集合按谓词单次迭代拆分的可读实现及Hash支持情况
需要将单个集合按指定谓词拆分为两个集合——一个包含符合谓词的元素,另一个包含不符合的元素,要求仅进行一次迭代且避免不必要的临时拷贝。目前已有如下可行实现:
irb> aaa = [ 1, 2, 3, 4 ,5, 6, 7, 8, 9 ]; bbb = []; irb> aaa.reject!{|n| bbb<<n unless n.odd? } irb> bbb # => [2, 4, 6, 8] irb> aaa # => [1, 3, 5, 7, 9]
但该实现可读性不足,同时希望了解Hash是否支持此类操作。
更可读的数组拆分方案
Ruby的Enumerable#partition方法正是为这类场景设计的,它会单次迭代集合,将元素按谓词条件拆分为两个数组返回,代码简洁易懂,完全符合单次迭代的要求:
aaa = [1, 2, 3, 4, 5, 6, 7, 8, 9] yes_set, no_set = aaa.partition(&:odd?) yes_set # => [1, 3, 5, 7, 9](符合谓词的元素) no_set # => [2, 4, 6, 8](不符合谓词的元素)
如果需要原地修改原数组(像原实现那样将符合条件的元素留在原数组,不符合的移到新数组),可以用更清晰的写法替代原reject!逻辑:
aaa = [1, 2, 3, 4, 5, 6, 7, 8, 9] no_set = aaa.select!(&:odd?).then { aaa - _1 }
若严格要求无临时拷贝,原实现的逻辑本质上是最优的,但可以通过提取方法来大幅提升可读性:
def split_in_place(collection, predicate) rejected = [] collection.reject! { |item| rejected << item unless predicate.call(item) } [collection, rejected] end aaa = [1, 2, 3, 4, 5, 6, 7, 8, 9] yes_set, no_set = split_in_place(aaa, ->(n) { n.odd? })
Hash的拆分实现
Hash同样属于Enumerable,因此可以直接使用partition方法,之后将拆分出的数组转换为Hash即可:
my_hash = { a: 1, b: 2, c: 3, d: 4 } yes_hash, no_hash = my_hash.partition { |_key, value| value.odd? }.map(&:to_h) yes_hash # => {:a=>1, :c=>3} no_hash # => {:b=>2, :d=>4}
也可以用group_by方法按谓词分组,再分别转换为Hash,同样是单次迭代完成:
my_hash = { a: 1, b: 2, c: 3, d: 4 } grouped = my_hash.group_by { |_key, value| value.odd? } yes_hash = grouped[true].to_h no_hash = grouped[false].to_h
内容的提问来源于stack exchange,提问作者Toni Schilling
相关产品推荐
相关产品推荐

