Related
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]).
my_list = ['a', 'b', 'a', 'd', 'e', 'f']
my_list_2 = ['a', 'b', 'c']
The common items are:
c = ['a', 'b', 'a']
The code:
for e in my_list:
if e in my_list_2:
c.append(e)
...
If the my_list is long, this would be very inefficient. If I convert both lists into two sets, then use set's intersection() function to get the common items, I will lose the duplicates in my_list.
How to deal with this efficiently?
dict is already a hashmap, so lookups are practically as efficient as a set, so you may not need to do any extra work collecting the values - if it wasn't, you could pack the values into a set to check before checking the dict
However, a large improvement may be to make a generator for the values, rather than creating a new intermediate list, to iterate over where you actually want the values
def foo(src_dict, check_list):
for value in check_list:
if value in my_dict:
yield value
With the edit, you may find you're better off packing all the inputs into a set
def foo(src_list, check_list):
hashmap = set(src_list)
for value in check_list:
if value in hashmap:
yield value
If you know a lot about the inputs, you can do better, but that's an unusual case (for example if the lists are ordered you could bisect, or if you have a huge verifying list or very very few values to check against it you may find some efficiency in the ordering and if you make a set)
I am not sure about time efficiency, but, personally speaking, list comprehension would always be more of interest to me:
[x for x in my_list if x in my_list_2]
Output
['a', 'b', 'a']
First, utilize the set.intersection() method to get the intersecting values in the list. Then, use a nested list comprehension to include the duplicates based on the original list's count on each value:
my_list = ['a', 'b', 'a', 'd', 'e', 'f']
my_list_2 = ['a', 'b', 'c']
c = [x for x in set(my_list).intersection(set(my_list_2)) for _ in range(my_list.count(x))]
print(c)
The above may be slower than just
my_list = ['a', 'b', 'a', 'd', 'e', 'f']
my_list_2 = ['a', 'b', 'c']
c = []
for e in my_list:
if e in my_list_2:
c.append(e)
print(c)
But when the lists are significantly larger, the code block utilizing the set.intersection() method will be significantly more efficient (faster).
sorry for not reading the post carefully and now it is not possible to delete.. however, it is an attempt for solution.
c = lambda my_list, my_list_2: (my_list, my_list_2, list(set(my_list).intersection(set(my_list_2))))
print("(list_1,list_2,duplicate_items) -", c(my_list, my_list_2))
Output:
(list_1,list_2,duplicate_items) -> (['a', 'b', 'a', 'd', 'e', 'f'], ['a', 'b', 'c'], ['b', 'a'])
or can be
[i for i in my_list if i in my_list_2]
output:
['a', 'b', 'a']
I create a list, and I want to remove a string from it.
Ex:
>>> myList = ['a', 'b', 'c', 'd']
>>> myList = myList.remove('c')
>>> print(myList)
None
What am I doing wrong here? All I want is 'c' to be removed from myList!
I am not sure what a is (I am guessing another list), you should do myList.remove() alone, without assignment.
Example -
>>> myList = ['a', 'b', 'c', 'd']
>>> myList.remove('c')
>>> myList
['a', 'b', 'd']
myList.remove() does not return anything, hence when you do myList = <anotherList>.remove(<something>) it sets myList to None
Remember that lists are mutable, so you can simply call remove on the list itself:
>>> myList = ['a', 'b', 'c', 'd']
>>> myList.remove('c')
>>> myList
['a', 'b', 'd']
The reason you were getting None before is because remove() always returns None
Just an addition to Anand's Answer,
mylist = mylist.remove('c')
The above code will return 'none' as the return type for my list. So you want to keep it as
mylist.remove('c')
The remove() function doesn't return anything, it modifies the list in place. If you don't assign it to a variable you will see that myList doesn't contain c anymore.
This question already has answers here:
Loop "Forgets" to Remove Some Items [duplicate]
(10 answers)
Closed 8 years ago.
I have a simple question about lists
Suppose that I want to delete all 'a's from a list:
list = ['a', 'a', 'b', 'b', 'c', 'c']
for element in list:
if element == 'a':
list.remove('a')
print list
==> result:
['a', 'b', 'b', 'c', 'c', 'd', 'd']
I know this is happening because, after I remove the first 'a', the list index gets
incremented while all the elements get pushed left by 1.
In other languages, I guess one way to solve this is to iterate backwards from the end of the list..
However, iterating through reversed(list) returns the same error.
Is there a pythonic way to solve this problem??
Thanks
One of the more Pythonic ways:
>>> filter(lambda x: x != 'a', ['a', 'a', 'b', 'b', 'c', 'c'])
['b', 'b', 'c', 'c']
You should never modify a list while iterating over it.
A better approach would be to use a list comprehension to exclude an item:
list1 = ['a', 'a', 'b', 'b', 'c', 'c']
list2 = [x for x in list1 if x != 'a']
Note: Don't use list as a variable name in Python - it masks the built-in list type.
You are correct, when you remove an item from a list while iterating over it, the list index gets out of sync. What both the other existing answers are hinting at is that you need to create a new list and copy over only the items you want.
For example:
existing_list = ['a', 'a', 'b', 'c', 'd', 'e']
new_list = []
for element in existing_list:
if element != 'a':
new_list.append(element)
existing_list = new_list
print existing_list
outputs: ['b', 'c', 'd', 'e']
I'm new to programming in general, so looking to really expand my skills here. I'm trying to write a script that will grab a list of strings from an object, then order them based on a template of my design. Any items not in the template will be added to the end.
Here's how I'm doing it now, but could someone suggest a better/more efficient way?
originalList = ['b', 'a', 'c', 'z', 'd']
listTemplate = ['a', 'b', 'c', 'd']
listFinal = []
for thing in listTemplate:
if thing in originalList:
listFinal.append(thing)
originalList.pop(originalList.index(thing))
for thing in originalList:
listFinal.append(thing)
originalList.pop(originalList.index(thing))
Try this:
originalList = ['b', 'a', 'c', 'z', 'd']
listTemplate = ['a', 'b', 'c', 'd']
order = { element:index for index, element in enumerate(listTemplate) }
sorted(originalList, key=lambda element: order.get(element, float('+inf')))
=> ['a', 'b', 'c', 'd', 'z']
This is how it works:
First, we build a dictionary indicating, for each element in listTemplate, its relative order with respect to the others. For example a is 0, b is 1 and so on
Then we sort originalList. If one of its elements is present in the order dictionary, then use its relative position for ordering. If it's not present, return a positive infinite value - this will guarantee that the elements not in listTemplate will end up at the end, with no further ordering among them.
The solution in the question, although correct, is not very pythonic. In particular, whenever you have to build a new list, try to use a list comprehension instead of explicit looping/appending. And it's not a good practice to "destroy" the input list (using pop() in this case).
You can create a dict using the listTemplate list, that way the expensive(O(N)) list.index operations can be reduced to O(1) lookups.
>>> lis1 = ['b', 'a', 'c', 'z', 'd']
>>> lis2 = ['a', 'b', 'c', 'd']
Use enumerate to create a dict with the items as keys(Considering that the items are hashable) and index as values.
>>> dic = { x:i for i,x in enumerate(lis2) }
Now dic looks like:
{'a': 0, 'c': 2, 'b': 1, 'd': 3}
Now for each item in lis1 we need to check it's index in dic, if the key is not found we return float('inf').
Function used as key:
def get_index(key):
return dic.get(key, float('inf'))
Now sort the list:
>>> lis1.sort(key=get_index)
>>> lis1
['a', 'b', 'c', 'd', 'z']
For the final step, you can just use:
listFinal += originalList
and it will add these items to the end.
There is no need to create a new dictionary at all:
>>> len_lis1=len(lis1)
>>> lis1.sort(key = lambda x: lis2.index(x) if x in lis2 else len_lis1)
>>> lis1
['a', 'b', 'c', 'd', 'z']
Here is a way that has better computational complexity:
# add all elements of originalList not found in listTemplate to the back of listTemplate
s = set(listTemplate)
listTemplate.extend(el for el in originalList if el not in s)
# now sort
rank = {el:index for index,el in enumerate(listTemplate)}
listFinal = sorted(originalList, key=rank.get)