Pythonic way to append output of function to several lists - python

I have a question that I haven't quite found a good solution to. I'm looking for a better way to append function output to two or more lists, without using temp variables. Example below:
def f():
return 5,6
a,b = [], []
for i in range(10):
tmp_a, tmp_b = f()
a.append(tmp_a)
b.append(temp_b)
I've tried playing around with something like zip(*f()), but haven't quite found a solution that way.
Any way to remove those temp vars would be super helpful though, thanks!
Edit for additional info:
In this situation, the number of outputs from the function will always equal the number of lists that are being appended to. The main reason I'm looking to get rid of temps is for the case where there are maybe 8-10 function outputs, and having that many temp variables would get messy (though I don't really even like having two).

def f():
return 5,6
a,b = zip(*[f() for i in range(10)])
# this will create two tuples of elements 5 and 6 you can change
# them to list by type casting it like list(a), list(b)

First solution: we make a list of all results, then transpose it
def f(i):
return i, 2*i
# First make a list of all your results
l = [f(i) for i in range(5)]
# [(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]
# then transpose it using zip
a, b = zip(*l)
print(list(a))
print(list(b))
# [0, 1, 2, 3, 4]
# [0, 2, 4, 6, 8]
Or, all in one line:
a, b = zip(*[f(i) for i in range(5)])
A different solution, building the lists at each iteration, so that you can use them while they're being built:
def f(i):
return 2*i, i**2, i**3
doubles = []
squares = []
cubes = []
results = [doubles, squares, cubes]
for i in range(1, 4):
list(map(lambda res, val: res.append(val), results, f(i)))
print(results)
# [[2], [1], [1]]
# [[2, 4], [1, 4], [1, 8]]
# [[2, 4, 6], [1, 4, 9], [1, 8, 27]]
print(cubes)
# [1, 8, 27]
Note about list(map(...)): in Python3, map returns a generator, so we must use it if we want the lambda to be executed.list does it.

For your specific case, the zip answers are great.
Using itertools.cycle and itertools.chain is a different approach from the existing answers that might come in handy if you have a lot of pre-existing lists that you want to append to in a round-robin fashion. It also works when your function returns more values than you have lists.
>>> from itertools import cycle, chain
>>> a, b = [], [] # new, empty lists only for demo purposes
>>> for l, v in zip(cycle([a, b]), (chain(*(f() for i in range(10))))):
... l.append(v)
...
>>> a
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
>>> b
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6]

I'd do
tmp = f()
a.append(tmp[0])
b.append(tmp[1])
Not sure how pythonic it is for you though.

Related

How to join adjacent elements in a list with a string in between?

I am starting on my Python journey and am doing some exercises to get the hang of it all. One question is really giving me troubles as I do not understand how to complete it.
Given a list with an even number of integers, join adjacent elements using '-' and print each pair.
So it will be that this is given:
a = [1, 2, 3, 4, 5, 6, 7, 8]
and the output needs to be:
1-2
3-4
5-6
7-8
Now I have gotten as far as this, but have no idea what to do next:
a = [1, 2, 3, 4, 5, 6, 7, 8]
a1 = a[::2]
a2 = a[1::2]
duos = zip(a1, a2)
print(list(duos))
And this only gives me this as result:
[(1, 2), (3, 4), (5, 6), (7, 8)]
I feel like I am close and just missing one tiny step.
Build a lazy iterator:
>>> a = [1, 2, 3, 4, 5, 6, 7, 8]
>>> it = iter(a)
>>> print([f"{x}-{y}" for x,y in zip(it,it)])
['1-2', '3-4', '5-6', '7-8']
Yep, very close indeed.
You can use a generator expression to form the pair strings without the intermediate variables, then "\n".join to make a single string out of the formatted pairs.
>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8]
>>> print("\n".join(f"{a}-{b}" for (a, b) in zip(numbers[::2], numbers[1::2])))
1-2
3-4
5-6
7-8
The more procedural version (that's functionally equivalent, but doesn't form a list, but just prints each pair) would be
for (a, b) in zip(numbers[::2], numbers[1::2]):
print(f"{a}-{b}")
Completing your work:
for x, y in duos:
print(f'{x}-{y}')
(Note you need to do this instead of your print(list(duos)), otherwise that consumes the zip iterator and there's nothing left.)
You're indeed very close. Now just print each pair in duos on a separate line with a dash as separator:
for a,b in duos: print(a,b,sep="-")
Or you could do it all in one line using a combination of map, zip and join:
print(*map("-".join,zip(*[map(str,a)]*2)),sep="\n")
A somewhat fun[*] alternative, easily adapts to similar cases just by altering the string of ends:
from itertools import cycle
a = [1, 2, 3, 4, 5, 6, 7, 8]
for x, end in zip(a, cycle('-\n')):
print(x, end=end)
For example with cycle('+-*\n'), it would instead print this:
1+2-3*4
5+6-7*8
[*] Everything itertools is fun for me :-)

Can I not change a list inside a function?

# left rotate using slicing
def leftRotate(arr, k, n):
arr = arr[k:] + arr[:k]
print(arr)
arr = [1, 2, 3, 4, 5, 6, 7]
leftRotate(arr, 2, 7)
print(arr)
Result:
[3, 4, 5, 6, 7, 1, 2]
[1, 2, 3, 4, 5, 6, 7]
When I print the array outside the function it is not rotated anymore and remains how it originally was. Can someone help me understand this?
Yes, you can change a list from within a function, but you need to use the correct syntax. As you've seen already, this is not the correct way:
def leftRotate(arr, k, n):
arr = arr[k:] + arr[:k]
I will try to explain why this did not work, and hope to give you a better intuition about what really happens. Inside the scope of the function shown above, there are 3 local variables: arr, k, and n. The right-hand side operations arr[k:] + arr[:k] creates a new list object, without modifying the original list, and this resulting object is bound to the local variable name arr. This does not modify the original object, because such assignment statements in Python are never mutating objects. They will only bind a name in a namespace. Think of it as if you're taking the nametag "arr" off of the old list object, which was passed in as argument, and sticking it on the new list object which was just created. The old list object is not modified by such an operation, only the local namespace is modified - the old list object becomes "anonymous" and is no longer reachable in this scope.
The solution is to use a different kind of assignment statement, a slice assignment, which does mutate:
def leftRotate(arr, k, n):
arr[:] = arr[k:] + arr[:k]
As a final note, there is a list-like data structure in stdlib which provides more efficient rotation operations (at the cost of less-efficient indexing into the middle of the collection). If you're interested in this, read the docs on the collections.deque.
The problem is list slicing is not being applied in place. Effectively a new list is created and assigned to a variable arr scoped to leftRotate, i.e. it can can be accessed within your function only. A method which does work in place will work as expected:
def rev_sort(arr, k, n):
arr.sort(reverse=True)
print(arr)
arr = [1, 2, 3, 4, 5, 6, 7]
rev_sort(arr, 2, 7)
print(arr)
[7, 6, 5, 4, 3, 2, 1]
[7, 6, 5, 4, 3, 2, 1]
In your example, you can have your function return a list and assign it to arr:
def leftRotate(arr, k, n):
arr = arr[k:]+arr[:k]
print(arr)
return arr
arr = [1, 2, 3, 4, 5, 6, 7]
arr = leftRotate(arr, 2, 7)
print(arr)
[3, 4, 5, 6, 7, 1, 2]
[3, 4, 5, 6, 7, 1, 2]
Your problem is because you can't change a variable inside a python function because of the scope. Read this for more info.
But resuming, you need to either return arr and assign it outside. Like this:
#left rotate using slicing
def leftRotate(arr, k, n):
arr=arr[k:]+arr[:k]
return arr
arr = [1, 2, 3, 4, 5, 6, 7]
arr = leftRotate(arr, 2, 7)
print arr
Or if you would like, you could make arr a global. (Check this for more info on that). (Don't recommend this last one, but exists)
arr = [1, 2, 3, 4, 5, 6, 7]
#left rotate using slicing
def leftRotate( k, n):
global arr
arr=arr[k:]+arr[:k]
leftRotate( 2, 7)
print arr
Hope it helped :)
There are a lot of really complicated answers. Here's the "for dummies" version:
You are passing arr into leftRotate()
For nearly all purposes, you can think of this as creating another variable, also called arr which leftRotate() works on. leftRotate()'s arr is not the same as the arr you are passing in to leftRotate(). It is a copy (technically it's not a copy until you assign arr to something else, but close enough for these purposes).
You're not getting your modified arr back out of leftRotate() again.
You can solve this in two ways:
Define arr outside of leftRotate(), and don't pass arr in. I'll call this the "global" approach. Not recommended unless you have a very good reason.
Use return arr after your function completes. Essentially, return 'x' means leftRotate() == 'x'. In 99.99999% of cases, this is what you want.
Therefore, in your example, what you really want is this:
#left rotate using slicing
def leftRotate(arr, k, n):
arr=arr[k:]+arr[:k] # not sure this is right, but once you get the return working, it should be easy to debug
# print arr # changed
return arr
arr = [1, 2, 3, 4, 5, 6, 7]
# leftRotate(arr, 2, 7) # changed
arr = leftRotate(arr, 2, 7)
print arr

Loop from a specific point in a list of lists Python

I would like to append to a new list all elements of an existing list of lists after a specific point
m = [[1,2,3],[4,5,10],[6,2,1]]
specific point = m[0][2]
newlist = [3,4,5,10,6,2,1]
You can directly slice off the remainder of the first target list and then add on all subsequent elements, eg:
m = [[1,2,3],[4,5,10],[6,2,1]]
y, x = 0, 2
new_list = m[y][x:] + [v for el in m[y+1:] for v in el]
# [3, 4, 5, 10, 6, 2, 1]
Here's a couple of functional approaches for efficiently iterating over your data.
If sublists are evenly sized, and you know the index from where to begin extracting elements, use chain + islice:
from itertools import chain, islice
n = 3 # Sublist size.
i,j = 0,2
newlist = list(islice(chain.from_iterable(m), i*n + j, None))
If you don't know the size of your sublists in advance, you can use next to discard the first portion of your data.
V = chain.from_iterable(m)
next(v for v in V if v == m[i][j])
newlist = list(V)
newlist.insert(m[i][j], 0)
This assumes there is no identical value earlier in the sequence.
You can put a conditional in your iteration and only add based on that condition. Once you hit that specific index, make your condition true. Something like this:
m = [[1,2,3],[4,5,10],[6,2,1]]
specific_point = (0,2)
newlist = [3,4,5,10,6,2,1]
output = []
for i in range(len(m)):
for j in range(len(m[i])):
if (i,j) < specific_point:
continue
output.append(m[i][j])
output:
[3, 4, 5, 10, 6, 2, 1]
why not flatten the initial list and go from there
flat_list = [item for sublist in m for item in sublist]
would return [1,2,3,4,5,10,6,2,1] so now you're really on flat_list[2:]
Most of the answers only work for this specific shape of nested list, but it's also possible to create a solution that works with any shape of nested list.
def flatten_from(sequence, path=[]):
start = path.pop(0) if path else 0
for item in sequence[start:]:
if isinstance(item, (list, tuple)):
yield from flatten_from(item, path)
else:
yield item
With the example from the question
>>> list(flatten_from([[1, 2, 3], [4, 5, 10], [6, 2, 1]], [0, 2]))
[3, 4, 5, 10, 6, 2, 1]
It also works with any shape and level of nesting of the input data
m = [[1], [[2], [3, 4, 5, 6, 7]], 8, [9, [10, 11]]]
flatten_from(m, [])) # 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
flatten_from(m, [2]) # 8, 9, 10, 11
flatten_from(m, [1, 1, 3]) # 6, 7, 8, 9, 10, 11
This is a bit of a bastard algorithm, though. On one hand, it uses nice functional programming concepts: recursion and yield.
On the other hand it relies on the side effect of mutating the path argument with list.pop, so it's not a pure function.
Below solution will work for your case where your array is restricted to list of list and the size of 'sublist' is consistent throughout i.e "3" in your case
m = [[1,2,3],[4,5,10],[6,2,1]] #input 2D array
a, b = 0, 2 #user input --> specific point a and b
flat_list_m = [item for firstlist in m for item in firstlist] #flat the 2D list
print (flat_list_m[len(m[0])*a+b:]) #print from specific position a and b, considering your sublist length is consistent throughout.
I hope this helps! :)

How to append multiple values to a list in Python

I am trying to figure out how to append multiple values to a list in Python. I know there are few methods to do so, such as manually input the values, or put the append operation in a for loop, or the append and extend functions.
However, I wonder if there is a more neat way to do so? Maybe a certain package or function?
You can use the sequence method list.extend to extend the list by multiple values from any kind of iterable, being it another list or any other thing that provides a sequence of values.
>>> lst = [1, 2]
>>> lst.append(3)
>>> lst.append(4)
>>> lst
[1, 2, 3, 4]
>>> lst.extend([5, 6, 7])
>>> lst.extend((8, 9, 10))
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> lst.extend(range(11, 14))
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
So you can use list.append() to append a single value, and list.extend() to append multiple values.
Other than the append function, if by "multiple values" you mean another list, you can simply concatenate them like so.
>>> a = [1,2,3]
>>> b = [4,5,6]
>>> a + b
[1, 2, 3, 4, 5, 6]
If you take a look at the official docs, you'll see right below append, extend. That's what your looking for.
There's also itertools.chain if you are more interested in efficient iteration than ending up with a fully populated data structure.
if the number of items was saved in a variable say n. you can use list comprehension and plus sign for list expansion.
lst = ['A', 'B']
n = 1
new_lst = lst + ['flag'+str(x) for x in range(n)]
print(my_lst)
>>> ['A','B','flag0','flag1']
One way you can work around this type of problem is -
Here we are inserting a list to the existing list by creating a variable new_values.
Note that we are inserting the values in the second index, i.e. a[2]
a = [1, 2, 7, 8]
new_values = [3, 4, 5, 6]
a.insert(2, new_values)
print(a)
But here insert() method will append the values as a list.
So here goes another way of doing the same thing, but this time, we'll actually insert the values in between the items.
a = [1, 2, 7, 8]
a[2:2] = [3,4,5,6]
print(a)

Modifying list contents in Python

I have a list like:
list = [[1,2,3],[4,5,6],[7,8,9]]
I want to append a number at the start of every value in the list programmatically, say the number is 9. I want the new list to be like:
list = [[9,1,2,3],[9,4,5,6],[9,7,8,9]]
How do I go about doing this in Python? I know it is a very trivial question but I couldn't find a way to get this done.
for sublist in thelist:
sublist.insert(0, 9)
don't use built-in names such as list for your own stuff, that's just a stupid accident in the making -- call YOUR stuff mylist or thelist or the like, not list.
Edit: as the OP aks how to insert > 1 item at the start of each sublist, let me point out that the most efficient way is by assignment of the multiple items to a slice of each sublist (most list mutators can be seen as readable alternatives to slice assignments;-), i.e.:
for sublist in thelist:
sublist[0:0] = 8, 9
sublist[0:0] is the empty slice at the start of sublist, and by assigning items to it you're inserting the items at that very spot.
>>> someList = [[1,2,3],[4,5,6],[7,8,9]]
>>> someList = [[9] + i for i in someList]
>>> someList
[[9, 1, 2, 3], [9, 4, 5, 6], [9, 7, 8, 9]]
(someList because list is already used by python)
Use the insert method, which modifies the list in place:
>>> numberlists = [[1,2,3],[4,5,6]]
>>> for numberlist in numberlists:
... numberlist.insert(0,9)
...
>>> numberlists
[[9, 1, 2, 3], [9, 4, 5, 6]]
or, more succintly
[numberlist.insert(0,9) for numberlist in numberlists]
or, differently, using list concatenation, which creates a new list
newnumberlists = [[9] + numberlist for numberlist in numberlists]
If you're going to be doing a lot of prepending,
perhaps consider using deques* instead of lists:
>>> mylist = [[1,2,3],[4,5,6],[7,8,9]]
>>> from collections import deque
>>> mydeque = deque()
>>> for li in mylist:
... mydeque.append(deque(li))
...
>>> mydeque
deque([deque([1, 2, 3]), deque([4, 5, 6]), deque([7, 8, 9])])
>>> for di in mydeque:
... di.appendleft(9)
...
>>> mydeque
deque([deque([9, 1, 2, 3]), deque([9, 4, 5, 6]), deque([9, 7, 8, 9])])
*Deques are a generalization of stacks and queues (the name is pronounced "deck" and is short for "double-ended queue"). Deques support thread-safe, memory-efficient appends and pops from either side of the deque with approximately the same O(1) performance in either direction.
And, as others have mercifully mentioned:
For the love of all things dull and ugly,
please do not name variables after your favorite data-structures.
#!/usr/bin/env python
def addNine(val):
val.insert(0,9)
return val
if __name__ == '__main__':
s = [[1,2,3],[4,5,6],[7,8,9]]
print map(addNine,s)
Output:
[[9, 1, 2, 3], [9, 4, 5, 6], [9, 7, 8, 9]]

Categories