मैं दो हैश की तुलना कैसे करूं?


108

मैं निम्नलिखित कोड का उपयोग करके दो रूबी हैशिंग की तुलना करने की कोशिश कर रहा हूं:

#!/usr/bin/env ruby

require "yaml"
require "active_support"

file1 = YAML::load(File.open('./en_20110207.yml'))
file2 = YAML::load(File.open('./locales/en.yml'))

arr = []

file1.select { |k,v|
  file2.select { |k2, v2|
    arr << "#{v2}" if "#{v}" != "#{v2}"
  }
}

puts arr

स्क्रीन पर आउटपुट फ़ाइल 2 से पूर्ण फ़ाइल है। मैं एक तथ्य के लिए जानता हूं कि फाइलें अलग हैं, लेकिन स्क्रिप्ट इसे लेने के लिए प्रतीत नहीं होती है।


जवाबों:


161

आप समानता के लिए सीधे हैश की तुलना कर सकते हैं:

hash1 = {'a' => 1, 'b' => 2}
hash2 = {'a' => 1, 'b' => 2}
hash3 = {'a' => 1, 'b' => 2, 'c' => 3}

hash1 == hash2 # => true
hash1 == hash3 # => false

hash1.to_a == hash2.to_a # => true
hash1.to_a == hash3.to_a # => false


आप हैश को सरणियों में बदल सकते हैं, फिर उनका अंतर पा सकते हैं:

hash3.to_a - hash1.to_a # => [["c", 3]]

if (hash3.size > hash1.size)
  difference = hash3.to_a - hash1.to_a
else
  difference = hash1.to_a - hash3.to_a
end
Hash[*difference.flatten] # => {"c"=>3}

आगे सरलीकरण:

एक टर्नरी संरचना के माध्यम से अंतर सौंपना:

  difference = (hash3.size > hash1.size) \
                ? hash3.to_a - hash1.to_a \
                : hash1.to_a - hash3.to_a
=> [["c", 3]]
  Hash[*difference.flatten] 
=> {"c"=>3}

एक ऑपरेशन में यह सब करना और differenceचर से छुटकारा पाना :

  Hash[*(
  (hash3.size > hash1.size)    \
      ? hash3.to_a - hash1.to_a \
      : hash1.to_a - hash3.to_a
  ).flatten] 
=> {"c"=>3}

3
क्या दोनों के बीच मतभेद पाने के लिए वैसे भी है?
dennismonsewicz

5
हैशिंग एक ही आकार के हो सकते हैं, लेकिन उनमें विभिन्न मूल्य होते हैं। इस तरह के मामले में दोनों hash1.to_a - hash3.to_aऔर hash3.to_a - hash1.to_aहालांकि गैर-रिक्त मान वापस कर सकते हैं hash1.size == hash3.sizeEDIT के बाद का हिस्सा तभी मान्य होता है जब हैश अलग आकार का हो।
ओलेक

3
अच्छा लगा, लेकिन आगे रहते हुए छोड़ देना चाहिए था। A.size> B.size का मतलब यह नहीं है कि A में B शामिल है। फिर भी सममित अंतरों के मिलन की आवश्यकता है।
जीन

सीधे के उत्पादन की तुलना .to_aकरेंगे असफल बराबर हैश भिन्न क्रम में कुंजी है जब: {a:1, b:2} == {b:2, a:1}=> true, {a:1, b:2}.to_a == {b:2, a:1}.to_a=> झूठी
एडन

के उद्देश्य क्या है flattenऔर *? सिर्फ क्यों नहीं Hash[A.to_a - B.to_a]?
जेरेमीकुन

34

आप हैशडिफ़ मणि की कोशिश कर सकते हैं , जो हैश में ऐरे और एरेज़ की गहरी तुलना करने की अनुमति देता है।

निम्नलिखित एक उदाहरण है:

a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
b = {a:{y:3}, b:{y:3, z:30}}

diff = HashDiff.diff(a, b)
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]

4
मैं परीक्षण विफलताओं के कारण कुछ काफी गहरी हैश था। के got_hash.should eql expected_hashसाथ बदलने से HashDiff.diff(got_hash, expected_hash).should eql []मुझे अब आउटपुट मिलता है जो वास्तव में मुझे दिखाता है कि मुझे क्या चाहिए। उत्तम!
davetapley

वाह, हाशिफ कमाल का है। एक बड़े नेस्टेड JSON सरणी में क्या बदल गया है, यह देखने की कोशिश करने का त्वरित काम किया। धन्यवाद!
जेफ विगल

आपका रत्न कमाल का है! सुपर उपयोगी जब JSON जोड़तोड़ शामिल चश्मा। धन्यवाद।
एलन

2
हाशिफ के साथ मेरा अनुभव यह रहा है कि यह छोटे हैश के लिए वास्तव में अच्छी तरह से काम करता है, लेकिन अलग गति अच्छी तरह से पैमाने पर नहीं लगती है। यदि आप उम्मीद करते हैं कि यह दो बड़े हैश खिलाया जा सकता है और यह सुनिश्चित करना कि आपके सहिष्णुता के भीतर पर्याप्त समय है, तो अपने कॉल को इसके लायक मान लें।
डेविड बोडो

use_lcs: falseझंडे का उपयोग करने से बड़ी हैश पर तुलना में काफी तेजी आ सकती है:Hashdiff.diff(b, a, use_lcs: false)
एरिक वॉकर

15

यदि आप दो हैश के बीच अंतर करना चाहते हैं, तो आप यह कर सकते हैं:

h1 = {:a => 20, :b => 10, :c => 44}
h2 = {:a => 2, :b => 10, :c => "44"}
result = {}
h1.each {|k, v| result[k] = h2[k] if h2[k] != v }
p result #=> {:a => 2, :c => "44"}

12

रेल विधि से पदावनत कर रही diffहै।

एक त्वरित लाइनर के लिए:

hash1.to_s == hash2.to_s

मैं हमेशा इस बारे में भूल जाता हूं। बहुत सारी समानता की जाँचें होती हैं जिनका उपयोग आसान बना दिया जाता है to_s
टीन मैन

17
यह असफल हो जायेगी बराबर हैश भिन्न क्रम में कुंजी है जब: {a:1, b:2} == {b:2, a:1}=> true, {a:1, b:2}.to_s == {b:2, a:1}.to_s=> झूठी
एडन

2
जो एक सुविधा है! : डी
डेव मोर्स

5

आप एक साधारण सरणी चौराहे का उपयोग कर सकते हैं, इस तरह आप जान सकते हैं कि प्रत्येक हैश में क्या अंतर है।

    hash1 = { a: 1 , b: 2 }
    hash2 = { a: 2 , b: 2 }

    overlapping_elements = hash1.to_a & hash2.to_a

    exclusive_elements_from_hash1 = hash1.to_a - overlapping_elements
    exclusive_elements_from_hash2 = hash2.to_a - overlapping_elements


1

यदि आपको हैश के बीच एक त्वरित और गंदे अंतर की आवश्यकता होती है जो मानों में सही ढंग से नील का समर्थन करता है तो आप कुछ का उपयोग कर सकते हैं

def diff(one, other)
  (one.keys + other.keys).uniq.inject({}) do |memo, key|
    unless one.key?(key) && other.key?(key) && one[key] == other[key]
      memo[key] = [one.key?(key) ? one[key] : :_no_key, other.key?(key) ? other[key] : :_no_key]
    end
    memo
  end
end

1

यदि आप एक अच्छी तरह से स्वरूपित अंतर चाहते हैं, तो आप यह कर सकते हैं:

# Gemfile
gem 'awesome_print' # or gem install awesome_print

और आपके कोड में:

require 'ap'

def my_diff(a, b)
  as = a.ai(plain: true).split("\n").map(&:strip)
  bs = b.ai(plain: true).split("\n").map(&:strip)
  ((as - bs) + (bs - as)).join("\n")
end

puts my_diff({foo: :bar, nested: {val1: 1, val2: 2}, end: :v},
             {foo: :bar, n2: {nested: {val1: 1, val2: 3}}, end: :v})

विचार यह है कि प्रारूप को भयानक प्रिंट का उपयोग करें, और आउटपुट को अलग करें। अंतर सटीक नहीं होगा, लेकिन यह डीबगिंग उद्देश्यों के लिए उपयोगी है।


1

... और अब विभिन्न प्रकार की संग्रह कक्षाओं (उनके बीच हैश) में लागू होने के लिए मॉड्यूल रूप में । यह एक गहन निरीक्षण नहीं है, लेकिन यह सरल है।

# Enable "diffing" and two-way transformations between collection objects
module Diffable
  # Calculates the changes required to transform self to the given collection.
  # @param b [Enumerable] The other collection object
  # @return [Array] The Diff: A two-element change set representing items to exclude and items to include
  def diff( b )
    a, b = to_a, b.to_a
    [a - b, b - a]
  end

  # Consume return value of Diffable#diff to produce a collection equal to the one used to produce the given diff.
  # @param to_drop [Enumerable] items to exclude from the target collection
  # @param to_add  [Enumerable] items to include in the target collection
  # @return [Array] New transformed collection equal to the one used to create the given change set
  def apply_diff( to_drop, to_add )
    to_a - to_drop + to_add
  end
end

if __FILE__ == $0
  # Demo: Hashes with overlapping keys and somewhat random values.
  Hash.send :include, Diffable
  rng = Random.new
  a = (:a..:q).to_a.reduce(Hash[]){|h,k| h.merge! Hash[k, rng.rand(2)] }
  b = (:i..:z).to_a.reduce(Hash[]){|h,k| h.merge! Hash[k, rng.rand(2)] }
  raise unless a == Hash[ b.apply_diff(*b.diff(a)) ] # change b to a
  raise unless b == Hash[ a.apply_diff(*a.diff(b)) ] # change a to b
  raise unless a == Hash[ a.apply_diff(*a.diff(a)) ] # change a to a
  raise unless b == Hash[ b.apply_diff(*b.diff(b)) ] # change b to b
end

1

मैंने यह तुलना करने के लिए विकसित किया कि क्या दो हैश बराबर हैं

def hash_equal?(hash1, hash2)
  array1 = hash1.to_a
  array2 = hash2.to_a
  (array1 - array2 | array2 - array1) == []
end

उपयोगितायें:

> hash_equal?({a: 4}, {a: 4})
=> true
> hash_equal?({a: 4}, {b: 4})
=> false

> hash_equal?({a: {b: 3}}, {a: {b: 3}})
=> true
> hash_equal?({a: {b: 3}}, {a: {b: 4}})
=> false

> hash_equal?({a: {b: {c: {d: {e: {f: {g: {h: 1}}}}}}}}, {a: {b: {c: {d: {e: {f: {g: {h: 1}}}}}}}})
=> true
> hash_equal?({a: {b: {c: {d: {e: {f: {g: {marino: 1}}}}}}}}, {a: {b: {c: {d: {e: {f: {g: {h: 2}}}}}}}})
=> false

0

इसका उत्तर " कम्पेयरिंग रूबी हैश " में दिया गया था । रेल हैश diffकरने के लिए एक विधि जोड़ता है । यह अच्छा काम करता है।


5
विचलन विधि को R4 संस्करणों से v4.0.2 की तुलना में नए सिरे से शुरू किया गया है।
एंड्रेस एरेनप्रेसिस

0

दोनों हैश to_json में कनवर्ट करने और स्ट्रिंग के रूप में तुलना करने के बारे में क्या? लेकिन यह ध्यान में रखते हुए

require "json"
h1 = {a: 20}
h2 = {a: "20"}

h1.to_json==h1.to_json
=> true
h1.to_json==h2.to_json
=> false

0

यहाँ दो हैश की तुलना करने के लिए एल्गोरिथ्म है, जो नेस्टेड एरेज़ की भी तुलना करेगा:

    HashDiff.new(
      {val: 1, nested: [{a:1}, {b: [1, 2]}] },
      {val: 2, nested: [{a:1}, {b: [1]}] }
    ).report
# Output:
val:
- 1
+ 2
nested > 1 > b > 1:
- 2

कार्यान्वयन:

class HashDiff

  attr_reader :left, :right

  def initialize(left, right, config = {}, path = nil)
    @left  = left
    @right = right
    @config = config
    @path = path
    @conformity = 0
  end

  def conformity
    find_differences
    @conformity
  end

  def report
    @config[:report] = true
    find_differences
  end

  def find_differences
    if hash?(left) && hash?(right)
      compare_hashes_keys
    elsif left.is_a?(Array) && right.is_a?(Array)
      compare_arrays
    else
      report_diff
    end
  end

  def compare_hashes_keys
    combined_keys.each do |key|
      l = value_with_default(left, key)
      r = value_with_default(right, key)
      if l == r
        @conformity += 100
      else
        compare_sub_items l, r, key
      end
    end
  end

  private

  def compare_sub_items(l, r, key)
    diff = self.class.new(l, r, @config, path(key))
    @conformity += diff.conformity
  end

  def report_diff
    return unless @config[:report]

    puts "#{@path}:"
    puts "- #{left}" unless left == NO_VALUE
    puts "+ #{right}" unless right == NO_VALUE
  end

  def combined_keys
    (left.keys + right.keys).uniq
  end

  def hash?(value)
    value.is_a?(Hash)
  end

  def compare_arrays
    l, r = left.clone, right.clone
    l.each_with_index do |l_item, l_index|
      max_item_index = nil
      max_conformity = 0
      r.each_with_index do |r_item, i|
        if l_item == r_item
          @conformity += 1
          r[i] = TAKEN
          break
        end

        diff = self.class.new(l_item, r_item, {})
        c = diff.conformity
        if c > max_conformity
          max_conformity = c
          max_item_index = i
        end
      end or next

      if max_item_index
        key = l_index == max_item_index ? l_index : "#{l_index}/#{max_item_index}"
        compare_sub_items l_item, r[max_item_index], key
        r[max_item_index] = TAKEN
      else
        compare_sub_items l_item, NO_VALUE, l_index
      end
    end

    r.each_with_index do |item, index|
      compare_sub_items NO_VALUE, item, index unless item == TAKEN
    end
  end

  def path(key)
    p = "#{@path} > " if @path
    "#{p}#{key}"
  end

  def value_with_default(obj, key)
    obj.fetch(key, NO_VALUE)
  end

  module NO_VALUE; end
  module TAKEN; end

end

-3

कैसे के बारे में एक और, सरल दृष्टिकोण:

require 'fileutils'
FileUtils.cmp(file1, file2)

2
यह केवल सार्थक है यदि आपको डिस्क पर समान होने के लिए हैश की आवश्यकता है। दो फाइलें जो डिस्क पर भिन्न होती हैं क्योंकि हैश तत्व अलग-अलग क्रम में होते हैं, उनमें अभी भी समान तत्व हो सकते हैं, और जब तक वे लोड किए जाते हैं तब तक रूबी के बराबर होगा।
टिन मैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.