I bet I already wrote that sometime earlier, but I found myself today writing the following function:
def blocks(seq, block_len):
"""blocks(range(5),2) -> [[0, 1], [2, 3], [4]]"""
seq_len = len(seq)
if seq_len%block_len == 0:
num_blocks = seq_len/block_len
else:
num_blocks = 1 + (seq_len/block_len)
result = [[] for i in xrange(num_blocks)]
for idx, obj in enumerate(seq):
result[idx/block_len].append(obj)
return result
I am not satisfied with this implementation.
The challenge is to write it in a more elegant and short manner. Functions from Python’s standard modules are considered valid solutions.
Ready? GO!
Im learning python and I can say I love it in the figurative sense…
Here is my try:
import math
def blocks(seq, subLen):
"""blocks(range(5), 2) -> [[0,1], [2,3], [4])
Basically get a slice of len = subLen and append until end skipping
current slice
"""
initialPosOfSlice = 0
result = []
for elementSlice in range(int(math.ceil(len(seq)/float(subLen)))):
result.append(seq[initialPosOfSlice:initialPosOfSlice+subLen])
initialPosOfSlice = initialPosOfSlice+subLen
return result
print blocks(range(5), 2)
I am aware that Python code doesn’t come out looking good on this blog, I will try to fix it in the following days. In the meantime, use the pre tag and preview your post, if you intend to post code.
Regarding the code: personally I don’t like the use of floating point division. At the least it feels like cheating :) At worst it can lead to strange results when depending on floats to yield specific ints. In any case, your solution is valid.
I’m still wondering if an elegant one-liner is possible…
hehe, I will take the idea of challenges, also I have take the idea of time roundup.
I guess what you mean by “floating point… ckeating”, the problem was how to determine the “end” ceil do the work and the conversion back forward intfloat do the job.
Here is a second try… ceiling is only “importan”, because you sometimes will need +1, but using a little more the slices…
def blocks3(seq, subLen):
“””blocks(range(5), 2) -> [[0,1], [2,3], [4])
The same operation over slices, but we use generators instead,
and an extra if clause for break before start adding []
“””
if subLen == 0:
subLen = 1
#sliceSubIndex = ((i,i+subLen)for i in range(0, len(seq), subLen))
#sliceResult = (seq[i:f] for i,f in sliceSubIndex)
sliceResult = (seq[i:i+subLen] for i in range(0, len(seq), subLen))
result = []
for ele in sliceResult:
result.append(ele)
return result
print “blocks3”, blocks3(range(7), 3)
print “blocks3”, blocks3(range(5), 0)
(by the way, I dont see how to edit the anterior post for put pre tags, also I not see a preview comment.)
Sorry for the double answer, but little time after, return this 1 liner ;) if you forget the check for the step of length zero
* turn ou that you dont whant to return the generator, but a generator can be “executed without the loop if you use list(generatorObject)
def blocks4(seq, subLen=1):
“””blocks(range(5), 2) -> [[0,1], [2,3], [4])
The same operation over slices, but we use generators instead,
and an extra if for check step not zero
“””
if subLen == 0:
subLen = 1
return list((seq[i:i+subLen] for i in range(0, len(seq), subLen)))
The following uses groupby which is great *if* you can remember how to use it:
>>> def blocks(seq, block_len):
… from itertools import groupby
… return [ list(z[1]) for z in groupby(range(len(seq)), lambda y: y / 2)]
…
>>> blocks(range(5),2)
[[0, 1], [2, 3], [4]]
>>>
– Paddy.
Whoops,
replace the 2 by block_len
P.S. the pre tag did not work in my comment.
– Paddy.
Thanks for the info on the Python problem. This really bugs me, in comments as well as in posts.
Regarding usage of groupby: Erez once wrote a function named classify_to_dict which was similar to groupby, but returned a dict. It was one of the more useful utility functions I encountered. Later on we found out about groupby, but still classify_to_dict was easier to use and understand.
Paddy: Your last solution however suffers from a flaw – try blocks(“abcdefg”,3) and see what I mean.
Tyoc: I really like your last solution, it has the right smell :). You can however remove the double parenthesis, yielding the following line:
return list(seq[i:i+subLen] for i in range(0, len(seq), subLen))
Or just list comprehension, list this [seq[i:i+subLen] for i in range(0, len(seq), subLen)].
I was not sure if you wanted members of the original sequence back or not. (You could help by making the function comment more complete).
Here is another go:
>>> def blocks(seq, block_len):
… from itertools import groupby
… return [ [j[1] for j in z[1]] for z in groupby(enumerate(seq), lambda y: y[0] / block_len)]
…
>>> blocks(“abcdefg”,3)
[[‘a’, ‘b’, ‘c’], [‘d’, ‘e’, ‘f’], [‘g’]]
>>> blocks(range(5),2)
[[0, 1], [2, 3], [4]]
>>>
– Paddy.
I also wrote classify_to_list, which could be more fitting for paddy’s first solution.
another onliner for the same problem but by using only __buitins__ (no import)
>>> def blocks(seq,block_len):
… return [seq[i:i+block_len] for i in range(0,len(seq),block_len)]
…
>>> blocks(range(5),2)
[[0, 1], [2, 3], [4]]
>>> blocks(range(6),2)
[[0, 1], [2, 3], [4, 5]]
However there are no checks. the inside range would fail for 0.
aa haan saw it bit late tyoc has the similar solution
import itertools
def split_subsequences(iterable, length=2, overlap=0):
it = iter(iterable)
results = list(itertools.islice(it, length))
while len(results) == length:
yield results
results = results[length – overlap:]
results.extend(itertools.islice(it, length – overlap))
if results:
yield results
if __name__ == ‘__main__’:
seq = ‘foobarbazer’
for length in (3, 4):
for overlap in (0, 1):
print ‘%d %d: %s’ % (length, overlap,
map(”.join, split_subsequences(seq, length, overlap)))