6

I am trying to solve a problem where I'm given an array, such as [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10] where all numbers are duplicated twice, excluding one number, and I need to return the number that is not duplicated.

I am trying to do it like this:

def findNumber(self, nums):

    if (len(nums) == 1):
        return nums[0]

    nums_copy = nums[:]

    for i in nums:
        nums_copy.remove(i)

        if i not in nums:
            return i
        else:
            nums_copy.remove(i)

However when it reaches the else statement, there is the following error:

ValueError: list.remove(x): x not in list

This is occurring when i is in nums_copy, so I do not understand why this error occurs in this situation?

Turn
  • 6,656
  • 32
  • 41

11 Answers11

9

An easier (and more efficient) way of doing this than your initial approach is with a Counter object:

 from collections import Counter

 singlet = Counter(nums).most_common()[-1][0]

The Counter object will create a dictionary-like object with the keys being the values in your list and the values being the number of times they appear. The most_common method will return a list of tuples of (value, count) sorted by count in decreasing order.

If you don't know how many singlets there will be, you can get a list of them with:

[k for k, v in Counter(nums).items() if v == 1]

Complexity:

I said my top solution was more efficient because your original implementation iterates through your list and for each item calls both remove and in which is going to get you to something like O(n2) complexity. In the Counter implementation the construction of the Counter object only does a single pass through the entire list. There is probably a sort going on when most_common is called so I'm guessing the complexity is about O(n log n). @Stefan Pochman has corrected me on this: Python uses the Timsort algorithm which will be very efficient in a case like this (if all but one of the numbers appear twice, the list is effectively almost completely sorted already) so its complexity will about about O(n).

Turn
  • 6,656
  • 32
  • 41
  • Assumes there is always a 'singlet', which may not always be the case. Why not iterate on the counter items and pick the first item with a count of 1. – Moses Koledoye Feb 08 '18 at 00:52
  • 1
    @MosesKoledoye My reading of the problem as stated by the OP was that all numbers would be duplicated except for one of which there would be a single instance. – Turn Feb 08 '18 at 00:54
  • 1
    Ok, fair enough +1 – Moses Koledoye Feb 08 '18 at 00:55
  • 2
    This gives an error as well "TypeError: 'method' object is not subscriptable". Also, sorry, could you explain what a singlet means? (my English is not great, and I tried searching online) –  Feb 08 '18 at 01:05
  • @StefanPochmann Thanks, I missed a set of parentheses. Fixed now. – Turn Feb 08 '18 at 01:12
  • @Shannon Sorry, I had a typo. Fixed now. I used the term 'singlet' just to mean the value that only appears a single time. It isn't a technical term, it's an abuse of English in an attempt to be cute. – Turn Feb 08 '18 at 01:13
  • Looks good, though something interesting is missing: That the sorting is O(n). – Stefan Pochmann Feb 08 '18 at 02:01
  • @StefanPochmann Sorry, can you explain what you mean? I mentioned hte sorting is likely O(n log n). You think it would be O(n)? Or are you referring to some other sort that I missed? – Turn Feb 08 '18 at 02:03
  • Yes, that's the one I mean, and yes, it's O(n). – Stefan Pochmann Feb 08 '18 at 02:04
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/164717/discussion-between-turn-and-stefan-pochmann). – Turn Feb 08 '18 at 02:04
7

You already nums_copy.remove(i) so you can't nums_copy.remove(i) again

You could do:

a = [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10]

def get_single_instance(array):
  d = {}

  for item in a:
    if item not in d:
      d[item] = 1
    else:
      d[item] += 1

  print d

  for k, v in d.iteritems():
    if v == 1:
      return k

print get_single_instance(a)

Result: 9

JacobIRR
  • 8,545
  • 8
  • 39
  • 68
6

The best algorithm is to use XOR to find the odd number.

def find_number(nums):
    s = 0 
    for n in nums:
        s ^= n 
    return s 


a = [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10] 
print(find_number(a))
northtree
  • 8,569
  • 11
  • 61
  • 80
3

If the array is sorted, we can find the answer in O(log n) time and O(1) extra space. Consider that repeated number pairs start on odd or even indexes depending on where the single element is:

              0  1  2  3  4  5  6  7  8  9   10
             [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10]
even indexes: x     x     x     x
odd indexes:                             x

search:                      ^  (0 + 11) / 2 = 5
                         [2, 2] pair starting on even index
                                so the singleton must be ahead

                                      ^  (6 + 11) / 2 = 8
                                     [9] singleton found in two steps!
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
2

Try this list comprehension. It goes through each element and checks if it is duplicated, and if it isn't, it lets it stay in the new list. It then gets the zeroth element of the new list:

a=[0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10]
[e for e in a if a.count(e)==1][0]
whackamadoodle3000
  • 6,684
  • 4
  • 27
  • 44
  • 2
    Not a good use-case for a list comprehension, and `.count` will give you polynomial performance in the worst case – juanpa.arrivillaga Feb 08 '18 at 00:57
  • It will, but for small numbers, it is a short option that requires no libraries – whackamadoodle3000 Feb 08 '18 at 01:04
  • @juanpa.arrivillaga You say "polynomial performance" like that's a bad thing... O(n) is polynomial as well, and optimal. – Stefan Pochmann Feb 08 '18 at 01:05
  • @ juanpa.arrivillaga Also, wouldn't this be faster than the counter answer because the counter has to count each one and turn it into an object and store it in memory? And it is not like other answers are not using list comprehension. – whackamadoodle3000 Feb 08 '18 at 01:08
  • `min(a, key=a.count)` – Stefan Pochmann Feb 08 '18 at 01:17
  • 1
    @ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 A quick test on my side shows that your solution is faster than my Counter one by about a second or two for lists less than about 20. For longer lists your solution quickly blows up. That's because your complexity is O(n^2) and the Counter is O(n) (creating the dict object happens once and is cheap). – Turn Feb 08 '18 at 01:43
  • @ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 for small lists (and I mean small, like less than 100 items) it won't make a big difference. But yours will scale *terribly*. It isn't the list comprehension, it is calling `.count` *inside* the list comprehension. Note, my quick tests show that at about 100 items, the `.count` solution is 16x slower already, and that's only going to continue to get worse and worse. It's definitely something you should keep in mind. – juanpa.arrivillaga Feb 08 '18 at 02:06
  • 1
    Thanks for the evaluation and explanation. – whackamadoodle3000 Feb 08 '18 at 02:12
2

For the list A we can first sort it so it takes the form similar to: [1,1,4,4,5,7,7]. We check if every second number is the same with the one coming after it. If not it means that the number is the odd one.

def findNumber(A):
        A=sorted(A)
        l=len(A)
        for i in range(0,l,2):
            if i<l-1:
                if A[i]!=A[i+1]:
                    break
            else:
                break
        return A[i]
1

This is a very long method of doing things. As suggested you could use nums_copy.remove(i) or you could implement this is a much simpler manner using count():

def findNumber(self, nums):

    for i in nums:

        val = nums.count(i)

        if val == 1:
            return i

This will return the single number. This method is fine as long as you do not have multiple values, if so it will return only the last one. You could otherwise return a list which would store multiple values like so:

def findNumber(self, nums):
    values = []
    for i in nums:

        val = nums.count(i)

        if val == 1:
            values.append(i)

    return values
Xantium
  • 11,201
  • 10
  • 62
  • 89
1

Assuming a sorted iterable (otherwise sort it), here is the first occurrence of a non-duplicate pair:

import more_itertools as mit


iterable = [0, 0, 1, 1, 2, 2, 6, 6, 9, 10, 10] 
next(x for x, y in mit.windowed(iterable, n=2, step=2) if x != y)
# 9

An alternative way to create non-overlapping pairs:

next(x for x, y in mit.sliced(iterable, 2) if x != y)
# 9

more_itertools is a third-party library, > pip install more_itertools.

pylang
  • 40,867
  • 14
  • 129
  • 121
1

A good way to do this in Python is with collections.Counter for O(n) complexity:

from collections import Counter

def solution(A):
    for x, occ in Counter(A).items():
        if occ % 2:
            return x

This will also generalize to return any number that occurs an odd number of times in the array.

SatoriChi
  • 31
  • 2
1

Python 3.6 above


Solution 1

def solution(A):
    # write your code in Python 3.6
    result =0
    for i in A:
        result ^= i
    return result

Solution 2:

from itertools import groupby


def solution(A):
    # write your code in Python 3.6
    for key, group_list in groupby(sorted(A)):
        if len(list(group_list)) % 2 == 1:
            return key

Time complexity: O(N)

Wise Lin
  • 11
  • 2
0

For anyone working with NumPy, this should be competitive in speed with other answers here:

>>> def get_single_instance_np(arr: np.ndarray):
...     return np.asscalar(arr[np.bincount(arr) == 1])

>>> print(get_single_instance(a))
9

This just returns elements for which their count equals 1.

Brad Solomon
  • 38,521
  • 31
  • 149
  • 235