77

I am trying to check if id is in a list and append the id only if its not in the list using the below code..however I see that the id is getting appended even though id is already present in the list.. can anyone provide inputs on what is wrong here?

   list = ['350882 348521 350166\r\n']
    id = 348521
    if id not in list:
        list.append(id)
    print list

OUTPUT:-
['350882 348521 350166\r\n', 348521]
user2341103
  • 2,333
  • 5
  • 20
  • 19
  • 2
    Your id is a part of the string, that is why the value is appended. `348521` is not equal to that string. – Rohit Jain Jun 28 '13 at 18:05
  • Why the weird list contents? a) they're strings, b) there seem to be multiple ids in one element. I would suggest you canonicalized your data structure first. – Nils Werner Jun 28 '13 at 18:06
  • @NilsWerner - how to check if an id is already present in one element which contains multiple ids – user2341103 Jun 28 '13 at 18:10
  • 2
    Change your data structrure first. – Nils Werner Jun 28 '13 at 18:12
  • @NilsWerner - it cannot be changed – user2341103 Jun 28 '13 at 18:13
  • 5
    Just as a warning - both `id` and `list` are already used by the standard library. If you overwrite them, you're going to have a bad time. – viraptor Jun 28 '13 at 18:14
  • 1
    @user2341103 What are your real constraints? That data structure can be changed. You're doing that with `.append()`. Tell us what you want to achieve as a result: values concatenated again into a string? Do you have multiple strings in the list? Something else? – viraptor Jun 28 '13 at 18:17

8 Answers8

110

What you are trying to do can almost certainly be achieved with a set.

>>> x = set([1,2,3])
>>> x.add(2)
>>> x
set([1, 2, 3])
>>> x.add(4)
>>> x.add(4)
>>> x
set([1, 2, 3, 4])
>>> 

using a set's add method you can build your unique set of ids very quickly. Or if you already have a list

unique_ids = set(id_list)

as for getting your inputs in numeric form you can do something like

>>> ids = [int(n) for n in '350882 348521 350166\r\n'.split()]
>>> ids
[350882, 348521, 350166]
John
  • 13,197
  • 7
  • 51
  • 101
  • 32
    But set is unordered. If OP wants to maintain the original order of the list this won't work. – LWZ Aug 29 '17 at 19:45
  • 3
    You are correct, if any future visitors need to maintain the order they may consider some implementation of an [ordered set](https://stackoverflow.com/questions/1653970/does-python-have-an-ordered-set). – John Aug 29 '17 at 20:38
  • original order will be lost in case something like this is done using a set. – Divij Sehgal Mar 21 '18 at 06:49
  • A better way of doing what you mentioned in the last step is : `ids = list(map(int,input.rstrip().split()))` – Dhiraj Gandhi Aug 31 '18 at 05:02
  • 1
    @DhirajGandhi [it seems most people consider list comprehensions more readable than the functional approach](https://stackoverflow.com/a/1247490/322909) (the functional approach is slightly faster). You shouldn't use variable names that collide with the build-ins, which is what's going on with `input`. The call to `.rstrip` is not needed as `.split` already removes the "whitespace" characters. I don't think I'd call your code better ... maybe faster at the cost of readable. – John Aug 31 '18 at 15:15
44

A more pythonic way, without using set is as follows:

lst = [1, 2, 3, 4]
lst.append(3) if 3 not in lst else lst
Atihska
  • 4,803
  • 10
  • 56
  • 98
  • 17
    This is a misuse of a conditional expression. Just use a normal if-statement: `if 3 not in lst: lst.append(3)` – wjandrea Feb 18 '21 at 21:51
  • 2
    Also this answer suggests `lst.append(3)` would return something other than `None`. I agree with @wjandrea to use a normal if-statement instead or `[*lst, 3] if 3 not in lst else lst` – Stacksatty Feb 19 '21 at 12:04
  • The ternary is strange since you're not assigning it. I agree that a standard if statement is cleaner. At the very least I would make it `lst.append(3) if 3 not in lst else None` since the append() returns NoneType. That at least would give consistency if someone tried to assign this to a variable. – Brent K. Jan 11 '23 at 13:20
9

There are a couple things going on with your example. You have a list containing a string of numbers and newline characters:

list = ['350882 348521 350166\r\n']

And you are trying to find a number ID within this list:

id = 348521
if id not in list:
    ...

Your first conditional is always going to pass, because it will be looking for integer 348521 in list which has one element at index list[0] with the string value of '350882 348521 350166\r\n', so integer 348521 will be added to that list, making it a list of two elements: a string and an integer, as your output shows.

To reiterate: list is searched for id, not the string in list's first element.

If you were trying to find if the string representation of '348521' was contained within the larger string contained within your list, you could do the following, noting that you would need to do this for each element in list:

if str(id) not in list[0]: # list[0]: '350882 348521 350166\r\n'
    ...                    #                  ^^^^^^

However be aware that you would need to wrap str(id) with whitespace for the search, otherwise it would also match:

2348521999
 ^^^^^^

It is unclear whether you want your "list" to be a "string of integers separated by whitespace" or if you really want a list of integers.

If all you are trying to accomplish is to have a list of IDs, and to add IDs to that list only if they are not already contained, (and if the order of the elements in the list is not important,) then a set would be the best data structure to use.

ids = set(
    [int(id) for id in '350882 348521 350166\r\n'.strip().split(' ')]
)

# Adding an ID already in the set has no effect
ids.add(348521)

If the ordering of the IDs in the string is important then I would keep your IDs in a standard list and use your conditional check:

ids = [int(id) for id in '350882 348521 350166\r\n'.strip().split(' ')]

if 348521 not in ids:
    ...
Austen Hoogen
  • 2,726
  • 3
  • 18
  • 12
8

7 years later, allow me to give a one-liner solution by building on a previous answer. You could do the followwing:

numbers = [1, 2, 3]

to Add [3, 4, 5] into numbers without repeating 3, do the following:

numbers = list(set(numbers + [3, 4, 5]))

This results in 4 and 5 being added to numbers as in [1, 2, 3, 4, 5]

Explanation:

Now let me explain what happens, starting from the inside of the set() instruction, we took numbers and added 3, 4, and 5 to it which makes numbers look like [1, 2, 3, 3, 4, 5]. Then, we took that ([1, 2, 3, 3, 4, 5]) and transformed it into a set which gets rid of duplicates, resulting in the following {1, 2, 3, 4, 5}. Now since we wanted a list and not a set, we used the function list() to make that set ({1, 2, 3, 4, 5}) into a list, resulting in [1, 2, 3, 4, 5] which we assigned to the variable numbers

This, I believe, will work for all types of data in a list, and objects as well if done correctly.

Chihab
  • 403
  • 5
  • 11
  • I forgot to mention, this is a general solution. this question's particular case, given that the ids are in a string, one could separate them by `' '` to get a list of strings corresponding each to an id, then stripping the special characters from the last id and apply the solution I gave. – Chihab Dec 31 '20 at 11:18
7

I agree with other answers that you are doing something weird here. You have a list containing a string with multiple entries that are themselves integers that you are comparing to an integer id.

This is almost surely not what you should be doing. You probably should be taking input and converting it to integers before storing in your list. You could do that with:

input = '350882 348521 350166\r\n'
list.append([int(x) for x in input.split()])

Then your test will pass. If you really are sure you don't want to do what you're currently doing, the following should do what you want, which is to not add the new id that already exists:

list = ['350882 348521 350166\r\n']
id = 348521
if id not in [int(y) for x in list for y in x.split()]:
    list.append(id)
print list
JoshG79
  • 1,685
  • 11
  • 14
  • is "int(y) for x in list for y in x.split()" a valid one? – user2341103 Jun 28 '13 at 18:31
  • @user - yes that should be valid. It looks weird, I know. The "for" loops should be read in the order they're written. Then you take whatever is before the for loop as the result. – JoshG79 Jun 28 '13 at 18:34
2

Your list just contains a string. Convert it to integer IDs:

L = ['350882 348521 350166\r\n']

ids = [int(i) for i in L[0].strip().split()]
print(ids)
id = 348521
if id not in ids:
    ids.append(id)
print(ids)
id = 348522
if id not in ids:
    ids.append(id)
print(ids)
# Turn it back into your odd format
L = [' '.join(str(id) for id in ids) + '\r\n']
print(L)

Output:

[350882, 348521, 350166]
[350882, 348521, 350166]
[350882, 348521, 350166, 348522]
['350882 348521 350166 348522\r\n']
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
0

If you really don't want to change your structure, or at least create a copy of it containing the same data (e.g. make a class property with a setter and getter that read from/write to that string behind the scenes), then you can use a regular expression to check if an item is in that "list" at any given time, and if not, append it to the "list" as a separate element.

if not re.match("\b{}\b".format(348521), some_list[0]): some_list.append(348521)

This is probably faster than converting it to a set every time you want to check if an item is in it. But using set as others have suggested here is a million times better.

dav
  • 1,211
  • 1
  • 13
  • 19
-2

Your id variable is a number where your list only has one element. It's a string that contains your other IDs. You either need to check if id is in that string, or pull the numbers out of the string and store them in the list separately

list  = [350882, 348521, 350166]
sedavidw
  • 11,116
  • 13
  • 61
  • 95