How to find first value in a list having no duplicates?

Viewed 117
l1 = ['A','B','C','D','A','B']
l2 = []

'C' is the first value in list l1, i want to create a function so that it returns C in l2.

6 Answers

In 3.6 and higher, this is very easy. Now that dicts preserve insertion order, collections.Counter can be used to efficiently count all elements in a single pass, then you can just scan the resulting Counter in order to find the first element with a count of 1:

from collections import Counter

l1 = ['A','B','C','D','A','B']
l2 = [next(k for k, v in Counter(l1).items() if v == 1)]

Work is strictly O(n), with only one pass of the input required (plus a partial pass of the unique values in the Counter itself), and the code is incredibly simple. In modern Python, Counter even has a C accelerator for counting inputs that pushes all the Counter construction work to the C layer, making it impossible to beat. If you want to account for the possibility that no such element exists, just wrap the l2 initialization to make it:

try:
    l2 = [next(k for k, v in Counter(l1).items() if v == 1)]
except StopIteration:
    l2 = []
    # ... whatever else makes sense for your scenario ...

or avoid exception handling with itertools.islice (so l2 is 0-1 items, and it still short-circuits once a hit is found):

from itertools import islice

l2 = list(islice((k for k, v in Counter(l1).items() if v == 1), 1))

You can convert list to string and then compare index of each character from left and right using find and rfind functions of string. It stops counting as soon as the first match is found,

l1 = ['A','B','C','D','A','B']

def i_list(input):
    l1 = ''.join(input)
    for i in l1:
        if l1.find(i) == l1.rfind(i):
            return(i)


print(i_list(l1))

# output
C

An implementation using a defaultdict:

# Initialize
from collections import defaultdict
counts = defaultdict(int)

# Count all occurrences
for item in l1: 
    counts[item] += 1

# Find the first non-duplicated item
for item in l1:
    if counts[item] == 1: 
        l2 = [item]
        break
else:
    l2 = []

As a follow up to ShadowRanger's answer, if you're using a lower version of Python, it's not that more complicated to filter the original list so that you don't have to rely on the ordering of the counter items:

from collections import Counter

l1 = ['A','B','C','D','A','B']
c = Counter(l1)
l2 = [x for x in l1 if c[x] == 1][:1]

print(l2)  # ['C']

This is also O(n).

We can do it also with "numpy".

def find_first_non_duplicate(l1):
    indexes_counts = np.asarray(np.unique(l1, return_counts=True, return_index=True)[1:]).T
    (not_duplicates,) = np.where(indexes_counts[:, 1] == 1)
    if not_duplicates.size > 0:
        return [l1[np.min(indexes_counts[not_duplicates, 0])]]
    else:
        return []

print(find_first_non_duplicate(l1))

# output
['C']

A faster way to get the first unique element in a list:

  1. Check each element one by one and store it in a dict.
  2. Loop through the dict and check the first element whose count is

    def countElement(a):
        """
        returns a dict of element and its count
        """
        g = {}
        for i in a:
            if i in g: 
                g[i] +=1
            else: 
                g[i] =1
        return g
    
    
    #List to be processed - input_list
    input_list = [1,1,1,2,2,2,2,3,3,4,5,5,234,23,3,12,3,123,12,31,23,13,2,4,23,42,42,34,234,23,42,34,23,423,42,34,23,423,4,234,23,42,34,23,4,23,423,4,23,4] #Input from user
    
    
    try:
        if input_list: #if list is not empty
            print ([i for i,v in countElement(input_list).items() if v == 1][0]) #get the first element whose count is 1
        else: #if list is empty
            print ("empty list in Input")
    except: #if list is empty - IndexError
        print (f"Only duplicate values in list - {input_list}")
    
Related