Let's say I have a number, l, and I'd like to split it up into roughly equal n chunks. For example:
l = 11
n = 3
step = 1 + l // n
for start in range(0, l, step):
stop = min(l, start+step)
print(start, stop)
In this case, the first chunk (chunk 0) goes from 0 to 4 (5 elements), the next chunk (chunk 1) goes from 4 to 8 (5 elements), and the last chunk (chunk 2) is slightly smaller and goes from 8 to 11 (4 elements). Of course, the values of l and n may vary but both values will always be positive integers and n will always be smaller than l.
What I need to do is to generate a list that will iterate through each chunk in a round-robin fashion and append some chunk information to a list. The list should contain a tuple of the chunk number (i.e., 0, 1, or 2) and the next available start value in that chunk (until that chunk is exhausted as controlled by the stop value). So, the output list would be:
[(0,0), (1,4), (2,8), (0,1), (1,5), (2,9), (0,2), (1,6), (2,10), (0,3), (1,7)]
Note that the last chunk has one last element than the first two chunks. Whatever the solution is, it needs to work for any l and n (as long as both values are positive integers and n is always smaller than l). For simplicity, you can assume that l will be less than 100,000,000.
What is the best way to generate this list?
Use two loops for the two levels of your problem. The outer loop runs the starting point through all numbers in range(step). From there, use that value as the starting point for the inner loop you already wrote. Note that you have to adjust your output: you're printing (start, stop) values, when your requested output has (chunk#, start) values.
Can you take it from there?
One possible solution, using generators:
from itertools import islice, zip_longest, cycle
def chunk(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
def generate(l, n):
c, step = cycle(range(n)), l // n + (l % n != 0)
yield from ((next(c), v) for vals in zip_longest(*chunk(range(l), step)) for v in vals if v is not None)
l = 11
n = 3
out = [*generate(l, n)]
print(out)
Prints:
[(0, 0), (1, 4), (2, 8), (0, 1), (1, 5), (2, 9), (0, 2), (1, 6), (2, 10), (0, 3), (1, 7)]
For:
l = 9
n = 3
The output is:
[(0, 0), (1, 3), (2, 6), (0, 1), (1, 4), (2, 7), (0, 2), (1, 5), (2, 8)]
Related
I have a list d of length r such that d = (d_1, d_2,..., d_r).
I would like to generate all possible vectors of length r such that for any i (from 0 to r), v_i is between 0 and d_i.
For example,
if r =2 and d= (1,2), v_1 can be 0 or 1 and v_2 can be 0,1 or 2.
Hence there are 6 possible vectors:
[0,0] , [0,1], [0,2], [1,0] , [1,1], [1,2]
I have looked into Itertools and combinations and I have a feeling I will have to use recursion however I have not managed to solve it yet and was hoping for some help or advice into the right direction.
Edit:
I have written the following code for my problem and it works however I did it in a very inefficient way by disregarding the condition and generating all possible vectors then pruning the invalid ones. I took the largest d_i and generated all vectors of size r from (0,0,...0) all the way to (max_d_i,max_d_i,....max_d_i) and then eliminated those that were invalid.
Code:
import itertools
import copy
def main(d):
arr = []
correct_list =[]
curr = []
r= len(d)
greatest = max(d)
for i in range(0,greatest+1):
arr = arr + [i]
#all_poss_arr is a list that holds all possible vectors of length r from (0,0,...,0) to (max,max,...,max)
# for example if greatest was 3 and r= 4, all_poss_arr would have (0,0,0,0), then (0,0,0,1) and so on,
#all the way to (3,3,3,3)
all_poss_arr = list(itertools.product(arr,repeat = r))
#Now I am going to remove all the vectors that dont follow the v_i is between 0 and d_i
for i in range(0,len(all_poss_arr)):
curr = all_poss_arr[i]
cnt = 0
for j in range(0,len(curr)):
if curr[j] <= d[j]:
cnt = cnt +1
if cnt == r:
curr = list(curr)
currcopy = copy.copy(curr)
correct_list = correct_list + [currcopy]
cnt =0
return correct_list
If anyone knows a better way, let me know, it is much appreciated.
You basically want a Cartesian product. I'll demonstrate a basic, functional and iterative approach.
Given
import operator as op
import functools as ft
import itertools as it
def compose(f, g):
"""Return a function composed of two functions."""
def h(*args, **kwargs):
return f(g(*args, **kwargs))
return h
d = (1, 2)
Code
Option 1: Basic - Manual Unpacking
list(it.product(range(d[0] + 1), range(d[1] + 1)))
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
Option 2: Functional - Automated Mapping
def vector_combs(v):
"""Return a Cartesian product of unpacked elements from `v`."""
plus_one = ft.partial(op.add, 1)
range_plus_one = compose(range, plus_one)
res = list(it.product(*map(range_plus_one, v)))
return res
vector_combs(d)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
Option 3: Iterative - Range Replication (Recommended)
list(it.product(*[range(x + 1) for x in d]))
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
Details
Option 1
The basic idea is illustrated in Option 1:
Make a Cartesian product using a series of modified ranges.
Note, each range is manually incremented and passed in as an index from d. We automate these limitations in with the last options.
Option 2
We apply a functional approach to handle the various arguments and functions:
Partial the 1 argument to the add() function. This returns a function that will increment any number.
Let's pass this function into range through composition. This allows us to have a modified range function that auto increments the integer passed in.
Finally we map the latter function to each element in tuple d. Now d works with any length r.
Example (d = (1, 2, 1), r = 3):
vector_combs((1, 2, 1))
# [(0, 0, 0),
# (0, 0, 1),
# (0, 1, 0),
# (0, 1, 1),
# (0, 2, 0),
# (0, 2, 1),
# (1, 0, 0),
# (1, 0, 1),
# (1, 1, 0),
# (1, 1, 1),
# (1, 2, 0),
# (1, 2, 1)]
Option 3
Perhaps most elegantly, just use a list comprehension to create r ranges. ;)
I am very new to python and I wanted to know, how can I assign an integer incrementally to every 3 values in a series. Might be better to explain with an example:
The result should contain the following pattern :
(1,1),(2,1),(3,1),(4,2),(5,2),(6,2),(7,3).......
Alternatively, just using a simple for loop and two variables:
val1 = 1
val2 = 1
for a in range(10): # this number is arbitrarily large
for b in range(3): # this number is not arbitrary
print( (val1, val2) )
val1 += 1
val2 += 1
You can use the floor division operator with a list comprehension:
n = range(1, 10)
res = [(i, idx//3 + 1) for idx, i in enumerate(n)]
print(res)
[(1, 1), (2, 1), (3, 1),
(4, 2), (5, 2), (6, 2),
(7, 3), (8, 3), (9, 3)]
If you want this to be infinite, I'd suggest looking into the utilities in itertools
from itertools import count
def integers_rep(n):
for i in count(1):
for _ in range(n):
yield i
def gen_sequence():
return zip(count(1), integers_rep(3))
count(n) is an iterable which will yield the sequence n, n+1, n+2, ... ad infinitum starting at it's first argument (which defaults to 0 if nothing is passed).
gen_sequence which would create your sequence, uses zip which takes N-iterables and produces a sequence of N-tuples (two sequences => pairs, three => triples, etc.). By taking count and the other sequence, you will be able to then get their pairs.
By using these tools, you would be able to define everything at a high level (which can be useful for mathematical sequences).
I am trying to make a program that will count the numbers in the list number, and would search for a sum of 10 in sequence_len numbers.
In the minute it gets a 10, it should stop.
1. With this code I have an error. what should I do?
total=total+(list_n[i+n])
IndexError: list index out of range
2.I want the first for to be stop if Im finding a sum of then. Is it write to "break" at the end as I did or should I write i=len(list_n)?
number = 1234
sequence_len = 2
list_n=[]
total=0
b="false"
list_t=[]
for j in str(number):
list_n.append(int(j))
c=len(list_n)
for i in list_n:
n=0
while n<sequence_len:
total=total+(list_n[i+n])
n=n+1
if total==10:
b=true
seq=0
while seq>sequence_len:
list_t.append(list_t[i+seq])
seq=seq+1
break
else:
total=0
if b=="true":
break
if b=="false":
print "Didn’t find any sequence of size", sequence_len
else:
print "Found a sequence of size", sequence_len ,":", list_t
You have a couple of errors. First with the basic:
b=true
This needs to the True, otherwise, python will look for the true variable.
Secondly, i actually contains the value of the variable for that iteration (loop). For example:
>>> l = ['a', 'b', 'c']
>>> for i in l: print i
a
b
c
Because of this, you cannot use it as an index, as indexes have to be integers. So, what you need to do is use enumerate, this will generate a tuple of both the index and the value, so something like:
for i, var in enumerate(list_n):
n = 0
An example of enumerate in action:
>>> var = enumerate([1,6,5,32,1])
>>> for x in var: print x
(0, 1)
(1, 6)
(2, 5)
(3, 32)
(4, 1)
And this statement should has logical problems I believe:
total = total + (list_n[i + n - 1])
If you want to get a sum of 10 from a list of numbers, you can use this brute-force technique:
>>> list_of_n = [1,0,5,4,2,1,2,3,4,5,6,8,2,7]
>>> from itertools import combinations
>>> [var for var in combinations(list_of_n, 2) if sum(var) == 10]
[(5, 5), (4, 6), (2, 8), (2, 8), (3, 7), (4, 6), (8, 2)]
So, if you want a 10 from 3 numbers in the list, you would put combinations(list_of_n, 3) instead of combinations(list_of_n, 2).
When you say
for i in list_n:
i will not refer to the indices, but to the list elements themselves. If you want just the indices,
for i in range(len(list_n)):
len(list_n) will give you the size of the list and range(len(list_n)) will give you a range of numbers starting from 0 and ending with len(list_n) - 1
How to make certain frame range (ie. 1-100) break into 4 equal frame ranges (like 1-25, 26-50, 51-75, 75-100 or anything similiar). I need first and last digit from every chunked frame range.
def chunk_range(first, last, howmany):
size = ((last - first + 1) + (howmany - 1)) // howmany
while first <= last:
next = first + size
yield first, min(next - 1, last)
first = next
list(chunk_range(1, 100, 4))
returns
[(1, 25), (26, 50), (51, 75), (76, 100)]
Do note that this makes all the segments of equal length except the last one - for instance,
list(chunk_range(1, 7, 3))
gives you
[(1, 3), (4, 6), (7, 7)] # last chunk is only one item
You may instead want to distribute the error along the sequence, a la Bresenham's algorithm.
I have a list as follows:
l = [0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,2,2,2]
I want to determine the length of a sequence of equal items, i.e for the given list I want the output to be:
[(0, 6), (1, 6), (0, 4), (2, 3)]
(or a similar format).
I thought about using a defaultdict but it counts the occurrences of each item and accumulates it for the entire list, since I cannot have more than one key '0'.
Right now, my solution looks like this:
out = []
cnt = 0
last_x = l[0]
for x in l:
if x == last_x:
cnt += 1
else:
out.append((last_x, cnt))
cnt = 1
last_x = x
out.append((last_x, cnt))
print out
I am wondering if there is a more pythonic way of doing this.
You almost surely want to use itertools.groupby:
l = [0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,2,2,2]
answer = []
for key, iter in itertools.groupby(l):
answer.append((key, len(list(iter))))
# answer is [(0, 6), (1, 6), (0, 4), (2, 3)]
If you want to make it more memory efficient, yet add more complexity, you can add a length function:
def length(l):
if hasattr(l, '__len__'):
return len(l)
else:
i = 0
for _ in l:
i += 1
return i
l = [0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,2,2,2]
answer = []
for key, iter in itertools.groupby(l):
answer.append((key, length(iter)))
# answer is [(0, 6), (1, 6), (0, 4), (2, 3)]
Note though that I have not benchmarked the length() function, and it's quite possible it will slow you down.
Mike's answer is good, but the itertools._grouper returned by groupby will never have a __len__ method so there is no point testing for it
I use sum(1 for _ in i) to get the length of the itertools._grouper
>>> import itertools as it
>>> L = [0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,2,2,2]
>>> [(k, sum(1 for _ in i)) for k, i in it.groupby(L)]
[(0, 6), (1, 6), (0, 4), (2, 3)]