1

I want to check if an item is already in a list with referential equality and not with strucural equality.

For clarity:

referential equality between 2 items is checked with item1 is item2

structural equality is checked with item1 == item2

Structural equality for checking if an item is already in a list is easily done like this:

item in list

So I'm looking for equivalent line with referential equality. Is this possible without looping over every item in the list?

An example of how this needs to be implemented (just for clarification):

def get_all_items_in_list(items):
   all_items = []
   for item in items:
       if not item in all_items:   # replace with referential equality here
           all_items.append(item)
   return all_items

# setup
a = (1, [])
b = (1, [])
print(a is b)    # prints False
items = [a, a, b]
print(get_all_items_in_list(items))  # should print [(1, []), (1, [])] but prints [(1, [])]
Daan Seuntjens
  • 880
  • 1
  • 18
  • 37
  • 1
    This question is a duplicate of [Check if object is in list (not “by value”, but by id)](https://stackoverflow.com/q/34498961/7851470). I can't vote to close it currently due to the open bounty. – Georgy Apr 20 '20 at 11:34

5 Answers5

3

You aren't going to be able to do this without some kind of loop. There's no equivalent operator in python of in that checks identity. A short solution might look like this:

l = [1,2,3]
l2 = [[1, 2, 3], [4, 5, 6]]
l3 = [[1, 2, 3], [4, 5, 6], l]

any(l is sub for sub in l2)
# false

any(l is sub for sub in l3)
# True

It still loops, but it will exit early if a true value is found.

If you really needed to use an operator for this, you could subclass UserList and override __contains__(). This would change the behavior of in:

from collections import UserList

class identList(UserList):
    def __contains__(self, other):
        return any(other is sub for sub in self)


l = [1,2,3]
l2 = identList([[1, 2, 3], [4, 5, 6]])
l3 = identList([[1, 2, 3], [4, 5, 6], l])

l in l2
# False

l in l3
# True
Mark
  • 90,562
  • 7
  • 108
  • 148
1

So, according to "is" python's operator, it checks the objects' identity, which can be achieved with id() function (scroll further for solution).

# as you did
a = (1, [])
b = (1, []) # two different objects
print(a is b)
print(id(a), id(b))
>>> False
>>> 2076884715144 2076884729224

# Now let's try making the second object the same
c = (2, [])
d = c #objects are the same
print(c is d)
print(id(c), id(d))
>>> True
>>> 2076899815240 2076899815240

SOLUTION:

The idea bellow is the get all ids() in another list with the same position as original list. When checking if item is in list, you'll check if the identity is already in.

def get_ids_items_in_list(items):
    """ Get the identity of the objects in same order as list"""
    items_ids = []
    for item in items:
        items_ids.append(id(item))
    return items_ids

def get_all_items_in_list(items_list):
    # get our ids
    items_ids = get_ids_items_in_list(items_list)

    all_items = []
    all_items_id = []

    # Loops over the ids but using enumerate to get positions
    for idx, item in enumerate(items_ids):
        if not item in all_items_id:
            all_items_id.append(item)
            # if id is in list, append object according to position
            all_items.append(items_list[idx])
    return all_items

# setup
a = (1, [])
b = (1, [])
print(a is b)    # prints False

items = [a, a, b]
print(get_all_items_in_list(items))

>>> False
>>> [(1, []), (1, [])]

MORE ON THE SUBJECT:

# another thing to notice is that a simple copy()
# will return the same object
from copy import copy
e = (3, [])
f = copy(e)
print(e is f)
print(id(e), id(f))
>>> True
>>> 2076884710600 2076884710600

# but a deepcopy will change the identity
from copy import deepcopy
g = (4, [])
h = deepcopy(g)
print(g is h)
print(id(g), id(h))
>>> False
>>> 2076884714120 2076884715016
mrbTT
  • 1,399
  • 1
  • 18
  • 31
1

You can use an intermediate dict keyed by item id as an identity set:

def get_all_items_in_list(items):
   return {id(it): it for it in items}.values()

# arrange
a = (1, [])
b = (1, [])
items = [a, a, b]

# act
result = list(get_all_items_in_list(items))

# assert
print(result[0] is a)
print(result[1] is b)
millimoose
  • 39,073
  • 9
  • 82
  • 134
1
#With minimal changes in existing code snippet
def get_all_items_in_list(items):
   all_items = {}
   for item in items:
       if not id(item) in all_items:   # replace with referential equality here
           all_items[id(item)] = item
   return all_items.values()

# setup
a = (1, [])
b = (1, [])
print(a is b)    # prints False
items = [a, a, b]
print(get_all_items_in_list(items))

Output :

False

dict_values([(1, []), (1, [])])
awakened_iota
  • 580
  • 5
  • 13
0

Try this,

a=10
b=20
c=30
l=[id(a),id(b),id(c)]
id(a) in l
Shubham Mishra
  • 834
  • 6
  • 8
  • 1
    I think this would work in certain cases if you have a static list, but in my application items are dynamically added to the array. So I'm not able to do it like this. – Daan Seuntjens Apr 11 '20 at 14:14