double list in return statement. need explanation in python

Viewed 47

So I was trying to complete this kata on code wars and I ran across an interesting solution. The kata states:

"Given an array of integers, find the one that appears an odd number of times. There will always be only one integer that appears an odd number of times."

and one of the solutions for it was:

def find_it(seq):
    return [x for x in seq if seq.count(x) % 2][0]

My question is why is there [0] at the end of the statement. I tried playing around with it and putting [1] instead and when testing, it passed some tests but not others with no obvious pattern.

Any explanation will be greatly appreciated.

4 Answers

The first brackets are a list comprehension, the second is indexing the resulting list. It's equivalent to:

def find_it(seq):
    thelist = [x for x in seq if seq.count(x) % 2]
    return thelist[0]

The code is actually pretty inefficient, because it builds the whole list just to get the first value that passed the test. It could be implemented much more efficiently with next + a generator expression (like a listcomp, but lazy, with the values produced exactly once, and only on demand):

def find_it(seq):
    return next(x for x in seq if seq.count(x) % 2)

which would behave the same, with the only difference being that the exception raised if no values passed the test would be IndexError in the original code, and StopIteration in the new code, and it would operate more efficiently by stopping the search the instant a value passed the test.

Really, you should just give up on using the .count method and count all the elements in a single pass, which is truly O(n) (count solutions can't be, because count itself is O(n) and must be called a number of times roughly proportionate to the input size; even if you dedupe it, in the worst case scenario all elements appear twice and you have to call count n / 2 times):

from collections import Counter

def find_it(it):
    # Counter(it) counts all items of any iterable, not just sequence,
    # in a single pass, and since 3.6, it's insertion order preserving,
    # so you can just iterate the items of the result and find the first
    # hit cheaply
    return next(x for x, cnt in Counter(it).items() if cnt % 2)

That list comprehension yields a sequence of values that occur an odd number of times. The first value of that sequence will occur an odd number of times. Therefore, getting the first value of that sequence (via [0]) gets you a value that occurs an odd number of times.

Happy coding!

That code [x for x in seq if seq.count(x) % 2] return the list which has 1 value appears in input list an odd numbers of times.
So, to make the output as number, not as list, he indicates 0th index, so it returns 0th index of list with one value.

There is a nice another answer here by ShadowRanger, so I won't duplicate it providing partially only another phrasing of the same.

The expression [some_content][0] is not a double list. It is a way to get elements out of the list by using indexing. So the second "list" is a syntax for choosing an element of a list by its index (i.e. the position number in the list which begins in Python with zero and not as sometimes intuitively expected with one. So [0] addresses the first element in the list to the left of [0].

['this', 'is', 'a', 'list'][0] <-- this an index of 'this' in the list

print( ['this', 'is', 'a', 'list'][0] ) 

will print

this

to the stdout.


The intention of the function you are showing in your question is to return a single value and not a list.

So to get the single value out of the list which is built by the list comprehension the index [0] is used. The index guarantees that the return value result is taken out of the list [result] using [result][0] as

 [result][0] == result. 

The same function could be also written using a loop as follows:

def find_it(seq):
    for x in seq: 
       if seq.count(x) % 2 != 0:
           return x

but using a list comprehension instead of a loop makes it in Python mostly more effective considering speed. That is the reason why it sometimes makes sense to use a list comprehension and then unpack the found value(s) out of the list. It will be in most cases faster than an equivalent loop, but ... not in this special case where it will slow things down as mentioned already by ShadowRanger.

It seems that your tested sequences not always have only one single value which occurs an odd number of times. This will explain why you experience that sometimes the index [1] works where it shouldn't because it was stated that the tested seq will contain one and only one such value.

What you experienced looking at the function in your question is a failed attempt to make it more effective by using a list comprehension instead of a loop. The actual improvement can be achieved but by using a generator expression and another way of counting as shown in the answer by ShadowRanger:

from collections import Counter
def find_it(it):
    return next(x for x, cnt in Counter(it).items() if cnt % 2)
Related