Find The Parity Outlier, CodeWars question on python

Viewed 741

I am trying to solve this problem but it doesn't give me the correct answer. Here is the problem:

You are given an array (which will have a length of at least 3, but could be very large) containing integers. The array is either entirely comprised of odd integers or entirely comprised of even integers except for a single integer N. Write a method that takes the array as an argument and returns this "outlier" N.

Here is my code:

a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]

def find_outlier(list_integers):
    for num in list_integers:
       if num % 2 != 0:
         odd = num
       elif num % 2 == 0:
         even = num
    for num in list_integers:
      if len([odd]) < len([even]):
        return odd
      else:
        return even


print(find_outlier(a))

print(find_outlier(b))

print(find_outlier(c))

It spits out 10, 36, 160 and obviously only the last one is correct. Can anyone help me out with it? Thanks!

5 Answers

You could analyze the first three and find the outlier if it is there. If it is there, you are done. If not, you know the expected parity and can test each subsequent element accordingly.

Creating lists for odd/even numbers, while in principle leading to a result, is unnecessarily memory inefficient.


In code this could look something like:

def find_outlier(seq):
    par0 = seq[0] % 2
    par1 = seq[1] % 2
    if par0 != par1:
        return seq[1] if seq[2] % 2 == par0 else seq[0]
    # the checks on the first 2 elements are redundant, but avoids copying
    # for x in seq[2:]: would do less iteration but will copy the input
    for x in seq:  
        if x % 2 != par0:
            return x


a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]

print(find_outlier(a))
# 3
print(find_outlier(b))
# 11
print(find_outlier(c))
# 160

Your code could not work in its current form:

  • this block:
    for num in list_integers:
       if num % 2 != 0:
         odd = num
       elif num % 2 == 0:
         even = num

will just have the last seen odd in odd and the last seen even in even, without any info on how many were seen. You would need to count how many even/odd numbers are there and eventually need to store the first value encountered for each parity.

  • this second block
    for num in list_integers:
      if len([odd]) < len([even]):
        return odd
      else:
        return even

is always checking the length of the length-1 lists, and will always return even.

I see no simple way of fixing your code to make it with comparable efficiency as the above solution. But you could adapt your code to make it reasonably efficient (O(n) in time -- but without short-circuiting, O(1) in memory):

def find_outlier_2(seq):
    odd = None
    even = None
    n_odd = n_even = 0
    for x in seq:
        if x % 2 == 0:
            if even is None:  # save first occurrence
                even = x
            n_even += 1
        else:  # no need to compute the modulus again
            if odd is None:  # save first occurrente
                odd = x
            n_odd += 1
    if n_even > 1:
        return odd
    else:
        return even

The above is significantly more efficient than some of the other answers in that it does not create unnecessary lists. For example, these solutions are unnecessarily more memory consuming (being O(n) in time and O(n) in memory):

def find_outlier_3(list_integers):
    odd = []
    even = []
    for num in list_integers:
       if num % 2 != 0:
         odd.append(num)
       elif num % 2 == 0:
         even.append(num)
    if len(odd) < len(even):
      return odd[0]
    else:
      return even[0]
def find_outlier_4(lst):
    odds = [el % 2 for el in lst]
    if odds.count(0) == 1:
        return lst[odds.index(0)]
    else:         
        return lst[odds.index(1)]

Simple benchmarks show that these solutions are also slower:

%timeit [find_outlier(x) for x in (a, b, c) * 100]
# 10000 loops, best of 3: 128 µs per loop
%timeit [find_outlier_2(x) for x in (a, b, c) * 100]
# 1000 loops, best of 3: 229 µs per loop
%timeit [find_outlier_3(x) for x in (a, b, c) * 100]
# 1000 loops, best of 3: 341 µs per loop
%timeit [find_outlier_4(x) for x in (a, b, c) * 100]
# 1000 loops, best of 3: 248 µs per loop

You can nicely use list comprehensions for this:

a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]

def outlier(lst):
    odds = [ el % 2 for el in lst ]    # list with 1's when odd, 0's when even
    print(odds)                        # just to show what odds contains
    if odds.count(0) == 1:             # if the amount of zeros (even numbers) = 1 in this list
        print(lst[odds.index(0)])      # find the index of the 'zero' and use it to read the value from the input lst
    else:         
        print(lst[odds.index(1)])      # find the index of the 'one' and use it to read the value from the input lst

outlier(a)
outlier(b)
outlier(c)

Output

[0, 0, 0, 0, 0, 1]        # only 1 'one' so use the position of that 'one'
3
[0, 0, 0, 0, 0, 1, 0, 0]  # only 1 'one' so use the position of that 'one'
11
[0, 1, 1, 1, 1, 1, 1]     # only 1 'zero' so use the position of that 'zero'
160

I imagine it would be a bit more efficient to first determine if the outlier is odd or even by looking at a small sample, then return just the outlier using list comprehension. This way, if the list is massive, you won't have timeout issues.

Here's what I would do:

def findoutlier(yourlist):
    if (yourlist[0] % 2 == 0 and yourlist[1] % 2 == 0) or (yourlist[0] % 2 == 0 and yourlist[2] % 2 == 0) or (yourlist[1] % 2 == 0 and yourlist[2] % 2 == 0):
        oddeven = "even"
    else:
        oddeven = "odd"
    if oddeven == "even":
        return [i for i in yourlist if i % 2 != 0][0]
    else:
        return [i for i in yourlist if i % 2 == 0][0]

a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]

print(findoutlier(a))
print(findoutlier(b))
print(findoutlier(c))

This will return 3, 11, and 160 as expected.

Count the number of odd values in the first three items in the list. This can be done using a sum(). It the sum > 1, the list has mostly odd numbers, so find the even outlier. Otherwise find the odd outlier.

def find_outlier(sequence):
    if sum(x & 1 for x in numbers[:3]) > 1:

        # find the even outlier
        for n in sequence:
            if not n & 1:
                return n

    else:
        # find the odd outlier
        for n in sequence:
            if n & 1:
                return n

You want to use a list to store your odd/even numbers. Right now you're storing them as int and they're getting replaced on your loop's next iteration.

def find_outlier(list_integers):
    odd = []
    even = []
    for num in list_integers:
       if num % 2 != 0:
         odd.append(num)
       elif num % 2 == 0:
         even.append(num)
    if len(odd) < len(even):
      return odd[0]
    else:
      return even[0]
Related