【问题标题】:Whats the 'Ruby way' to identify duplicate values in a hash?识别哈希中重复值的“Ruby方式”是什么?
【发布时间】:2021-03-15 17:08:47
【问题描述】:

我在 macOS Catalina 上使用 Ruby 2.7.x。

我有多达 100 万个键值元组。 键是字符串,保证唯一。 这些值是字符串,可能包含重复项(或三次或更多)。 鉴于键的唯一性,散列似乎是它们的一种自然数据结构。 所以如果我从 original_hash 开始,包含所有的键值元组, 我想以 uniques_hash 结尾,包含所有且唯一的键值元组, 和 duplicates_hash,包含所有具有重复值的键。

我对优化清晰度和 Ruby 习惯比内存效率或速度更感兴趣 - 我不希望经常运行此代码,而且我有足够的 RAM。

如果我转换为两个数组,我可以在 values 数组中找到唯一的 - 但我如何保证与正确的键重新配对?这是解决这个问题的正确方法吗?

非常感谢您的帮助!

【问题讨论】:

  • 您有任何代码或您正在查看的数据示例吗?
  • 请举个例子。如果您的哈希是{ :a=>1, :b=>1},您希望您的uniques_hash 等于{ :a=>1}{:b=>1}
  • jad,Cary - 感谢您的参与!键是文件路径,例如photos/3 May 2009/IMG_4588.jpg 等等。我通过遍历目录结构来生成文件路径列表,所以我知道它们是独一无二的。这些值是十六进制摘要,由 Digest::SHA512.hexdigest File.read 从文件路径生成。因此,如果我的目录结构包含同一张照片的 2 个或更多副本,我将有重复的值。我想要做的是(最终)将目录结构复制到一个包含每个文件的副本的目录结构,以及另一个只包含重复副本的目录结构。 @Cary - 我不在乎哪一个!

标签: arrays ruby hash


【解决方案1】:

假设

original_hash = {:a=>1, :b=>2, :c=>2, :d=>2, :e=>3, :f=>4, :g=>4}

如果您只对返回包含唯一值的哈希 uniques_hash 感兴趣,您可以编写以下代码。

uniques_hash = original_hash.invert.invert
  #=> {:a=>1, :d=>2, :e=>3, :g=>4}

中间步骤是

original_hash.invert
  #=> {1=>:a, 2=>:d, 3=>:e, 4=>:g}

Hash#invert。请注意,定义的uniques_hash 本身并不是唯一的。它可以是以下任何一种。

{:a=>1, :b=>2, :e=>3, :f=>4}
{:a=>1, :b=>2, :e=>3, :g=>4}
{:a=>1, :c=>2, :e=>3, :f=>4}
{:a=>1, :c=>2, :e=>3, :g=>4}
{:a=>1, :d=>2, :e=>3, :f=>4}
{:a=>1, :d=>2, :e=>3, :g=>4}

另一种方法是使用Enumerable#uniqArray#to_h

unique_hash = original_hash.uniq(&:last).to_h
  #=> {:a=>1, :b=>2, :e=>3, :f=>4} 

中间计算是

original_hash.uniq(&:last)
  #=> [[:a, 1], [:b, 2], [:e, 3], [:f, 4]]

这是的简写

original_hash.uniq { |_k,v| v }

推测duplicates_hash 的每个键都是original_hash 中的一个值,而该键的值是original_hashkoriginal_hash[k] == v 中的那些键的数组。

计算duplicates_hash的一种方法如下。

duplicates_hash = original_hash.each_with_object({}) do |(k,v),h|
  h[v] = (h[v] || []) << k
end
  #=> {1=>[:a], 2=>[:b, :c, :d], 3=>[:e], 4=>[:f, :g]}

这个也可以写

duplicates_hash = original_hash.
  each_with_object(Hash.new { |h,k| h[k] = [] }) { |(k,v),h| h[v] << k }
  #=> {1=>[:a], 2=>[:b, :c, :d], 3=>[:e], 4=>[:f, :g]}

Hash::new。两种形式都等价于

duplicates_hash = original_hash.each_with_object({}) do |(k,v),h|
  h[v] = [] unless h.key?(v)
  h[v] << k
end

将块变量写为|(k,v),h| 使用array decomposition

我们有一个枚举器,它将生成值并将它们传递给它的块。

enum = original_hash.each_with_object({})
  #=> #<Enumerator: {:a=>1, :b=>2, :c=>2, :d=>2, :e=>3, :f=>4, :g=>4}:
  #     each_with_object({})> 

枚举器是 Enumerator 类的实例。

生成枚举器的第一个值,并为块变量赋值,如下所示:

(k,v),h = enum.next
  #=> [[:a, 1], {}]

可以看到数组分解将这个包含两个元素的数组拆分如下:

k #=> :a 
v #=> 1 
h #=> {} 

注意左边的括号如何对应右边的内括号。然后使用这些变量执行块计算。

h[v] = (h[v] || []) << k
  #=> [:a]

现在,

h #=> {1=>[:a]}

然后由枚举器生成下一个值并进行块计算。

(k,v),h = enum.next
  #=> [[:b, 2], {1=>[:a]}] 
k #=> :b 
v #=> 2 
h #=> {1=>[:a]} 
h[v] = (h[v] || []) << k

现在

h #=> {1=>[:a], 2=>[:b]} 

这样一直持续到

enum.next
  #=> Stop Interation (exception)

导致 Ruby 返回 h 的值。


请注意,通过首先计算duplicates_hash,我们可以计算uniques_hash,如下所示。

keeper_keys = duplicates_hash.values.map(&:first)
  #=> [:a, :b, :e, :f] 
unique_keys = original_hash.slice(*keeper_keys)
  #=> {:a=>1, :b=>2, :e=>3, :f=>4} 

unique_keys = original_hash.slice(*duplicates_hash.values.map(&:first))
  #=> {:a=>1, :b=>2, :e=>3, :f=>4}

Hash#slice。如果某人因偏爱某些键而感到内疚,则可以改写

unique_keys = original_hash.slice(*duplicates_hash.values.map(&:sample))
  #=> {:a=>1, :b=>2, :e=>3, :g=>4}

Array#sample

【讨论】:

  • 卡里,非常感谢。双重反转很聪明,我将完成其余的工作。很明显,我是这方面的初学者!
【解决方案2】:

这可能是也可能不是解决此问题的最佳方法,但我使用了“group_by”和“select”函数为我获取了一个新的哈希来查找重复项:

hash.group_by{|k,v| v}.select{|k,v| v.count > 1}

在这种情况下,返回的哈希看起来有点像:

{value: [{key: value}, {key: value}]}

【讨论】:

  • jad - 超级棒,看起来 'group_by' 让我到达了我想去的地方!
【解决方案3】:

使用group_by 计算值并将结果存储在哈希中。使用该散列到 partition 原始散列,如下所示:

h = {a: 1, b: 2, c: 2, d: 2, e: 3, f: 4, g: 4}

cnt = Hash[h.values.group_by{ |i| i }.map { |k, v| [k, v.count] }]
h_uniq, h_dups = h.partition{ |k, v| cnt[v] == 1 }.map(&:to_h)

puts cnt
# {1=>1, 2=>3, 3=>1, 4=>2}

puts h_uniq.inspect
# {:a=>1, :e=>3}

puts h_dups.inspect
# {:b=>2, :c=>2, :d=>2, :f=>4, :g=>4}

【讨论】:

    【解决方案4】:

    感谢大家对此的帮助!我认为如果我发布我的代码草案可能会对某人有所帮助,并且非常欢迎任何 cmets /改进! (我正在重构Listing..) 否

    #!/usr/bin/env ruby
    # shebang to run the script from Terminal
    
    # include shasum
    require 'digest'
    
    class Listing
      # class of arrays of file listings 
    attr_reader :path
      def initialize(path)
        @path = path
        Dir.chdir(@path)
        @list_of_items = Dir['**/*']
        @list_of_folders = []
        @list_of_files = []
        @list_of_extensions = []
        @list_of_uniques = []
        @list_of_duplicates = []
      end
    
      def to_s
        "#{@path}"
      end
      
      def analyse_items
        @list_of_items.each do |f|
          if File.directory?(f)
            @list_of_folders << f
          else
            @list_of_files << f
          end
        end
        @list_of_folders.sort!
        @list_of_files.sort!
        @folder_count = @list_of_folders.count
        @files_count = @list_of_files.count
        @items_count = @list_of_items.count
        @count_check = (@items_count -(@folder_count + @files_count))
        # count_check should be zero
      end
    
      def identify_duplicates
        # Given an array of filepaths, this method divides it into an array of unique files and an array of duplicated files.  
        source = {}
        uniques = {}
            
        @list_of_files.each do |f|
          digest = Digest::SHA512.hexdigest File.read f
          source.store(f, digest)
        end
        
        uniques = source.invert.invert
        @list_of_uniques = uniques.keys
        @list_of_duplicates = source.keys - uniques.keys
      end
      
      def tell_duplicates
        puts "dupes = #{@list_of_duplicates}"
      end
        
    end
     
    
    
    l = Listing.new("/Volumes/Things/Photos/")
    l.analyse_items
    l.identify_duplicates
    l.tell_duplicates
    

    【讨论】:

      猜你喜欢
      • 2020-12-31
      • 2012-09-12
      • 1970-01-01
      • 2010-10-21
      • 2014-05-21
      • 2021-11-03
      • 2015-05-14
      • 1970-01-01
      • 2018-08-23
      相关资源
      最近更新 更多