Iterating over an infinite container with a stop point in python - python

Suppose I want to generate a list of the set of positive integers whose square is less than 100 in python.
My initial thinking was to do something like this
from itertools import count
numbers = [x for x in count() if x**2<100]
However this code won't complete, as python goes through infinitely many numbers.
There are two solutions to this as far as I can tell:
Put in a bound on the range of integers to go over, so use range(1000) instead of count() above.
Use a while loop, incrementing x and stopping the loop when x squared is greater than or equal to 100.
Neither of these solutions are elegant or (as far as I can tell) pythonic. Is there a good way to handle cases like this ,when iterating over an infinite container but you know that the loop stops at some point.
Thanks!

This would be a use case for itertools.takewhile:
from itertools import count, takewhile
numbers = list(takewhile(lambda x: x**2 < 100, count()))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
For a slice of known bounds from a (infinite) generator (not all of those have such simple finite equivalents as range), use itertools.islice:
numbers = list(islice(count(), 4, 10, 2))
# [4, 6, 8]
That being said, and with a loop-based approach being perfectly legit as well, there is a hacky way of achieving it in one line without any library tools, by causing an "artificial" StopIteration:
numbers = list(x if x**2<100 else next(iter([])) for x in count())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
don't do this in serious code though - not just because it will stop working in Python3.7 ;-)

I'd use takewhile:
nums = itertools.takewhile(lambda x: x**2 < 100, count())
Literally, take while the square is less than 100

If you were to use range, I would write it like this.
import math
[x for x in range(int(math.sqrt(100))+1) if (x**2) < 100]
You can change 100 to what you like or set it as a variable, but you know the list of numbers will end at the square root of your target number.

Related

Does Python calculate list comprehension condition in each step?

In a list comprehension with a condition that has a function call in it, does Python (specifically CPython 3.9.4) call the function each time, or does it calculate the value once and then uses it?
For example if you have:
list_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
list_2 = [x for x in list_1 if x > np.average(list_1)]
Will Python actually calculate the np.average(list_1) len(list_1) times? So would it be more optimized to write
list_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
np_avg = np.average(list_1)
list_2 = [x for x in list_1 if x > np_avg]
instead? Or does Python already "know" to just calculate the average beforehand?
Python has to call the function each time. It cannot optimize that part, because successive calls of the function might return different results (for example because of side effects). There is no easy way for Python’s compiler to be sure that this can’t happen.
Therefore, if you (the programmer) know that the result will always be the same – like in this case – it is probably advisable to calculate the result of the function in advance and use it inside the list comprehension.
Assuming standard CPython - Short answer: Yes. Your second snippet is more efficient.
A function call in the filter part of a list comprehension will be called for each element.
We can test this quite easily with a trivial example:
def f(value):
""" Allow even values only """
print('function called')
return value % 2 == 0
mylist = [x for x in range(5) if f(x)]
# 'function called' will be printed 5 times
The above is somewhat equivalent to doing:
mylist = []
for x in range(5):
if f(x):
mylist.append(x)
Since you're comparing against the same average each time, you can indeed just calculate it beforehand and use the same value as you did in your second code snippet.

compute suffix maximums using itertools.accumulate

What's the recommended way to compute the suffix maximums of a sequence of integers?
Following is the brute-force approach (O(n**2)time), based on the problem definition:
>>> A
[9, 9, 4, 3, 6]
>>> [max(A[i:]) for i in range(len(A))]
[9, 9, 6, 6, 6]
One O(n) approach using itertools.accumulate() is the following, which uses two list constructors:
>>> A
[9, 9, 4, 3, 6]
>>> list(reversed(list(itertools.accumulate(reversed(A), max))))
[9, 9, 6, 6, 6]
Is there a more pythonic way to do this?
Slice-reversal makes things more concise and less nested:
list(itertools.accumulate(A[::-1], max))[::-1]
It's still something you'd want to bundle up into a function, though:
from itertools import accumulate
def suffix_maximums(l):
return list(accumulate(l[::-1], max))[::-1]
If you're using NumPy, you'd want numpy.maximum.accumulate:
import numpy
def numpy_suffix_maximums(array):
return numpy.maximum.accumulate(array[::-1])[::-1]
Personally when I think "Pythonic" I think "simple and easy-to-read", so here's my Pythonic version:
def suffix_max(a_list):
last_max = a[-1]
maxes = []
for n in reversed(a):
last_max = max(n, last_max)
maxes.append(last_max)
return list(reversed(maxes))
For what it's worth, this looks to be about 50% slower than the itertools.accumulate approach, but we're talking 25ms vs 17ms for a list of 100,000 ints, so it may not much matter.
If speed is the utmost concern and the range of numbers you expect to see is significantly smaller than the length of list you're working with, it might be worth using RLE:
def suffix_max_rle(a_list):
last_max = a_list[-1]
count = 1
max_counts = []
for n in a_list[-2::-1]:
if n <= last_max:
count += 1
else:
max_counts.append([last_max, count])
last_max = n
count = 1
if n <= last_max:
max_counts.append([last_max, count])
return list(reversed(max_counts))
This is about 4 times faster than the above, and about 2.5 times faster than the itertools approach, for a list of 100,000 ints in the range 0-10000. Provided, again, that your range of numbers is significantly smaller than the length of your lists, it will take less memory, too.

counting up smaller numbers in python

i am trying to write a code as part of a simulator that will read a list of numbers and compare them to a secondary list of numbers and tell you how many numbers there are that are less then the first number. for example
X=[5,20,14,1,7]
Y=[2,12,9,5,4,6]
the code will take the first X value 5 and see how many of the Y values are less then 5. so the output Z would look something like
Z=[2,6,6,0,4]
i am not very familiar with these concepts at all, i am wondering how i would go about making a function for this type of work. how would i make a for loop that would go through and compare the numbers like that? also is it possible to combine and sort the lists from smallest to largest and then just search that list for the X value and print its position in the list?
Something like:
[len(list(filter(lambda k: k<m, Y))) for m in X]
You can do it using map and list comprehension in one line:
first = [5, 20, 14, 1, 7]
second = [2, 12, 9, 5, 4, 6]
z = map(lambda x: len([y for y in second if x > y]), first)
or without lambda (as #RobertB wrote):
z = [sum([x > y for y in second]) for x in first]
Result is:
[2, 6, 6, 0, 4]
There are many ways to go about the above question. I will explain the easiest method, although it is not the most efficient method
Concept: Nested For Loop
for x in range (0, a1_len-1):
for y in range (0, a2_len -1):
if a[y] < a[x]:
new_array.append(a[y])
print (new_array)
Hope this helps
Another answer using broadcasting with numpy:
import numpy as np
np.apply_along_axis(np.sum,0,np.array(Y).reshape((len(Y),1))<X)

Sort an array from right to left without using default functions

I was reading about arrays and I'm wondering how can I sort the elements from an array from right to left.
For example:
n = 10
numbers = []
for i in range(1, n+1):
numbers.append(i)
print(numbers)
How can I show the components from the last one to the very first one (10, 9, 8...) using basics tools like cycles and conditions?.
And besides this alternative:
for i in range(-1, (-len(numbers) - 1), -1):
print(numbers[i])
You aren't sorting an array. You are attempting to construct one with a particular ordering. You can do this using range directly. range can be invoked with three arguments start, stop and step. This allows you to construct a range 10, 9, ...:
Python 2:
numbers = range(10, 0, -1)
print numbers
Python 3:
numbers = list(range(10, 0, -1))
print(numbers)
output
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Use sorted with reverse=True
sorted(numbers, reverse=True)
or try list.sort method
number.sort(reverse=True)
But in your case you simple can use reverse indexing
number[: : -1]
list(reversed([1,2]))
>> [2,1]
There are many different algorithms to sort arrays without default functions. Here's a list of some of them: https://en.wikipedia.org/wiki/Sorting_algorithm. You can use a sorting algorithm to sort the array, then, reverse it using reversed(list).
If you are looking to have list in reverse you could do this:
In [17]: [4,6,7][::-1]
Out[17]: [7, 6, 4]
You can use the range function for that, like
for i in range(n,0,-1):
print(number[i])
If you want to reverse your list in descending order then you can use the sort method of lists.
number.sort(reverse=True)

Removing nested loops in numpy

I've been writing a program to brute force check a sequence of numbers to look for euler bricks, but the method that I came up with involves a triple loop. Since nested Python loops get notoriously slow, I was wondering if there was a better way using numpy to create the array of values that I need.
#x=max side length of brick. User Input.
for t in range(3,x):
a=[];b=[];c=[];
for u in range(2,t):
for v in range(1,u):
a.append(t)
b.append(u)
c.append(v)
a=np.array(a)
b=np.array(b)
c=np.array(c)
...
Is there a better way to generate the array af values, using numpy commands?
Thanks.
Example:
If x=10, when t=3 I want to get:
a=[3]
b=[2]
c=[1]
the first time through the loop. After that, when t=4:
a=[4, 4, 4]
b=[2, 3, 3]
c=[1, 1, 2]
The third time (t=5) I want:
a=[5, 5, 5, 5, 5, 5]
b=[2, 3, 3, 4, 4, 4]
c=[1, 1, 2, 1, 2, 3]
and so on, up to max side lengths around 5000 or so.
EDIT: Solution
a=array(3)
b=array(2)
c=array(1)
for i in range(4,x): #Removing the (3,2,1) check from code does not affect results.
foo=arange(1,i-1)
foo2=empty(len(foo))
foo2.fill(i-1)
c=hstack((c,foo))
b=hstack((b,foo2))
a=empty(len(b))
a.fill(i)
...
Works many times faster now. Thanks all.
Try to use .empty and .fill (http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.fill.html)
There are couple of things which could help, but probably only for large values of x. For starters use xrange instead of range, that will save creating a list you never need. You could also create empty numpy arrays of the correct length and fill them up with the values as you go, instead of appending to a list and then converting it into a numpy array.
I believe this code will work (no python access right this second):
for t in xrange(3, x):
size = (t - 2) * (t - 3)
a = np.zeros(size)
b = np.zeros(size)
c = np.zeros(size)
idx = 0
for u in xrange(2,t):
for v in xrange(1,u):
a[idx] = t
b[idx] = u
c[idx] = v
idx += 1

Categories

Resources