【问题标题】:How to detect duplicate keys in hash and add prefix to the duplicate?如何检测哈希中的重复键并向重复项添加前缀?
【发布时间】:2019-03-06 05:12:55
【问题描述】:

我有两个数组,我正在使用 Ruby 中的哈希创建一个键值对。将两个数组压缩成键值对并在重复项的键名前添加诸如“A-”之类的前缀时,如何检测重复键?

我正在使用 .zip 合并两个数组,并将一个作为键,另一个作为值

[0] = "David"
[1] = "John"
[2] = "Alex"
[3] = "Sam"
[4] = "Caleb"
[5] = "David"
[6] = "John"
[7] = "Alex"
[8] = "Sam"

[0] = "1"
[1] = "2"
[2] = "3"
[3] = "4"
[4] = "5"
[5] = "6"
[6] = "7"
[7] = "8"
[8] = "9"


name_number_key_value_pair_hash = first_names.zip(numbers).to_h
puts(name_number_key_value_pair_hash)

预期: {"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4", "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7", "A-Alex"=>"8", "A-Sam"=>"9"} 实际的: {"David"=>"6", "John"=>"7", "Alex"=>"8", "Sam"=>"9", "Caleb"=>"5"}

【问题讨论】:

  • 请将示例中的数组显示为有效的 Ruby 对象,以便读者可以剪切和粘贴:arr1 = ["David", "John", "Alex", "Sam", "Caleb", "David", "John", "Alex", "Sam"]arr2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]。正如我所做的那样,通过将变量设置为每个数组,读者可以在答案和 cmets 中引用这些变量,而无需定义它们。

标签: ruby


【解决方案1】:

好像直截了当附上代码sn -p

names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9] 

key_pair = {}
names.each_with_index do |name, index|
  name = "A-#{name}" if key_pair[name]
  key_pair[name] = numbers[index]
end

它产生预期的输出:

{"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4", "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7", "A-Alex"=>"8", "A-Sam"=>"9"}

【讨论】:

  • 工作就像一个魅力。谢谢拉胡尔
  • 您所写的返回names,而不是所需的哈希值。您需要添加最后一行,key_pair(如果您的代码要包装在方法中,这将是必不可少的)。这对我们大多数人来说可能是显而易见的,但对于刚接触 Ruby 的读者来说可能不是。为避免这种情况,请使用names.each_with_index.with_object({}) do |(name, index),key_pair|。另外,作为一般做法,我建议写if key_pair.key?(name)(或has_key?),而不是if key_pair[name],因为它读起来更好,而且因为当nil 是有效的哈希值。
  • result = names.zip(numbers).reduce({}) { |memo, (names, numbers)| memo.merge(memo.key?(names) ? "A-#{names}" : names => numbers) }
【解决方案2】:

您基本上只需要在构建哈希时跟踪它的状态,当您发现冲突时,改为创建一个新密钥。这捕获了一般方法:

def hash_with_prefixes(a, b, prefixes)
    kv_pairs = a.zip(b)
    prefixes = prefixes.to_enum
    result_hash = {}

    kv_pairs.each do |initial_key, value|
        final_key = initial_key

        while result_hash.include? final_key
            final_key = "#{pfx.next}-#{initial_key}"
        end

        prefixes.rewind
        result_hash[final_key] = value
    end

    result_hash
rescue StopIteration
    fail "Insufficient prefixes to provide unique keys for input lists."
end

稍微清晰为代价,您还可以将其写成更短的形式:

def hash_with_prefixes(a, b, prefixes)
    pi = Hash[a.map {|k| [k, prefixes.lazy.map {|p| "#{p}-#{k}"}]}] 
    a.zip(b).inject({}) {|h, kv| h[h.include?(kv[0]) ? pi[kv[0]].next : kv[0]] = kv[1]; h}
rescue StopIteration
    fail "Insufficient prefixes to provide unique keys for input lists."
end

(不要这样做。)

【讨论】:

    【解决方案3】:

    这真的很简单。

    names = ["John","John", "John", "David", "David", "Susan", "Sue"]
    numbers = ["1", "2", "3", "4", "5", "6","7"]
    
    def uniq_hash_keys(names, numbers)
      hash = {}
      names.each_with_index do |name,i|
        if hash[name]
          prefix = 'A1-'
          key = prefix + name 
          while hash[key]
            version = prefix.match(/A(\d+)-.*/i)[1].to_i
            prefix = "A#{version + 1}-"
            key = prefix + name 
          end 
          name = key 
        end 
        hash[name] = numbers[i] 
      end 
      hash 
    end 
    

    这个函数产生:

    {
      "John"=>"1", 
      "A1-John"=>"2",
      "A2-John"=>"3",
      "David"=>"4",
      "A1-David"=>"5",
      "Susan"=>"6",
      "Sue"=>"7"
    }
    

    注意有 3 个 John,这就是为什么 while 循环在函数内部。

    【讨论】:

      【解决方案4】:

      这是创建所需哈希的一种方法。请注意,在arr1 中,“John”出现了 3 次。

      arr1 = ["David", "John", "Alex", "Sam", "Caleb",
              "David", "John", "Alex", "John", "Sam"]
      arr2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
      
      prefixes =
        arr1.each_with_object({}) do |s,h|
          if h.key?(s)
            prefix = "A-"
            (h[s].size-1).times { prefix = prefix.next } 
            h[s] << prefix
          else
            h[s] = ['']
          end
        end 
          #=> {"David"=>["", "A-"], "John"=>["", "A-", "B-"],
          #    "Alex"=>["", "A-"], "Sam"=>["", "A-"],
          #    "Caleb"=>[""]} 
      
      arr1.map { |s| "#{prefixes[s].shift}#{s}" }.zip(arr2).to_h
        #=> {"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4",
        #    "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7",
        #    "A-Alex"=>"8", "B-John"=>"9", "A-Sam"=>"10"} 
      

      注意"A-".next #=&gt; "B-""Z-".next #=&gt; "AA-"

      替代数据结构

      您可能希望考虑一种不同的数据结构,一种返回

      {"David"=>["1", "6"], "John"=>["2", "7", "9"],
       "Alex" =>["3", "8"], "Sam" =>["4", "10"], "Caleb"=>["5"]} 
      

      你可以这样做。

      arr1.each_with_index.
           group_by(&:first).
           transform_values { |v| arr2.values_at(*v.map(&:last)) }
        #=> {"David"=>["1", "6"], "John"=>["2", "7", "9"],
        #    "Alex" =>["3", "8"], "Sam" =>["4", "10"],
        #    "Caleb"=>["5"]} 
      

      请参阅Enumerable#each_with_indexEnumerable#group_byHash#transform_values1Array#values_atv.map(*:last) 在这里与v.map { |arr| arr.last } 相同。 步骤如下。

      a = arr1.each_with_index
        #=> #<Enumerator: ["David", "John", "Alex", "Sam",
        #     "Caleb", "David", "John", "Alex", "John", "Sam"]:
        #     each_with_index>
      

      我们可以通过将此枚举器转换为数组来查看将生成的值。

      a.to_a
        #=> [["David", 0], ["John", 1], ["Alex", 2], ["Sam", 3],
        #    ["Caleb", 4], ["David", 5], ["John", 6], ["Alex", 7],
        #    ["John", 8], ["Sam", 9]]
      

      继续,

      b = a.group_by(&:first)
        #=> {"David"=>[["David", 0], ["David", 5]],
        #    "John"=> [["John",  1], ["John",  6], ["John", 8]],
        #    "Alex"=> [["Alex",  2], ["Alex",  7]],
        #    "Sam"=>  [["Sam",   3], ["Sam",   9]],
        #    "Caleb"=>[["Caleb", 4]]} 
      b.transform_values { |v| arr2.values_at(*v.map(&:last)) }
        #=> {"David"=>["1", "6"], "John"=>["2", "7", "9"],
        #    "Alex"=> ["3", "8"], "Sam"=> ["4", "10"], "Caleb"=>["5"]} 
      

      对于最后一步,哈希b 的第一个值被传递给块,并且块变量被分配给该值。

      v = b.values.first
        #=> [["David", 0], ["David", 5]]
      

      那么块计算如下。

      c = v.map(&:last)
        #=> [0, 5] 
      arr2.values_at(*c)
        #=> arr2.values_at(0, 5)
        #=> ["1", "6"]
      

      对于传递给块的 b 的每个剩余值,计算都是相似的。

      1. Ruby MRI v2.4 中的新功能。

      【讨论】:

        【解决方案5】:

        此代码可读性较差,但紧凑且具有函数式风格。

        它在概念上与 rahul mishra 代码 https://stackoverflow.com/a/54697573/2109121 相同

        names = %w[David John Alex Sam Caleb David John Alex Sam]
        numbers = %w[1 2 3 4 5 6 7 8 9]
        
        result = names.zip(numbers).reduce({}) { |a, (b, c)| a.merge(a.key?(b) ? "A-#{b}" : b => c) }
        

        【讨论】:

          【解决方案6】:

          使用zipeach_with_object

          names   = %w[David John Alex Sam Caleb David John Alex Sam]
          numbers = %w[1 2 3 4 5 6 7 8 9] 
          
          names.zip(numbers).each_with_object({}) do |(name, number), hash|
            key          = hash.key?(name) ? "A-#{name}" : name
            hash[key]    = number 
          end
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-04-05
            • 2013-07-16
            • 2019-04-13
            • 2015-08-09
            • 2021-11-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多