Python list comprehension does not seem to work - python

I am trying to implement a list comprehension for a for loop-nested if code block. However, what seems to work using the conventional nested form does not seem to work in its list comprehension representation. From what I could see after spending a considerable amount of time is that it follows the logic as required. Please let me know if there is anything else I am skipping.
for x in data_list:
if x not in encoding:
encoding.append(x)
Using list comprehension
encoding = [x for x in data_list if x not in encoding]
Thank you.

Your issue is here:
encoding = [x for x in data_list if x not in encoding]
What you are doing is you are reassigning encoding to this list comprehension [x for x in data_list if x not in encoding].
So what you're doing is setting encoding to be only the elements that weren't in it in the first place.
What you should do is this:
encoding.extend([x for x in data_list if x not in encoding])
In this way, you're extending the list with the results of the list comprehension.
Here's some test code:
encoding = ['a','b','c','d']
encoding2 = ['a','b','c','d']
data_list = ['a','b','c','d','d','d','e','f','g']
print(encoding)
print(encoding2)
for x in data_list:
if x not in encoding:
encoding.append(x)
encoding2.extend([x for x in data_list if x not in encoding2])
print(encoding)
print(encoding2)
which prints:
['a', 'b', 'c', 'd']
['a', 'b', 'c', 'd']
['a', 'b', 'c', 'd', 'e', 'f', 'g']
['a', 'b', 'c', 'd', 'e', 'f', 'g']
Now, this is not a perfect solution, as it will still duplicate elements if they're in data_list more than once. The reason for this is that in the for loop example, encoding is checked after each append operation whereas the list comprehension operates only based on encoding's initial state. So it will push elements into it more than once if they're in data_list more than once.
If you want to get around this, convert the list comprehension to a set first as follows:
encoding.extend(set([x for x in data_list if x not in encoding]))
There you have it!

Your two examples are not equivalent. If you want to append elements from data_list to encoding that are not already in encoding (which is what your for-loop example does), but using list-comprehension, do:
encoding.extend(x for x in data_list if x not in encoding)

[x for x in data_list if x not in encoding] executes on 'one breath', so it doesn't update encoding on the run, resulting in empty encoding that leads to every item being selected.
Use list(set(data_list)) to do what you are trying to achieve. That will convert the list to set, resulting in loss of duplicates, then convert it back to list.
>>> a = [1, 2, 3, 2, 3]
>>> list(set(a))
[1, 2, 3]
>>> a = [1, 4, 2, 5, 'g', 'd', 'g', 2, 4]
>>> list(set(a))
[1, 2, 'd', 4, 5, 'g']
If you already have data in encoding, you can simply add it:
list(set([x for x in data_list if x not in encoding]))

The 2 encoding's in your list comprehension are not the same variable. If you must have an one liner, reduce is more appropriate:
encoding = reduce(lambda l,x: x in l and l or l+[x], data_list, encoding)
although I don't think it's very readable.

Related

Taking the 'product' of lists

Suppose that I am given a list of strings, e.g. list = ['a', 'b', 'c']. I am also given a list of 'continuation strings', e.g. continuations = ['d', 'f'], and I want to form a list of all possible sequences formed by combining the original list with a continuation letter. In this example, I want to obtain the list of lists: new_list = [['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'f']]. To do this, I tried
new_list = []
for element in continuations:
# Make a copy of the original list
copy = list
# Add a continuation letter to the original list
possible_sequence = copy.append(element)
# Add the new list to the list of lists
new_list.append(possible_sequence)
But this generates [None, None]... Can anyone explain what is wrong with my code?
# it is a bad practice to shadows built-in name, so I changed 'list' name to 'abc_list'
abc_list = ['a', 'b', 'c']
continuation = ['d', 'f']
print([abc_list + [x] for x in continuation])
Output: [['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'f']]
CODE
main_list = ['a', 'b', 'c']
continuations = ['d', 'f']
new_list = []
for element in continuations:
temp_list = main_list.copy()
temp_list.append(element)
new_list.append(temp_list)
print(new_list)
OUTPUT
[['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'f']]
Here's how I would do it.
create a list to store the possible sequences
iterate through the continuation list
copy the original list
append the continuation letter to the copied list
append the copied list to the possible list
def combine_list(list_, cont_list):
# create a list to store the possible sequences
possible_list = []
# iterate through the continuation list
for j in cont_list:
# copy the original list
l2 = list_.copy()
# append the continuation letter to the copied list
l2.append(j)
# append the copied list to the possible list
possible_list.append(l2)
return possible_list
l = ['a', 'b', 'c']
c = ['d', 'f']
print(combine_list(l, c))
Output:
[['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'f']]
Edit
What's wrong with your code?
If you want to copy a list you need to it with list.copy(). If you just do copy = list you are not creating a new list object. if you make changes in copy all changes will apply to list also.
The list.append(element) function does not return a list object it returns None that's why your result looks like this [None, None] you appended None twice.
In Python, append modifies the list it is called on and doesn't return anything (technically it returns None, which is why you ended up with a list full of None). That means that you cannot store the result of append in a variable.
my_list = []
foo = my_list.append(1)
print(foo) # Prints 'None' because that's all that append returns
print(my_list) # Prints '[1]' because the value got added to the original list by append
This is the big difference between lists and strings in Python that beginners sometimes get confused about. Strings are immutable which means they cannot be changed. So methods such as replace return a new string, because they cannot modify the original string. Lists, on the other hand, are mutable, meaning they can be modified. So methods on lists such as append or pop modify the list they are called on rather than returning a new one.
my_string = "Python"
# Needs to be stored in a new variable,
# the original string cannot be modified
new_string = my_string.replace("n", "k")
print(my_string) # Still the original value, Python
print(new_string) # The new modified value, Pythok
my_list = [1, 2]
my_list.append(3) # Modified the list itself, no need to store anything new
print(my_list) # [1, 2, 3]
Also, note that it is an extremely bad idea to call one of your lists list as list is a keyword in Python. (It is used to construct new lists, e.g. list(range(10)) creates a list [0, 1, ..., 9]).

how remove bracket from almost a nested list? [duplicate]

This question already has answers here:
Flatten an irregular (arbitrarily nested) list of lists
(51 answers)
Closed 1 year ago.
I have the following list:
my_list=[[['A','B'],'C'],[['S','A'],'Q']]
How I can remove the bracket from the first two elements?
output:
my_list=[['A','B','C'],['S','A','Q']]
Slice it in?
for a in my_list:
a[:1] = a[0]
This produces the desired output:
[x[0]+[x[1]] for x in my_list]
my_list=[
[['A','B'],'C'],
[['S','A'],'Q'],
]
result = [[item1, item2, item3] for (item1, item2), item3 in my_list]
>>> result
[['A', 'B', 'C'], ['S', 'A', 'Q']]
You can use the flattening solution in Cristian's answer on Flatten an irregular list of lists.
>>> [list(flatten(x)) for x in my_list]
[['A', 'B', 'C'], ['S', 'A', 'Q']]
Other people have already given the oneline solutions, but let's take a step back and think about how to approach this problem, so you can solve it for yourself.
The tools you need:
for [element] in list: Iterate over each element in a list
list[i]: Get the ith element of a list
list.append(element): Add an element to a list
Let's start with the simple case. We have [['A','B'],'C'] and want ['A','B','C'].
We want to
Get the sublist ['A', 'B']
Get the single item that isn't in the sublist, 'C'
Add the single item into the sublist
Make the sublist the main list
Sometimes it's easiest to sketch these things out in the python shell:
>>> l = [['A','B'],'C']
>>> l[0]
['A', 'B']
>>> l[1]
'C'
>>> l[0].append(l[1])
>>> l = l[0]
>>> l
['A', 'B', 'C']
Now, we can build up a function to do this
def fix_single_element(element):
"""
Given a list in the format [['A','B'],'C']
returns a list in the format ['A','B','C']
"""
# We use copy since we don't want to mess up the old list
internal_list = element[0].copy()
last_value = element[1]
internal_list.append(last_value)
return internal_list
Now we can use that:
>>> for sublist in my_list:
... print(sublist)
...
[['A', 'B'], 'C']
[['S', 'A'], 'Q']
Note that the sublists are exactly the problem we just solved.
>>> new_list = []
>>> for sublist in my_list:
... new_list.append(fix_single_element(sublist))
...
>>> new_list
[['A', 'B', 'C'], ['S', 'A', 'Q']]
There are LOTS of ways to do any particular task, and this is probably not the "best" way, but it's a way that will work. Focus on writing code you understand and can change, not the shortest code, especially when you start.

Splitting string values in list into individual values, Python

I have a list of values in which some values are words separated by commas, but are considered single strings as shown:
l = ["a",
"b,c",
"d,e,f"]
#end result should be
#new_list = ['a','b','c','d','e','f']
I want to split those strings and was wondering if there's a one liner or something short to do such a mutation. So far what, I was thinking of just iterating through l and .split(',')-ing all the elements then merging, but that seems like it would take a while to run.
import itertools
new_list = list(itertools.chain(*[x.split(',') for x in l]))
print(new_list)
>>> ['a', 'b', 'c', 'd', 'e', 'f']
Kind of unusual but you could join all your elements with , and then split them:
l = ["a",
"b,c",
"d,e,f"]
newList = ','.join(l).split(',')
print(newList)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
Here's a one-liner using a (nested) list comprehension:
new_list = [item for csv in l for item in csv.split(',')]
See it run here.
Not exactly a one-liner, but 2 lines:
>>> l = ["a",
"b,c",
"d,e,f"]
>>> ll =[]
>>> [ll.extend(x.split(',')) for x in l]
[None, None, None]
>>> ll
['a', 'b', 'c', 'd', 'e', 'f']
The accumulator needs to be created separately since x.split(',') can not be unpacked inside a comprehension.

Access list elements that are not equal to a specific value

I am searching through a list like this:
my_list = [['a','b'],['b','c'],['a','x'],['f','r']]
and I want to see which elements come with 'a'. So first I have to find lists in which 'a' occurs. Then get access to the other element of the list. I do this by abs(pair.index('a')-1)
for pair in my_list:
if 'a' in pair:
print( pair[abs(pair.index('a')-1)] )
Is there any better pythonic way to do that?
Something like: pair.index(not 'a') maybe?
UPDATE:
Maybe it is good to point out that 'a' is not necessarily the first element.
in my case, ['a','a'] doesn't happen, but generally maybe it's good to choose a solution which handles this situation too
Are you looking for elements that accompany a? If so, a simple list comprehension will do:
In [110]: [x for x in my_list if 'a' in x]
Out[110]: [['a', 'b'], ['a', 'x']]
If you just want the elements and not the pairs, how about getting rid of a before printing:
In [112]: [(set(x) - {'a'}).pop() for x in my_list if 'a' in x]
Out[112]: ['b', 'x']
I use a set because a could either be the first or second element in the pair.
If I understand your question correctly, the following should work:
my_list = filter(
lambda e: 'a' not in e,
my_list
)
Note that in python 3, this returns a filter object instance. You may want to wrap the code in a list() command to get a list instance instead.
That technique works ok here, but it may be more efficient, and slightly more readable, to do it using sets. Here's one way to do that.
def paired_with(seq, ch):
chset = set(ch)
return [(set(pair) - chset).pop() for pair in seq if ch in pair]
my_list = [['a','b'], ['b','c'], ['x','a'], ['f','r']]
print(paired_with(my_list, 'a'))
output
['b', 'x']
If you want to do lots of tests on the same list, it would be more efficient to build a list of sets.
def paired_with(seq, ch):
chset = set(ch)
return [(pair - chset).pop() for pair in seq if ch in pair]
my_list = [['a','b'], ['b','c'], ['x','a'], ['f','r']]
my_sets = [set(u) for u in my_list]
print(my_sets)
print(paired_with(my_sets, 'a'))
output
[{'b', 'a'}, {'c', 'b'}, {'x', 'a'}, {'r', 'f'}]
['b', 'x']
This will fail if there's a pair like ['a', 'a'], but we can easily fix that:
def paired_with(seq, ch):
chset = set(ch)
return [(pair - chset or chset).pop() for pair in seq if ch in pair]
my_list = [['a','b'], ['b','c'], ['x','a'], ['f','r'], ['a', 'a']]
my_sets = [set(u) for u in my_list]
print(paired_with(my_sets, 'a'))
output
['b', 'x', 'a']

How to convert frozenset to normal sets or list?

For example, I have a frozen set
[frozenset({'a', 'c,'}), frozenset({'h,', 'a,'})]
I want to convert it to a normal list like
[['a', 'c,'],['a,', 'd,']...]
What method should I use?
sets=[frozenset({'a', 'c,'}), frozenset({'h,', 'a,'})]
print([list(x) for x in sets])
The list comprehension will convert every frozenset in your list of sets and put them into a new list. That's probably what you want.
You can also you map, map(list, sets). Please be aware, that in Python 3, if you want the result of map as list you need to manually convert it using list, otherwise, it's just a map object which looks like <map object 0xblahblah>
We can convert frozenset object x to list or set by using list() or set() on it. So we can do this on each item in the list containing frozensets as mentioned in the another by Ivan
>>> x=frozenset(['a','b','c','d'])
>>> print(x)
frozenset({'c', 'd', 'a', 'b'})
>>> y=list(x)
>>> y
['c', 'd', 'a', 'b']
>>> y = set(x)
>>> y
{'c', 'd', 'a', 'b'}

Categories