I was reviewing some algorithms and they had a for loop that increases by a constant multiple. What would be the most Pythonic way of solving this?
This is not an issue of how to solve the problem, but more of a discussion on what the best solution would be?
This is the Java snip:
for (int i = 1; i <=n; i *= c) {
// some stuff
}
Here is an actual solution in python. I don't think it is the most Pythonic method:
i = 1
while i < limit:
# some stuff, remember to use i - 1 as array index
i *= constant
Pythonic way I could see (That does not exist):
for i in mrange(1, limit, c):
# some stuff
First post here. Hope I tagged and all correctly...
You still can do this :
def mrange(start, stop, step):
i = start
while i < stop:
yield i
i *= step
And then :
for i in mrange(1, 100, 4):
print(i)
Prints :
1
4
16
64
Python cannot provide default range functions to fit every needs, but it is pythonic to create your own generators.
If you don't like this solution, the while alternative looks ok too.
You can use itertools.accumulate; start with a range 1 to n, then apply a function which multiplies its first argument by your constant and ignores its second argument.
>>> from itertools import accumulate
>>> [x for x in accumulate(range(1,10), lambda x,_: 4*x)]
[1, 4, 16, 64, 256, 1024, 4096, 16384, 65536]
Having missed that you want to take the values less than n, start with the infinite sequence [c, c**2, c**3, c**4, ...] and use takewhile to "filter" it. (Also, I just realized you only need map, not accumulate, although accumulate may be more efficient. Note the difference in starting points when using map vs accumulate, too.):
>>> from itertools import count, takewhile
>>> n = 100
>>> [x for x in takewhile(lambda x: x < n, map(lambda x: 4**x, count(0)))]
[1, 4, 16, 64]
>>> [x for x in takewhile(lambda x: x < n, accumulate(count(1), lambda x,_: x*4))]
[1, 4, 16, 64]
Using math module:-
for i in range(math.ceil(math.log(limit, const))):
# code goes here
Ex:-
>>> for i in range(math.ceil(math.log(20, 2))):
... print("runs")
...
runs
runs
runs
runs
runs
which is similar to:-
i = 1
while i< 20:
print('runs')
i*=2
Finally; in easy seeming way:-
>>> import math
>>> mrange = lambda i, limit, const: range(i, math.ceil(math.log(limit, const)))
>>> for i in mrange(0, 20, 2):
print('whoa')
..
whoa
whoa
whoa
whoa
whoa
Related
I would like to know if there exists a base solution to do something like this:
for n in range(length=8, start_position= 3, direction= forward)
The problem I'm encountering is I would like the loop to continue past the final index, and pick up again at idx =0, then idx=1, etc. and stop at idx= 3, the start_position.
To give context, I seek all possible complete solutions to the n-queen problem.
Based on your latest edit, you need a "normal" range and the modulo operator:
for i in range(START, START + LEN):
do_something_with(i % LEN)
from itertools import chain
for n in chain(range(3,8), range(3)):
...
The chain() returns an iterator with 3, 4, ..., 7, 0, 1, 2
Another option for solving this is to use modular arithmetic. You could do something like this, for example:
for i in range(8)
idx = (i + 3) % 8
# use idx
This easily can be generalized to work with different lengths and offsets.
def loop_around_range(length, start_position, direction='forward'):
looped_range = [k % length for k in range(start_position, start_position+length)]
if direction == 'forward':
return looped_range
else:
return looped_range[::-1]
You could implement this for an arbitrary iterable by using itertools.cycle.
from itertools import cycle
def circular_iterator(iterable, skip=0, length=None, reverse=False):
"""Produces a full cycle of #iterable#, skipping the first #skip# elements
then tacking them on to the end.
if #iterable# does not implement #__len__#, you must provide #length#
"""
if reverse:
iterable = reversed(iterable)
cyc_iter = cycle(iterable)
for _ in range(skip):
next(cyc_iter, None)
if length:
total_length = length
else:
total_length = len(iterable)
for _ in range(total_length):
yield next(cyc_iter, None)
>>> lst = [x for x in range(1, 9)]
# [1, 2, 3, 4, 5, 6, 7, 8]
>>> list(circular_iterator(lst, skip=3))
[4, 5, 6, 7, 8, 1, 2, 3]
I have implemented a list of all prime numbers from a set amount.
What I'm trying to do is hard to explain so I'll just show it with some hard code:
euclst = []
euclst.append((primelst[0]) + 1)
euclst.append((primelst[0] * primelst[1]) + 1)
euclst.append((primelst[0] * primelst[1] * primelst[2]) + 1)
....
So essentially I'm trying to take a single element in order from my prev list and multiplying it exponentially I guess and appending it to my other list.
I realized that I could just do this, which is probably easier:
euclst = []
euclst.append(primelst[0])
euclst.append(primelst[0] * primelst[1])
euclst.append(primelst[0] * primelst[1] * primelst[2])
....
#then add one to each element in the list later
I need some ideas to do this in a loop of some sort.
You want a list of the cumulative product. Here's a simple recipe:
>>> primelist = [2, 3, 5, 7, 11, 13, 17, 19, 23]
>>> euclist = []
>>> current = 1
>>> for p in primelist:
... current *= p
... euclist.append(current)
...
>>> euclist
[2, 6, 30, 210, 2310, 30030, 510510, 9699690, 223092870]
>>>
Another way, using itertools:
>>> import itertools
>>> import operator
>>> list(itertools.accumulate(primelist, operator.mul))
[2, 6, 30, 210, 2310, 30030, 510510, 9699690, 223092870]
>>>
OR, perhaps this is what you mean:
>>> [x + 1 for x in itertools.accumulate(primelist, operator.mul)]
[3, 7, 31, 211, 2311, 30031, 510511, 9699691, 223092871]
With the equivalent for-loop:
>>> euclist = []
>>> current = 1
>>> for p in primelist:
... current = current*p
... euclist.append(current + 1)
...
>>> euclist
[3, 7, 31, 211, 2311, 30031, 510511, 9699691, 223092871]
>>>
You could do it like this:
euclst = [primelst[0]]
for p in primelst[1:]:
euclst.append(euclst[-1]*p)
initialize your list with first element
loop and append the current element with the previous appended element
(since current result depends on previous results, it's not easily doable in a list comprehension)
To solve the more complex one with a +1 on it:
euclst = [primelst[0]+1]
for p in primelst[1:]:
euclst.append((euclst[-1]-1)*p+1)
(the previous result is the product plus one, so to reuse it, just substract one)
EDIT: other answers make me realize that I'm overcomplicating things. A temp variable to store the cumulative product would probably be cleaner.
It might be clearest to use an intermediate variable to keep track of the product, and then add the 1 as you put it in the list.
euclst = []
running_prod = 1
for p in primelst[]:
running_prod *= p
euclst.append(running_prod + 1)
you can use something like this:
euclst.append(primelst[0])
euclst.append(euclst[-1]*primelst[i])
Sorry for what seems like a basic question, but I could not find it anywhere. In Python 2, I would like to apply a 1-variable function to its own output storing the list of all steps, i.e. if f(x) returns x*x then iterating from 2, i need to get
[2, 4, 16, 256, 65536, ...]
Ideally, I would need to pass in my function f, the first input 1, and the number of iterations I would like to keep.
I guess this is, in some sense, the opposite of reduce and somewhat similar to unfold from functional programming.
A naive way to do this is to write
out = [2]
for x in xrange(5):
out.append(f(out[-1]))
What is a good Pythonic way to do this?
Thank you very much.
What you need is a "Generator". For example,
def f(x, n):
for _ in range(n):
yield x
x = x * x
l = list(f(2, 5))
print(l) # [2, 4, 16, 256, 65536]
Or
def f(x):
while True:
yield x
x = x * x
for v in f(2):
if v > 100000:
break
print(v), # 2 4 16 256 65536
Ideally, I would need to pass in my function f, the first input 1, and
the number of iterations I would like to keep.
Here is an unfold function that accepts a function, a starting value, and an iteration count.
def unfold(function, start, iterations):
results = []
for _ in range(iterations):
results.append(start)
start = function(start)
return results
Which you can use as expected:
>>> print unfold(lambda x: x*x, 2, 5)
[2, 4, 16, 256, 65536]
I'm doing some analysis of images, and I have a generator that gives me all pixels in image:
def pixels_g(img):
w, h = img.shape
for y in range(0, h):
for x in range(0, w):
yield img[y][x]
It's output if converted to list would be something like
[0, 1, 2, 5, 240, 5, ... ]
Now I'd like to get "frequency table" for it:
{
0: 0,
1: 5,
2: 10,
3: 0,
4: 0,
#snip
255: 7
}
I've found some useful options in this question, but they all work with list, and I don't think that creating a list from my generator is a good idea - it can have millions of elements.
I'm therefore looking for a way to do this while preserving the benefits of generator. I'll process many images and don't want to hog resources too much.
Use Counter from "collections". It works with any iterable, not just lists.
from collections import Counter
pixels = pixels_g(img)
c = Counter(pixels)
print c[4]
If you need to reuse the contents of 'pixels' generator instance after running it through Counter, use 'tee' from itertools:
from collections import Counter
from itertools import tee
(pixels, hist) = tee(pixels_g(img))
c = Counter(pixels)
# can use hist for something else
I have a sumranges() function, which sums all the ranges of consecutive numbers found in a tuple of tuples. To illustrate:
def sumranges(nums):
return sum([sum([1 for j in range(len(nums[i])) if
nums[i][j] == 0 or
nums[i][j - 1] + 1 != nums[i][j]]) for
i in range(len(nums))])
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sumranges(nums)
7
As you can see, it returns the number of ranges of consecutive digits within the tuple, that is: len((1, 2, 3, 4), (1), (5, 6), (19, 20), (24), (29), (400)) = 7. The tuples are always ordered.
My problem is that my sumranges() is terrible. I hate looking at it. I'm currently just iterating through the tuple and each subtuple, assigning a 1 if the number is not (1 + previous number), and summing the total. I feel like I am missing a much easier way to accomplish my stated objective. Does anyone know a more pythonic way to do this?
Edit: I have benchmarked all the answers given thus far. Thanks to all of you for your answers.
The benchmarking code is as follows, using a sample size of 100K:
from time import time
from random import randrange
nums = [sorted(list(set(randrange(1, 10) for i in range(10)))) for
j in range(100000)]
for func in sumranges, alex, matt, redglyph, ephemient, ferdinand:
start = time()
result = func(nums)
end = time()
print ', '.join([func.__name__, str(result), str(end - start) + ' s'])
Results are as follows. Actual answer shown to verify that all functions return the correct answer:
sumranges, 250281, 0.54171204567 s
alex, 250281, 0.531121015549 s
matt, 250281, 0.843333005905 s
redglyph, 250281, 0.366822004318 s
ephemient, 250281, 0.805964946747 s
ferdinand, 250281, 0.405596971512 s
RedGlyph does edge out in terms of speed, but the simplest answer is probably Ferdinand's, and probably wins for most pythonic.
My 2 cents:
>>> sum(len(set(x - i for i, x in enumerate(t))) for t in nums)
7
It's basically the same idea as descriped in Alex' post, but using a set instead of itertools.groupby, resulting in a shorter expression. Since sets are implemented in C and len() of a set runs in constant time, this should also be pretty fast.
Consider:
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> flat = [[(x - i) for i, x in enumerate(tu)] for tu in nums]
>>> print flat
[[1, 1, 1, 1], [1, 4, 4], [19, 19, 22, 26, 396]]
>>> import itertools
>>> print sum(1 for tu in flat for _ in itertools.groupby(tu))
7
>>>
we "flatten" the "increasing ramps" of interest by subtracting the index from the value, turning them into consecutive "runs" of identical values; then we identify and could the "runs" with the precious itertools.groupby. This seems to be a pretty elegant (and speedy) solution to your problem.
Just to show something closer to your original code:
def sumranges(nums):
return sum( (1 for i in nums
for j, v in enumerate(i)
if j == 0 or v != i[j-1] + 1) )
The idea here was to:
avoid building intermediate lists but use a generator instead, it will save some resources
avoid using indices when you already have selected a subelement (i and v above).
The remaining sum() is still necessary with my example though.
Here's my attempt:
def ranges(ls):
for l in ls:
consec = False
for (a,b) in zip(l, l[1:]+(None,)):
if b == a+1:
consec = True
if b is not None and b != a+1:
consec = False
if consec:
yield 1
'''
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sum(ranges(nums))
7
'''
It looks at the numbers pairwise, checking if they are a consecutive pair (unless it's at the last element of the list). Each time there's a consecutive pair of numbers it yields 1.
This could probably be put together in a more compact form, but I think clarity would suffer:
def pairs(seq):
for i in range(1,len(seq)):
yield (seq[i-1], seq[i])
def isadjacent(pair):
return pair[0]+1 == pair[1]
def sumrange(seq):
return 1 + sum([1 for pair in pairs(seq) if not isadjacent(pair)])
def sumranges(nums):
return sum([sumrange(seq) for seq in nums])
nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
print sumranges(nums) # prints 7
You could probably do this better if you had an IntervalSet class because then you would scan through your ranges to build your IntervalSet, then just use the count of set members.
Some tasks don't always lend themselves to neat code, particularly if you need to write the code for performance.
There is a formula for this, the sum of the first n numbers, 1+ 2+ ... + n = n(n+1) / 2 . Then if you want to have the sum of i-j then it is (j(j+1)/2) - (i(i+1)/2) this I am sure simplifies but you can work that out. It might not be pythonic but it is what I would use.