List comprehension python - python

What is the equivalent list comprehension in python of the following Common Lisp code:
(loop for x = input then (if (evenp x)
(/ x 2)
(+1 (* 3 x)))
collect x
until (= x 1))

A list comprehension is used to take an existing sequence and perform some function and/or filter to it, resulting in a new list. So, in this case a list comprehension is not appropriate since you don't have a starting sequence. An example with a while loop:
numbers = []
x=input()
while x != 1:
numbers.append(x)
if x % 2 == 0: x /= 2
else: x = 3 * x + 1

I believe you are writing the hailstone sequence, although I could be wrong since I am not fluent in Lisp.
As far as I know, you can't do this in only a list comprehension, since each element depends on the last.
How I would do it would be this
def hailstone(n):
yield n
while n!=1
if n%2 == 0: # even
n = n / 2
else: # odd
n = 3 * n + 1
yield n
list = [ x for x in hailstone(input) ]
Of course, input would hold whatever your input was.
My hailstone function could probably be more concise. My goal was clarity.

Python doesn't have this kind of control structure built in, but you can generalize this into a function like this:
def unfold(evolve, initial, until):
state = initial
yield state
while not until(state):
state = evolve(state)
yield state
After this your expression can be written as:
def is_even(n): return not n % 2
unfold(lambda x: x/2 if is_even(x) else 3*x + 1,
initial=input, until=lambda x: x == 1)
But the Pythonic way to do it is using a generator function:
def produce(x):
yield x
while x != 1:
x = x / 2 if is_even(x) else 3*x + 1
yield x

The hackery referred to by Laurence:
You can do it in one list comprehension, it just ends up being AWFUL python. Unreadable python. Terrible python. I only present the following as a curiosity, not as an actual answer. Don't do this in code you actually want to use, only if you fancy having a play with the inner workings on python.
So, 3 approaches:
Helping List 1
1: Using a helping list, answer ends up in the helping list. This appends values to the list being iterated over until you've reached the value you want to stop at.
A = [10]
print [None if A[-1] == 1
else A.append(A[-1]/2) if (A[-1]%2==0)
else A.append(3*A[-1]+1)
for i in A]
print A
result:
[None, None, None, None, None, None, None]
[10, 5, 16, 8, 4, 2, 1]
Helping List 2
2: Using a helping list, but with the result being the output of the list comprehension. This mostly relies on list.append(...) returning None, not None evaluating as True and True being considered 1 for the purposes of arithmetic. Sigh.
A=[10]
print [A[0]*(not A.append(A[0])) if len(A) == 1
else 1 if A[-1] == 2 else (A[-1]/2)*(not A.append(A[-1]/2)) if (A[-1]%2==0)
else (3*A[-1]+1)*(not A.append(3*A[-1]+1))
for i in A]
result:
[10, 5, 16, 8, 4, 2, 1]
Referencing the List Comprehension from within
3: Not using a helping list, but referring back to the list comprehension as it's being built. This is a bit fragile, and probably wont work in all environments. If it doesn't work, try running the code on its own:
from itertools import chain, takewhile
initialValue = 10
print [i if len(locals()['_[1]']) == 0
else (locals()['_[1]'][-1]/2) if (locals()['_[1]'][-1]%2==0)
else (3*locals()['_[1]'][-1]+1)
for i in takewhile(lambda x:x>1, chain([initialValue],locals()['_[1]']))]
result:
[10, 5, 16, 8, 4, 2, 1]
So, now forget that you read this. This is dark, dark and dingy python. Evil python. And we all know python isn't evil. Python is lovely and nice. So you can't have read this, because this sort of thing can't exist. Good good.

As Kiv said, a list comprehension requires a known sequence to iterate over.
Having said that, if you had a sequence and were fixated on using a list comprehension, your solution would probably include something like this:
[not (x % 2) and (x / 2) or (3 * x + 1) for x in sequence]
Mike Cooper's answer is a better solution because it both retains the x != 1 termination, and this line doesn't read cleanly.

1
I have discovered a truly marvelous proof of this, which this margin is too narrow to contain.
In all seriousness though, I don't believe you can do this with Python list comprehensions. They have basically the same power as map and filter, so you can't break out or look at previous values without resorting to hackery.

Related

Infinite loop struggle for a beginner

I need to change the condition so that the loop is finite and list_ contains following values:
1 2 4 8 16 32 64
This is what I have in python, and I am struggling to understand what exactly needs to be changed and why.
list_ = []
i = 0
while i >= 0: # change the condition
list_.append(2 ** i)
i += 1
print(list_)
I suggest to use a for-loop instead of a while loop.
list_ = []
for i in range(7):
list_.append(2 ** i)
print(list_)
If you want to be very pythonic, then you can use a list comprehension:
list_ = [2 ** i for i in range(7)]
With a while a breaking condition is required (this implementation is to highlight the break)
out = []
i = 0
while True:
if i > 6:
break
out.append(2**i)
i += 1
print(out)
or with a list comprehension
print([2**i for i in range(6+1)])
Looks like you'll only need to switch the while condition in the loop. Rather than have a lower bound, you'll need to update with an upper bound - basically a "loop until" condition.
list_ = []
i = 0
while i <= 6: # change the condition
list_.append(2 ** i)
i += 1
print(list_). # [1, 2, 4, 8, 16, 32, 64]
But you can simplify this even further, with the help of a list comprehension. This is just a fancy (and bit more efficient) way of writing a for loop in python. Its more efficient because it doesn't need to call list.append within each loop iteration.
>>> [2 ** i for i in range(7)]
[1, 2, 4, 8, 16, 32, 64]
You could use while i <= 6: so that you while loop counts up to i=6.
However, I would recommend to use a for-loop with range(6) as a condition. So that your loop looks like the following:
for i in range(7):
…
This is just an alternative way, where i also counts up to the value of 6. using a for-loop would be good practice, as it saves you to declare i previously.

How do I perform math on every other number in a list?

E.g.: How do I change
a = [1,2,3,4]
to this:
a = [2,2,6,4]
so every other element is doubled?
If you want to do it in place, you can use slice assignment:
>>> a[::2] = [x*2 for x in a[::2]]
>>> a
[2, 2, 6, 4]
You can loop through every other index:
for index in range(0, len(your_list), 2):
your_list[index] *= 2
You can also do it using slice assignment, as #mgilson notes:
your_list[::2] = [x*2 for x in your_list[::2]]
While this is certainly more concise, it may also be more confusing for the average person reading through the code - assigning to a slice with a non-default skip factor isn't very intuitive.
There is another way to take two steps at a time a little more intuitive, like this
for i in range(len(yourList)/2):
yourList[2*i] = 2*yourList[2*i]
Though I do like the neat tricks used in the other answers, perhaps a more verbose and less-language specific explanation of what's going on is as follows:
for i in range(0, len(a)): # Iterate through the list
if i%2 == 0: # If the remainder of i ÷ 2 is equal to 0...
a[i] = a[i] * 2 # Change the current element to twice what it was

Looping through a list not in order in Python

I am very new to programming, so please bear with me...I have been learning Python and I just did an assessment that involved looping through a list using your current value as the next index value to go to while looping. This is roughly what the question was:
You have a zero-indexed array length N of positive and negative integers. Write a function that loops through the list, creates a new list, and returns the length of the new list. While looping through the list, you use your current value as the next index value to go to. It stops looping when A[i] = -1
For example:
A[0] = 1
A[1] = 4
A[2] = -1
A[3] = 3
A[4] = 2
This would create:
newlist = [1, 4, 2, -1]
len(newlist) = 4
It was timed and I was not able to finish, but this is what I came up with. Any criticism is appreciated. Like I said I am new and trying to learn. In the meantime, I will keep looking. Thanks in advance!
def sol(A):
i = 0
newlist = []
for A[i] in range(len(A)):
e = A[i]
newlist.append(e)
i == e
if A[i] == -1:
return len(newlist)
This might be the easiest way to do it if your looking for the least lines of code to write.
A = [1,4,-1,3,2]
B = []
n = 0
while A[n] != -1:
B.append(A[n])
n = A[n]
B.append(-1)
print(len(B))
First of all, note that for A[i] in range(len(A)) is a pattern you certainly want to avoid, as it is an obscure construct that will modify the list A by storing increasing integers into A[i]. To loop over elements of A, use for val in A. To loop over indices into A, use for ind in xrange(len(A)).
The for loop, normally the preferred Python looping construct, is not the right tool for this problem because the problem requires iterating over the sequence in an unpredictable order mandated by the contents of the sequence. For this, you need to use the more general while loop and manage the list index yourself. Here is an example:
def extract(l):
newlist = []
ind = 0
while l[ind] != -1:
newlist.append(l[ind])
ind = l[ind]
newlist.append(-1) # the problem requires the trailing -1
print newlist # for debugging
return len(newlist)
>>> extract([1, 4, -1, 3, 2])
[1, 4, 2, -1]
4
Note that collecting the values into the new list doesn't really make sense in any kind of real-world scenario because the list is not visible outside the function in any way. A more sensible implementation would simply increment a counter in each loop pass and return the value of the counter. But since the problem explicitly requests maintaining the list, code like the above will have to do.
It's simpler to just use a while loop:
data = [1,4,-1,3,2]
ls = []
i = 0
steps = 0
while data[i] != -1:
ls.append(data[i])
i = data[i]
steps += 1
assert steps < len(data), "Infinite loop detected"
ls.append(-1)
print ls, len(ls)

Referencing a list inside it's inline for loop initialization?

I would like to be able to write something like this:
x = [0]
x = [x[i-1] for i in range(1,10)]
I know this example doesn't make any sense. But I would like to know if it's possible to use previously computed values, while initializing a list in this way. (Perhaps some kind of lambda expression)
Here is the actual code I need:
x = [(b[i] - sum([(a[i][j] * x[j]) for j in range(i)])) / a[i][i] for i in range(n)]
This gives of course the following error:
UnboundLocalError: local variable 'x' referenced before assignment
I know there is this way around it:
x = []
for i in range(n):
x.append((b[i] - sum([(a[i][j] * x[j]) for j in range(i)])) / a[i][i])
But I would really like to know if the first one is possible somehow.
Even if it was possible I don’t think you should even try to do it. The “verbose” three-lines-solution is already quite complicated due to the nested list comprehension which is not even the outer part of that line. And you have another sum inside.
I’d actually even split it even more to have the list comprehension separately like this:
x = []
for i in range(n):
k = sum(a[i][j] * x[j] for j in range(i))
x.append((b[i] - k) / a[i][i])
I believe it might be possible to simplify this a bit more, but it’s hard to tell just by looking at it like that without any context on what it actually does, and without example data for a, b and n.
Although I perfectly agree that what you want is not a good practice. There is a way of doing it (sort of). For example, let's see this:
x = [1]
z = [x.append(x[i]*2) for i in range(10)]
Now naturally we have:
>>> print z
[None, None, None, None, None, None, None, None, None, None]
but, x hold the value we want:
>>> print x
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
So in a similar freakish way you can do:
x = [some_init_value]
[x.append((b[i] - sum([(a[i][j] * x[j]) for j in range(i)])) / a[i][i]) for i in range(n)]
Note that x is changed inplace and we do not assign the list comprehension to x as the list comprehension will return a bunch of Nones.
I want to stress that this is bad. Good practices state that list comprehensions shouldn't have side effects (as our has).
EDIT: Apparently in CPython <= 2.6 you have this "sick hack" where the list comprehension had a hidden value. eryksun's awesome hack for the Fibonacci sequence:
fib = [[0,1][j] if j<2 else locals()['_[1]'][j-1] + locals()['_[1]'][j-2] for j in range(10)]
Naturally this fails on CPython 2.7 and PyPy.

List comprehension for running total

I want to get a running total from a list of numbers.
For demo purposes, I start with a sequential list of numbers using range
a = range(20)
runningTotal = []
for n in range(len(a)):
new = runningTotal[n-1] + a[n] if n > 0 else a[n]
runningTotal.append(new)
# This one is a syntax error
# runningTotal = [a[n] for n in range(len(a)) if n == 0 else runningTotal[n-1] + a[n]]
for i in zip(a, runningTotal):
print "{0:>3}{1:>5}".format(*i)
yields
0 0
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55
11 66
12 78
13 91
14 105
15 120
16 136
17 153
18 171
19 190
As you can see, I initialize an empty list [], then append() in each loop iteration. Is there a more elegant way to this, like a list comprehension?
A list comprehension has no good (clean, portable) way to refer to the very list it's building. One good and elegant approach might be to do the job in a generator:
def running_sum(a):
tot = 0
for item in a:
tot += item
yield tot
to get this as a list instead, of course, use list(running_sum(a)).
If you can use numpy, it has a built-in function named cumsum that does this.
import numpy as np
tot = np.cumsum(a) # returns a np.ndarray
tot = list(tot) # if you prefer a list
I'm not sure about 'elegant', but I think the following is much simpler and more intuitive (at the cost of an extra variable):
a = range(20)
runningTotal = []
total = 0
for n in a:
total += n
runningTotal.append(total)
The functional way to do the same thing is:
a = range(20)
runningTotal = reduce(lambda x, y: x+[x[-1]+y], a, [0])[1:]
...but that's much less readable/maintainable, etc.
#Omnifarous suggests this should be improved to:
a = range(20)
runningTotal = reduce(lambda l, v: (l.append(l[-1] + v) or l), a, [0])
...but I still find that less immediately comprehensible than my initial suggestion.
Remember the words of Kernighan: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."
This can be implemented in 2 lines in Python.
Using a default parameter eliminates the need to maintain an aux variable outside, and then we just do a map to the list.
def accumulate(x, l=[0]): l[0] += x; return l[0];
map(accumulate, range(20))
Use itertools.accumulate(). Here is an example:
from itertools import accumulate
a = range(20)
runningTotals = list(accumulate(a))
for i in zip(a, runningTotals):
print "{0:>3}{1:>5}".format(*i)
This only works on Python 3. On Python 2 you can use the backport in the more-itertools package.
When we take the sum of a list, we designate an accumulator (memo) and then walk through the list, applying the binary function "x+y" to each element and the accumulator. Procedurally, this looks like:
def mySum(list):
memo = 0
for e in list:
memo = memo + e
return memo
This is a common pattern, and useful for things other than taking sums — we can generalize it to any binary function, which we'll supply as a parameter, and also let the caller specify an initial value. This gives us a function known as reduce, foldl, or inject[1]:
def myReduce(function, list, initial):
memo = initial
for e in list:
memo = function(memo, e)
return memo
def mySum(list):
return myReduce(lambda memo, e: memo + e, list, 0)
In Python 2, reduce was a built-in function, but in Python 3 it's been moved to the functools module:
from functools import reduce
We can do all kinds of cool stuff with reduce depending on the function we supply as its the first argument. If we replace "sum" with "list concatenation", and "zero" with "empty list", we get the (shallow) copy function:
def myCopy(list):
return reduce(lambda memo, e: memo + [e], list, [])
myCopy(range(10))
> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
If we add a transform function as another parameter to copy, and apply it before concatenating, we get map:
def myMap(transform, list):
return reduce(lambda memo, e: memo + [transform(e)], list, [])
myMap(lambda x: x*2, range(10))
> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
If we add a predicate function that takes e as a parameter and returns a boolean, and use it to decide whether or not to concatenate, we get filter:
def myFilter(predicate, list):
return reduce(lambda memo, e: memo + [e] if predicate(e) else memo, list, [])
myFilter(lambda x: x%2==0, range(10))
> [0, 2, 4, 6, 8]
map and filter are sort of unfancy ways of writing list comprehensions — we could also have said [x*2 for x in range(10)] or [x for x in range(10) if x%2==0]. There's no corresponding list comprehension syntax for reduce, because reduce isn't required to return a list at all (as we saw with sum, earlier, which Python also happens to offer as a built-in function).
It turns out that for computing a running sum, the list-building abilities of reduce are exactly what we want, and probably the most elegant way to solve this problem, despite its reputation (along with lambda) as something of an un-pythonic shibboleth. The version of reduce that leaves behind copies of its old values as it runs is called reductions or scanl[1], and it looks like this:
def reductions(function, list, initial):
return reduce(lambda memo, e: memo + [function(memo[-1], e)], list, [initial])
So equipped, we can now define:
def running_sum(list):
first, rest = list[0], list[1:]
return reductions(lambda memo, e: memo + e, rest, first)
running_sum(range(10))
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
While conceptually elegant, this precise approach fares poorly in practice with Python. Because Python's list.append() mutates a list in place but doesn't return it, we can't use it effectively in a lambda, and have to use the + operator instead. This constructs a whole new list, which takes time proportional to the length of the accumulated list so far (that is, an O(n) operation). Since we're already inside the O(n) for loop of reduce when we do this, the overall time complexity compounds to O(n2).
In a language like Ruby[2], where array.push e returns the mutated array, the equivalent runs in O(n) time:
class Array
def reductions(initial, &proc)
self.reduce [initial] do |memo, e|
memo.push proc.call(memo.last, e)
end
end
end
def running_sum(enumerable)
first, rest = enumerable.first, enumerable.drop(1)
rest.reductions(first, &:+)
end
running_sum (0...10)
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
same in JavaScript[2], whose array.push(e) returns e (not array), but whose anonymous functions allow us to include multiple statements, which we can use to separately specify a return value:
function reductions(array, callback, initial) {
return array.reduce(function(memo, e) {
memo.push(callback(memo[memo.length - 1], e));
return memo;
}, [initial]);
}
function runningSum(array) {
var first = array[0], rest = array.slice(1);
return reductions(rest, function(memo, e) {
return x + y;
}, first);
}
function range(start, end) {
return(Array.apply(null, Array(end-start)).map(function(e, i) {
return start + i;
}
}
runningSum(range(0, 10));
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
So, how can we solve this while retaining the conceptual simplicity of a reductions function that we just pass lambda x, y: x + y to in order to create the running sum function? Let's rewrite reductions procedurally. We can fix the accidentally quadratic problem, and while we're at it, pre-allocate the result list to avoid heap thrashing[3]:
def reductions(function, list, initial):
result = [None] * len(list)
result[0] = initial
for i in range(len(list)):
result[i] = function(result[i-1], list[i])
return result
def running_sum(list):
first, rest = list[0], list[1:]
return reductions(lambda memo, e: memo + e, rest, first)
running_sum(range(0,10))
> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
This is the sweet spot for me: O(n) performance, and the optimized procedural code is tucked away under a meaningful name where it can be re-used the next time you need to write a function that accumulates intermediate values into a list.
The names reduce/reductions come from the LISP tradition, foldl/scanl from the ML tradition, and inject from the Smalltalk tradition.
Python's List and Ruby's Array are both implementations of an automatically resizing data structure known as a "dynamic array" (or std::vector in C++). JavaScript's Array is a little more baroque, but behaves identically provided you don't assign to out of bounds indices or mutate Array.length.
The dynamic array that forms the backing store of the list in the Python runtime will resize itself every time the list's length crosses a power of two. Resizing a list means allocating a new list on the heap of twice the size of the old one, copying the contents of the old list into the new one, and returning the old list's memory to the system. This is an O(n) operation, but because it happens less and less frequently as the list grows larger and larger, the time complexity of appending to a list works out to O(1) in the average case. However, the "hole" left by the old list can sometimes be difficult to recycle, depending on its position in the heap. Even with garbage collection and a robust memory allocator, pre-allocating an array of known size can save the underlying systems some work. In an embedded environment without the benefit of an OS, this kind of micro-management becomes very important.
Starting Python 3.8, and the introduction of assignment expressions (PEP 572) (:= operator), we can use and increment a variable within a list comprehension:
# items = range(7)
total = 0
[(x, total := total + x) for x in items]
# [(0, 0), (1, 1), (2, 3), (3, 6), (4, 10), (5, 15), (6, 21)]
This:
Initializes a variable total to 0 which symbolizes the running sum
For each item, this both:
increments total by the current looped item (total := total + x) via an assignment expression
and at the same time returns the new value of total as part of the produced mapped tuple
I wanted to do the same thing to generate cumulative frequencies that I could use bisect_left over - this is the way I've generated the list;
[ sum( a[:x] ) for x in range( 1, len(a)+1 ) ]
Here's a linear time solution one liner:
list(reduce(lambda (c,s), a: (chain(c,[s+a]), s+a), l,(iter([]),0))[0])
Example:
l = range(10)
list(reduce(lambda (c,s), a: (chain(c,[s+a]), s+a), l,(iter([]),0))[0])
>>> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
In short, the reduce goes over the list accumulating sum and constructing an list. The final x[0] returns the list, x[1] would be the running total value.
Another one-liner, in linear time and space.
def runningSum(a):
return reduce(lambda l, x: l.append(l[-1]+x) or l if l else [x], a, None)
I'm stressing linear space here, because most of the one-liners I saw in the other proposed answers --- those based on the pattern list + [sum] or using chain iterators --- generate O(n) lists or generators and stress the garbage collector so much that they perform very poorly, in comparison to this.
I would use a coroutine for this:
def runningTotal():
accum = 0
yield None
while True:
accum += yield accum
tot = runningTotal()
next(tot)
running_total = [tot.send(i) for i in xrange(N)]
You are looking for two things: fold (reduce) and a funny function that keeps a list of the results of another function, which I have called running. I made versions both with and without an initial parameter; either way these need to go to reduce with an initial [].
def last_or_default(list, default):
if len(list) > 0:
return list[-1]
return default
def initial_or_apply(list, f, y):
if list == []:
return [y]
return list + [f(list[-1], y)]
def running_initial(f, initial):
return (lambda x, y: x + [f(last_or_default(x,initial), y)])
def running(f):
return (lambda x, y: initial_or_apply(x, f, y))
totaler = lambda x, y: x + y
running_totaler = running(totaler)
running_running_totaler = running_initial(running_totaler, [])
data = range(0,20)
running_total = reduce(running_totaler, data, [])
running_running_total = reduce(running_running_totaler, data, [])
for i in zip(data, running_total, running_running_total):
print "{0:>3}{1:>4}{2:>83}".format(*i)
These will take a long time on really large lists due to the + operator. In a functional language, if done correctly, this list construction would be O(n).
Here are the first few lines of output:
0 0 [0]
1 1 [0, 1]
2 3 [0, 1, 3]
3 6 [0, 1, 3, 6]
4 10 [0, 1, 3, 6, 10]
5 15 [0, 1, 3, 6, 10, 15]
6 21 [0, 1, 3, 6, 10, 15, 21]
This is inefficient as it does it every time from beginning but possible it is:
a = range(20)
runtot=[sum(a[:i+1]) for i,item in enumerate(a)]
for line in zip(a,runtot):
print line
with Python 3.8 and above you can now use walrus operator
xs = range(20)
total = 0
run = [(total := total + d) for d in xs]

Categories

Resources