Test if all values of a dictionary are equal - whe

2020-07-24 03:37发布

问题:

I have 2 dictionaries: the values in each dictionary should all be equal.
BUT I don't know what that number will be...

dict1 = {'xx':A, 'yy':A, 'zz':A}
dict2 = {'xx':B, 'yy':B, 'zz':B}

N.B. A does not equal B
N.B. Both A and B are actually strings of decimal numbers (e.g. '-2.304998') as they have been extracted from a text file

I want to create another dictionary - that effectively summarises this data - but only if all the values in each dictionary are the same.
i.e.

summary = {}
if dict1['xx'] == dict1['yy'] == dict1['zz']:
    summary['s'] = dict1['xx']
if dict2['xx'] == dict2['yy'] == dict2['zz']:
    summary['hf'] = dict2['xx']

Is there a neat way of doing this in one line?

I know it is possible to create a dictionary using comprehensions
summary = {k:v for (k,v) in zip(iterable1, iterable2)}
but am struggling with both the underlying for loop and the if statement...

Some advice would be appreciated.

I have seen this question, but the answers all seem to rely on already knowing the value being tested (i.e. are all the entries in the dictionary equal to a known number) - unless I am missing something.

回答1:

sets are a solid way to go here, but just for code golf purposes here's a version that can handle non-hashable dict values:

expected_value = next(iter(dict1.values())) # check for an empty dictionary first if that's possible
all_equal = all(value == expected_value for value in dict1.values())

all terminates early on a mismatch, but the set constructor is well enough optimized that I wouldn't say that matters without profiling on real test data. Handling non-hashable values is the main advantage to this version.



回答2:

While we can use set for this, doing so has a number of inefficiencies when the input is large. It can take memory proportional to the size of the input, and it always scans the whole input, even when two distinct values are found early. Also, the input has to be hashable.

For 3-key dicts, this doesn't matter much, but for bigger ones, instead of using set, we can use itertools.groupby and see if it produces multiple groups:

import itertools

groups = itertools.groupby(dict1.values())

# Consume one group if there is one, then see if there's another.
next(groups, None)
if next(groups, None) is None:
    # All values are equal.
    do_something()
else:
    # Unequal values detected.
    do_something_else()


回答3:

One way to do this would be to leverage set. You know a set of an iterable has a length of 1 if there is only one value in it:

if len(set(dct.values())) == 1:
    summary[k] = next(iter(dct.values()))

This of course, only works if the values of your dictionary are hashable.



回答4:

Except for readability, I don't care for all the answers involving set or .values. All of these are always O(N) in time and memory. In practice it can be faster, although it depends on the distribution of values.

Also because set employs hashing operations, you may also have a hefty large constant multiplier to your time cost. And your values have to hashable, when a test for equality is all that's needed.

It is theoretically better to take the first value from the dictionary and search for the first example in the remaining values that is not equal to. set might be quicker than the solution below because its workings are may reduce to C implementations.

def all_values_equal(d):
    if len(d)<=1: return True # Treat len0 len1 as all equal
    i = d.itervalues()
    firstval = i.next()
    try:
        # Incrementally generate all values not equal to firstval
        # .next raises StopIteration if empty.
        (j for j in i if j!=firstval).next()
        return False
    except StopIteration:
        return True

print all_values_equal({1:0, 2:1, 3:0, 4:0, 5:0}) # False
print all_values_equal({1:0, 2:0, 3:0, 4:0, 5:0}) # True
print all_values_equal({1:"A", 2:"B", 3:"A", 4:"A", 5:"A"}) # False
print all_values_equal({1:"A", 2:"A", 3:"A", 4:"A", 5:"A"}) # True

In the above:

(j for j in i if j!=firstval)

is equivalent to:

def gen_neq(i, val):
    """
    Give me the values of iterator i that are not equal to val
    """
    for j in i:
        if j!=val:
            yield j


回答5:

I found this solution, which I find quite a bit I combined another solution found here: enter link description here

user_min = {'test':1,'test2':2}
all(value == list(user_min.values())[0] for value in user_min.values())

>>> user_min = {'test':1,'test2':2}
>>> all(value == list(user_min.values())[0] for value in user_min.values())
False
>>> user_min = {'test':2,'test2':2}
>>> all(value == list(user_min.values())[0] for value in user_min.values())
True
>>> user_min = {'test':'A','test2':'B'}
>>> all(value == list(user_min.values())[0] for value in user_min.values())
False
>>> user_min = {'test':'A','test2':'A'}
>>> all(value == list(user_min.values())[0] for value in user_min.values())
True

Good for a small dictionary, but I'm not sure about a large dictionary, since we get all the values to choose the first one