简体   繁体   中英

Most pythonic way to check list boils down to one value

I want a function check to check that a given list reduces ("boils down") to exactly one value under a given function reduce_function . (A common example could be to check that a list of lists contains sublists of equal length only.)

I see at least the three following ways to achieve this. For each of them, I see some advantages and disadvantages. None of them really looks very readable to my eye. Could you give me an elaborate overview about:

Which one would be considered most readable and most "pythonic"?

1. Measure the length of the set of reduced values.

This seems most readable, but requires reduce_function to return a hashable:

def check(lst):
    return len(set(map(reduce_function, lst))) == 1

2. Count the number of groups

def check(lst):
    return len(list(itertools.groupby(lst, key=reduce_function)) == 1

3. Use all on comparison with first element

This needs an additional or -statement (which could be replaced by an if - else -statement) to cover the case of lst being empty.

def check(lst):
    return not lst or all([reduce_function(el) == reduce_function(lst[0]) for el in lst])

I like all 3 options although the third doesn't need to be a list comprehension, just take the square brackets off.

Like your second option the itertools documentation has a recipe called all_equal that checks if all elements in an iterable are equal using itertools.groupby as well, although they didn't account for a custom function and defaulting to false when empty but it can easily be implemented:

def all_equal(iterable, key=reduce_function):
    g = groupby(iterable, key)
    return next(g, False) and not next(g, False)

itertools.all_equal is the "pythonic" way to check if all elements are equal in an iterable; I only modified it to fit your needs.

This is probably a bit opinion based, but there are also objective reasons for or against the different alternatives. I'll focus on the first and third one here.

The first approach, converting to a set and testing it's length, is IMHO the cleanest, but it has O(n) additional space requirement (in the worst case of all elements being not the same). It also works with any iterable, whereas the third one only works if lst is actually a list . In it's current form the third approach also has O(n) space complexity, too, (in all cases) due to the list comprehension [...] within the all ; you can use a generator expression instead. Further, reduce_function(lst[0]) is recomputed for each other element. Finally, the not lst or is redundant, as all of an empty list is True .

Also, note that if you want to test if the list "boils down" to at most one value, as implied by the not lst or , you should check len(...) <= 1 for the first two approaches.


I did not test this, but I think this should work, and a) work with non-hashable reduce_function , b) be O(1) space complexity, c) work with lists or iterables, and d) respect the empty-list corner case:

def check(lst):
    return sum(1 for _ in itertools.groupby(lst, key=reduce_function)) <= 1

This will still evaluate reduce_function for the entire lst , though, even if it's already clear that there is more than one distinct value.

If you want to check if all values can be reduced to the same value you don't need to go through the whole list. You can stop as soon as you find a value that is not equal to the first element in the list. It will be more efficient:

def check(func, lst):
    it = iter(lst)
    first = func(next(it))
    for i in it:
        if func(i) != first:
            return False
    return True

You can also replace the for loop with the function all . In your solution with all you calculate the first element in the list len(lst) + 1 times.

def check(func, lst):
    it = iter(lst)
    first = func(next(it))
    return all(first == func(i) for i in it)

check(sum, [[0, 1], [0, 1], [0, 1]])
# True

check(sum, [[1, 1], [0, 1], [0, 1]])
# False

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM