I've found numerous solutions on how to repeat a List of Lists while flattening the repeated elements, like so:
[[1,2] for x in range(3)]
: [[1, 2], [1, 2], [1, 2]]
However, I actually have a list of objects that need to be repeated in-line in the List, like so:
mylist = [var1, var2, var1, var2, va1, var2]
without the extra inner list, and importantly, var1 should be copied, not referenced, such that changes to mylist[0] will not also change mylist[2] & mylist[4] etc.
Other solutions regarding lists only still leave the list elements as “pointers”, meaning editing one element actually alters all the repeats of that element in the list.
Is there a readable one-liner for using multiplication/comprehension to do this while removing the “pointers” in the list?
This is an operation users may have to do often with my class and a for() loop will make construction of their list much less readable. I'm already sad that we can't just use [var1, var2] * 30 without overloading __mult__, which I'm trying to avoid. That would have made their code so readable.
For example, the following use of deepcopy() is not good enough, as the objects are referenced and thus unexpectedly altered
>>> obj1=[1]; obj2=[200]
>>> a=deepcopy( 3*[obj1, obj2] )
>>> a
[[1], [200], [1], [200], [1], [200]]
>>> a[0][0]=50
>>> a
[[50], [200], [50], [200], [50], [200]]
the 50 propagated throughout the list, rather than changing only the first element.
You need
import copy
[copy.copy(y) for x in range(3) for y in [var1, var2]]
Or shallow copy is not enough
[copy.deepcopy(y) for x in range(3) for y in [var1, var2]]
I'm not quite sure what behavior you're looking for. Depending on what you want, one of these two options might be right:
In [0]: from itertools import chain
In [1]: from copy import deepcopy
In [2]: [deepcopy(x) for x in 3*[var1, var2]]
Out[2]: [[1, 2], [3, 4], [1, 2], [3, 4], [1, 2], [3, 4]]
In [3]: list( chain( *(3*[var1, var2]) ) )
Out[3]: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
Related
I have a function that sorts a list of lists by the first list. When I use the function with the variables like so:
sort_lists(IN[0],IN[1],IN[2])
it works perfectly. Although, as I don't know how many lists my input contains, I want to use this as my variable:
sort_lists(IN[idx] for idx in range(len(IN)))
Although this returns a sorting of one list (the superlist). Why is there a difference between these variables, and how can I improve the code?
Here is the function if decisive (here IN[0] is the input with a number of sublists):
def sort_lists(*args):
zipped_list= zip(*sorted(zip(*args)))
return [list(l) for l in zipped_list]
OUT = sort_lists(data_sort[0],data_sort[1],data_sort[2])
I want to use this output:
OUT = sort_lists(data_sort[idx] for idx in range(len(IN[0])))
Two things to understand here:
*args will give you all function parameters as a tuple
IN[idx] for idx in range(len(IN)) is a generator expression
You can see how your inputs are different if you simply add print statement in your function:
def sort_lists(*args):
print(args)
zipped_list= zip(*sorted(zip(*args)))
return [list(l) for l in zipped_list]
Let the input list of lists be: lists = [[2, 1, 3], [1, 3, 4], [5, 4, 2]].
sort_lists(lists[0], lists[1], lists[2])
will print: ([2, 1, 3], [1, 3, 4], [5, 4, 2]). That's a tuple of inner lists.
Though, if you call it like this:
sort_lists(lists[idx] for idx in range(len(lists)))
or
sort_lists(sublist for sublist in lists)
this will print (<generator object <genexpr> at 0x0000007001D3FBA0>,), a one-element tuple of a generator.
You can make your function work with a generator by accepting only one parameter:
def sort_lists(arg):
zipped_list= zip(*sorted(zip(*arg)))
return [list(l) for l in zipped_list]
sort_lists(lists[idx] for idx in range(len(lists)))
# [[1, 2, 3], [3, 1, 4], [4, 5, 2]]
but I suggest to leave your function as is, and unpack your lists in the place where you call it instead:
>>> sort_lists(*lists)
[[1, 2, 3], [3, 1, 4], [4, 5, 2]]
Just change the function to accept list of lists, is it problem? This piece of code:
IN[idx] for idx in range(len(IN))
returns again list of lists
As Georgy pointed out, the difference is between the arguments being a generator or list. I would also like to point out that this is an opportunity to use and practice the map method. map applies the same function to each entry in a list. The function can be a built-in like sorted.
list_a = [[2, 1, 3], [1, 3, 4], [5, 4, 2]]
sorted_list_a = list(map(sorted, list_a)) # sorted is the python built-in function
print(sorted_list_a)
Returns:
[[1, 2, 3], [1, 3, 4], [2, 4, 5]]
You'll notice that you'll have to pass your map to the list function because map returns a map object, so you have to turn it into a list.
The map documentation is here. And a good example of it is here.
I want to do the following elegantly. I have a list:
list1 = [[1,2],[3,1,4,7],[5],[7,8]]
I'd like to append the number 1 to each element of the list, so that I have
list1 = [[1,2,1],[3,1,4,7,1],[5,1],[7,8,1]]
I'm trying to map this via
map(list.append([1]), vectors)
but this returns the error append() takes exactly one argument (0 given) and if I just try append([1]) (without list.), I get NameError: global name 'append' is not defined. I guess I could do it with a loop, but this seems more elegant, is there a way to map this correctly?
Here is a several ways to implement what you want:
More readable and classic way
for el in list1:
el.append(1)
List comprehension
list1 = [el + [1] for el in list1]
Generators:
list1 = (el + [1] for el in list1)
Map
list1 = map(lambda el: el + [1], list1)
What to use?
It depends on you own situation and may depends on execution speed optimizations, code readability, place of usage.
Map is a worst choice in case of readability and execution speed
For is a fastest and more plain way to do this
Generators allows you to generate new list only when you really need this
List comprehension - one liner for classic for and it takes advantage when you need quickly filter the new list using if
i.e. if you need only add element to each item - for loop is a best choice to do this, but if you need add item only if item > 40, then you may consider to use List comprehension.
For example:
Classic For
x = 41
for el in list1:
if x > 40:
el.append(x)
List comprehension
x = 1
list1 = [el + [x] for el in list1 if x > 40]
as #jmd_dk mentioned, in this sample is one fundamental difference: with simple for you can just append to an existing object of the list which makes much less impact to execution time and memory usage. When you use List comprehension, you will get new list object and in this case new list object for each item.
Try a list comprehension, taking advantage of the fact the adding lists concats them together.
new_list = [l + [1] for l in list1]
You can simply do
list1 = [[1,2],[3,1,4,7],[5],[7,8]]
for el in list1:
el.append(1)
map(lambda x: x + [1], list1)
you mean this?
list.append() have NO return (mean always return None)
With list comprehension and append, you can do:
list1 = [[1, 2], [3, 1, 4, 7], [5], [7, 8]]
[item.append(1) for item in list1]
print(list1) # Output: [[1, 2, 1], [3, 1, 4, 7, 1], [5, 1], [7, 8, 1]]
Output:
>>> list1 = [[1, 2], [3, 1, 4, 7], [5], [7, 8]]
>>> [item.append(1) for item in list1]
[None, None, None, None]
>>> list1
[[1, 2, 1], [3, 1, 4, 7, 1], [5, 1], [7, 8, 1]]
You may also use extend like this:
[item.extend([1]) for item in list1]
print(list1) # Output: [[1, 2, 1], [3, 1, 4, 7, 1], [5, 1], [7, 8, 1]]
This question already has answers here:
How to remove duplicate lists in a list of list? [duplicate]
(2 answers)
Closed 6 years ago.
as a python list follow
list1 = [[1,2],[3,4],[1,2]]
I want make a set so I can the unique list items like
list2 = [[1,2],[3,4]].
Is there some function in python I can use. Thanks
That will do:
>>> list1 = [[1,2],[3,4],[1,2]]
>>> list2 = list(map(list, set(map(tuple,list1))))
>>> list2
[[1, 2], [3, 4]]
Unfortunately, there is not a single built-in function that can handle this. Lists are "unhashable" (see this SO post). So you cannot have a set of list in Python.
But tuples are hashable:
l = [[1, 2], [3, 4], [1, 2]]
s = {tuple(x) for x in l}
print(s)
# out: {(1, 2), (3, 4)}
Of course, this won't help you if you want to later, say, append to these lists inside your main data structure, as they are now all tuples. If you absolutely must have the original list functionality, you can check out this code recipe for uniquification by Tim Peters.
Note that this only removes duplicate sublists, it does not take into account the sublist's individual elements. Ex: [[1,2,3], [1,2], [1]] -> [[1,2,3], [1,2], [1]]
>>> print map(list, {tuple(sublist) for sublist in list1})
[[1, 2], [3, 4]]
You can try this:
list1 = [[1,2],[3,4],[1,2]]
list2 = []
for i in list1:
if i not in list2:
list2.append(i)
print(list2)
[[1, 2], [3, 4]]
The most typical solutions have already been posted, so let's give a new one:
Python 2.x
list1 = [[1, 2], [3, 4], [1, 2]]
list2 = {str(v): v for v in list1}.values()
Python 3.x
list1 = [[1, 2], [3, 4], [1, 2]]
list2 = list({str(v): v for v in list1}.values())
There is no inbuilt single function to achieve this. You have received many answers. In addition to those, you may also use a lambda function to achieve this:
list(map(list, set(map(lambda i: tuple(i), list1))))
There are many questions similar to this (here is one) but the solutions I've seen use list comprehension or filter and those ways generate a new list (or a new iterator in the case of filter in Python 3.x). There are solutions that remove instances by modifying the list itself (like this, and this), and those would be my last resort. However, I am asking to see if there a more elegant ("Pythonic" as another question calls it) way of doing it.
Why I am disregarding solutions that generate a new list: I am iterating over a collection of lists, and Python allows me to modify the "current" list I am iterating over, but not to replace it entirely:
>>> ll= [[1,2,3], [2,3,4], [4,5,6]]
>>> for l in ll:
if l[0] == 2:
l = [10]
>>> ll
[[1, 2, 3], [2, 3, 4], [4, 5, 6]] #replacement didn't happen
>>> for l in ll:
if l[0] == 2:
l.remove(2)
>>> ll
[[1, 2, 3], [3, 4], [4, 5, 6]] #modification succeeded
You need to use slice assignment to replace all list elements instead of the list itself:
for l in ll:
if l[0] == 2:
l[:] = [10]
The [:] part turns this into slice assignment; instead of replacing the reference l points to, you replace all elements contained in l with the new list.
Demo:
>>> ll= [[1,2,3], [2,3,4], [4,5,6]]
>>> for l in ll:
... if l[0] == 2:
... l[:] = [10]
...
>>> ll
[[1, 2, 3], [10], [4, 5, 6]]
I've just started programming, and am working my way through "How to think like a Computer Scientist" for Python. I haven't had any problems until I came to an exercise in Chapter 9:
def add_column(matrix):
"""
>>> m = [[0, 0], [0, 0]]
>>> add_column(m)
[[0, 0, 0], [0, 0, 0]]
>>> n = [[3, 2], [5, 1], [4, 7]]
>>> add_column(n)
[[3, 2, 0], [5, 1, 0], [4, 7, 0]]
>>> n
[[3, 2], [5, 1], [4, 7]]
"""
The code should make the above doctest pass. I was getting stuck on the last test: getting the original list to stay unaffected. I looked up the solution, which is the following:
x = len(matrix)
matrix2 = [d[:] for d in matrix]
for z in range(x):
matrix2[z] += [0]
return matrix2
My question is this: why can't the second line be:
matrix2 = matrix[:]
When this line is in place the original list gets edited to include the addition elements. The "How to be.." guide makes it sound like cloning creates a new list that can be edited without affecting the original list. If that were true, what's going on here? If I use:
matrix2 = copy.deepcopy(matrix)
Everything works fine, but I wasn't under the impression that cloning would fail...
any help would be greatly appreciated!
In your case, matrix contains other lists, so when you do matrix[:], you are cloning matrix, which contains references to other lists. Those are not cloned too. So, when you edit these, they are still the same in the original matrix list. However, if you append an item to the copy (matrix[:]), it will not be appended to the original list.
To visualize this, you can use the id function which returns a unique number for each object: see the docs.
a = [[1,2], [3,4], 5]
print 'id(a)', id(a)
print '>>', [id(i) for i in a]
not_deep = a[:]
# Notice that the ids of a and not_deep are different, so it's not the same list
print 'id(not_deep)', id(not_deep)
# but the lists inside of it have the same id, because they were not cloned!
print '>>', [id(i) for i in not_deep]
# Just to prove that a and not_deep are two different lists
not_deep.append([6, 7])
print 'a items:', len(a), 'not_deep items:', len(not_deep)
import copy
deep = copy.deepcopy(a)
# Again, a different list
print 'id(deep)', id(deep)
# And this time also all the nested list (and all mutable objects too, not shown here)
# Notice the different ids
print '>>', [id(i) for i in deep]
And the output:
id(a) 36169160
>> [36168904L, 35564872L, 31578344L]
id(not_deep) 35651784
>> [36168904L, 35564872L, 31578344L]
a items: 3 not_deep items: 4
id(deep) 36169864
>> [36168776L, 36209544L, 31578344L]
Say you have nested lists, copying will only copy the references to those nested lists.
>>> a = [1]
>>> b = [2]
>>> c = [a, b]
>>> c
[[1], [2]]
>>> d = c[:]
>>> d
[[1], [2]]
>>> d[1].append(2)
>>> d
[[1], [2, 2]]
>>> c
[[1], [2, 2]]
As where, with copy.deepcopy():
>>> d = copy.deepcopy(c)
>>> d[1].append(2)
>>> c
[[1], [2]]
>>> d
[[1], [2, 2]]
This is true of any mutable items. copy.deepcopy() will attempt to make sure that they are copied too.
It's also worth noting that using d = c[:] to copy a list isn't a very clear syntax anyway. A much better solution is d = list(c) (list() returns a new list from any iterable, including another list). Even more clear, obviously, is copy.copy().