2017-04-19 147 views
0

TL; DR:如果兩個python字典中的某些字典的值不可更改/可變(例如列表或熊貓數據框),您如何比較兩個python字典?我不得不比較字典對的相等性。在這個意義上,這個問題類似於這兩個,但他們的解決方案似乎只是爲不可變對象工作...將字典與不可比較的值或不可比較的值進行比較? (如列表或數據框)

我的問題,是我處理對高度嵌套字典其中不可對象可以在不同的地方找到取決於我比較哪一對字典。我的想法是,我需要遍歷字典中包含的最便宜的值,並且不能僅依靠僅展開最高鍵值對的dict.iteritems()。我不確定在字典中包含的所有可能的鍵值對中進行迭代,並使用sets/==對可哈希對象和pandas數據框的情況進行比較,運行df1.equals(df2).(Note for pandas dataframe,just running df1==df2不分段比較和NA的都處理不好df1.equals(df2)得到周圍做的伎倆)

因此,例如:。

a = {'x': 1, 'y': {'z': "George", 'w': df1}} 
b = {'x': 1, 'y': {'z': "George", 'w': df1}} 
c = {'x': 1, 'y': {'z': "George", 'w': df2}} 

至少,,這將是相當真棒不已,解決方案將產生TRUE/FALSE關於它們的值是否相同,並且可以用於熊貓數據框。

def dict_compare(d1, d2): 
    if ... 
     return True 
    elif ... 
     return False 

dict_compare(a,b) 
>>> True 
dict_compare(a,c) 
>>> False 

中等更好:的解決方案會指出什麼鍵/值要橫跨字典不同。

在理想情況下:溶液可以在值分成4個分組:

  • 加入,
  • 除去,
  • 改性
  • 相同
+0

@MSeifert更清晰嗎? – Afflatus

+0

@Afflatus你已經解決了數據框的問題,什麼具體問題阻止你? – Goyo

+0

@Goyo我不知道如何遍歷字典中包含的所有可能的鍵值對 - 即我正在處理高度嵌套的字典對,其中不可取消的對象可以在不同的地方找到,取決於哪對我正在比較的字典。 – Afflatus

回答

1

那麼,有一種方法可以將任何類型進行比較:只需將它包裝在一個可以根據需要進行比較的類中即可它:

class DataFrameWrapper(): 
    def __init__(self, df): 
     self.df = df 

    def __eq__(self, other): 
     return self.df.equals(other.df) 

所以,當你換你的「不可比」的價值觀,你現在可以簡單地使用==

>>> import pandas as pd 

>>> df1 = pd.DataFrame({'a': [1,2,3]}) 
>>> df2 = pd.DataFrame({'a': [3,2,1]}) 

>>> a = {'x': 1, 'y': {'z': "George", 'w': DataFrameWrapper(df1)}} 
>>> b = {'x': 1, 'y': {'z': "George", 'w': DataFrameWrapper(df1)}} 
>>> c = {'x': 1, 'y': {'z': "George", 'w': DataFrameWrapper(df2)}} 
>>> a == b 
True 
>>> a == c 
False 

當然包裹你的價值觀有它的缺點,但如果你只需要比較他們會是一個非常簡單的方法。所有這一切可能需要的是做的比較和遞歸解包之後前一個遞歸包裝:

def recursivewrap(dict_): 
    for key, value in dict_.items(): 
     wrapper = wrappers.get(type(value), lambda x: x) # for other types don't wrap 
     dict_[key] = wrapper(value) 
    return dict_ # return dict_ so this function can be used for recursion 

def recursiveunwrap(dict_): 
    for key, value in dict_.items(): 
     unwrapper = unwrappers.get(type(value), lambda x: x) 
     dict_[key] = unwrapper(value) 
    return dict_ 

wrappers = {pd.DataFrame: DataFrameWrapper, 
      dict: recursivewrap} 
unwrappers = {DataFrameWrapper: lambda x: x.df, 
       dict: recursiveunwrap} 

樣本案例:

>>> recursivewrap(a) 
{'x': 1, 
'y': {'w': <__main__.DataFrameWrapper at 0x2affddcc048>, 'z': 'George'}} 
>>> recursiveunwrap(recursivewrap(a)) 
{'x': 1, 'y': {'w': a 
    0 1 
    1 2 
    2 3, 'z': 'George'}} 

如果你感覺真的很冒險,您可以使用,這取決於包裝類比較結果修改了一些變量,它包含了不相等的信息。


答案的這部分是基於沒有包括嵌套原題:

您可以從可哈希值單獨的unhashable值,並做了可哈希值的設定比較一個「不依賴訂單」的清單比較:

def split_hashable_unhashable(vals): 
    """Seperate hashable values from unhashable ones and returns a set (hashables) 
    and list (unhashable ones)""" 
    set_ = set() 
    list_ = [] 
    for val in vals: 
     try: 
      set_.add(val) 
     except TypeError: # unhashable 
      list_.append(val) 
    return set_, list_ 


def compare_lists_arbitary_order(l1, l2, cmp=pd.DataFrame.equals): 
    """Compare two lists using a custom comparison function, the order of the 
    elements is ignored.""" 
    # need to have equal lengths otherwise they can't be equal 
    if len(l1) != len(l2): 
     return False 

    remaining_indices = set(range(len(l2))) 
    for item in l1: 
     for cmpidx in remaining_indices: 
      if cmp(item, l2[cmpidx]): 
       remaining_indices.remove(cmpidx) 
       break 
     else: 
      # Run through the loop without finding a match 
      return False 
    return True 

def dict_compare(d1, d2): 
    if set(d1) != set(d2): # compare the dictionary keys 
     return False 
    set1, list1 = split_hashable_unhashable(d1.values()) 
    set2, list2 = split_hashable_unhashable(d2.values()) 
    if set1 != set2: # set comparison is easy 
     return False 

    return compare_lists_arbitary_order(list1, list2) 

它比預期的要長一點。爲了您的測試情況下,它的工作原理definetly:

>>> import pandas as pd 

>>> df1 = pd.DataFrame({'a': [1,2,3]}) 
>>> df2 = pd.DataFrame({'a': [3,2,1]}) 

>>> a = {'x': 1, 'y': df1} 
>>> b = {'y': 1, 'x': df1} 
>>> c = {'y': 1, 'x': df2} 
>>> dict_compare(a, b) 
True 
>>> dict_compare(a, c) 
False 
>>> dict_compare(b, c) 
False 

set -operations也可以用來尋找差異(見set.difference)。這與list有點複雜,但並非不可能。可以將沒有找到匹配項目的項目添加到單獨列表中,而不是立即返回False

+0

當我用一對字典運行它時:dict_compare(dict1,dict2),我得到一個錯誤,它追溯到compare_lists_arbitary_order()函數,它調用:「if cmp(item,l2 [cmpidx]):」錯誤消息是一個TypeError:unbound方法equals()必須用DataFrame實例作爲第一個參數調用(代替dict實例) – Afflatus

+0

@Afflatus是的,你在改變你的問題的同時我寫了答案(我一開始並沒有注意到它,並認爲它不是關於嵌套字典)。這就是爲什麼我包括一個可能的遞歸方法,可以在開始時爲類型定製:) – MSeifert