My function needs two params(a,b) and if...break
, which seems redundant. Can it be made prettier?
def slice_list(lst,num):
"""
Given a list or Numpy array,
get consecutive tuples based on specific window.
"""
rst = []
for i,_ in enumerate(lst):
a = i
b = i + num
if len(lst[a:b])<num:
break
rst.append(tuple(lst[a:b]))
return rst
Example:
>>> lst = np.array([10, 11, 12, 13, 26, 28])
>>> slice_list(lst,4)
[(10, 11, 12, 13), (11, 12, 13, 26), (12, 13, 26, 28)]
-
5\$\begingroup\$ This question is incomplete. To help reviewers give you better answers, please add sufficient context to your question. The more you tell us about what your code does and what the purpose of doing that is, the easier it will be for reviewers to help you. Questions should include a description of what the code does \$\endgroup\$301_Moved_Permanently– 301_Moved_Permanently2018年06月12日 12:46:49 +00:00Commented Jun 12, 2018 at 12:46
-
1\$\begingroup\$ @MathiasEttinger I don't know why my code should give more context. What did I miss? I think others answers get my point. And I open your link but get 'Site not found' \$\endgroup\$Jack– Jack2018年06月13日 00:55:48 +00:00Commented Jun 13, 2018 at 0:55
-
\$\begingroup\$ Sorry, the link should be codereview.meta.stackexchange.com/q/1226/84718 Basically you don't explain the requirements of your function and thus we are left to guess at the specifications from the code and the example. How can we know that the code is correctly implemented, then? \$\endgroup\$301_Moved_Permanently– 301_Moved_Permanently2018年06月13日 07:46:18 +00:00Commented Jun 13, 2018 at 7:46
3 Answers 3
I believe that what you are looking for is already available as an itertools
recipe; even though pairwise
only allow you to return couples and not tuple of arbitrary length.
You will need to modify it so that:
tee
will returnnum
iterators;- you advance each of these iterators by one more element than the previous one (see the
consume
recipe for that).
This can lead to the following code:
import itertools
def advance(iterator, step):
next(itertools.islice(iterator, step, step), None)
def tuplewize(iterable, size):
iterators = itertools.tee(iterable, size)
for position, iterator in enumerate(iterators):
advance(iterator, position)
return zip(*iterators)
Usage being:
>>> for t in tuplewize(lst, 4):
... print(t)
...
(10, 11, 12, 13)
(11, 12, 13, 26)
(12, 13, 26, 28)
However, you are using numpy
so we may come up with a better numpy approach:
numpy.roll
allows us to advance the nth element on top of the list;numpy.stack
allows us to concatenate the rolled arrays into a single 2D array;numpy.transpose
allows us to convert a "list of lists" into a "list of tuples".
Full code being:
import numpy as np
def tuplewize(array, size):
if size < 2:
return np.array([array])
stack = np.stack([np.roll(array, -i) for i in range(size)])
return np.transpose(stack)[:-size+1]
Usage being:
>>> tuplewise(lst, 4)
array([[10, 11, 12, 13],
[11, 12, 13, 26],
[12, 13, 26, 28]])
And as @Peilonrayz indicated in a comment, a third possibility is to use the more_itertools
package and its windowed
function which has extended capabilities; and as such more overhead, so depending on your needs it may or may not suit you.
-
3\$\begingroup\$ There is a package that can replace
tuplewize
,more_itertools.windowed
\$\endgroup\$2018年06月12日 14:39:57 +00:00Commented Jun 12, 2018 at 14:39 -
\$\begingroup\$ @Peilonrayz your answer is great. \$\endgroup\$Jack– Jack2018年06月13日 01:12:36 +00:00Commented Jun 13, 2018 at 1:12
If you are passing in a list of 6 items, and you want sublists of length 4, the last list you can create starts at index 6-4=2
. Instead of checking if the sublist is long enough, and breaking if it isn’t, generate only the valid list of starting indices:
for i in range( len(lst) - num + 1 ):
rst.append( tuple( lst[i:i+num] ) )
Depending on your definition of pretty, and personal aesthetics:
rst = [ tuple( lst[i:i+num] ) for i in range( len(lst) - num + 1 ) ]
Comments about your code in no particular order:
- You throw away a value from enumerate, I would instead use range.
- You do an if statement every time you loop and because of this you need the a and b variables.
- I think size is a better variable name then num.
Code after my comments:
def slice_list2(lst,size):
if size > len(lst):
return []
rst = []
for i in range(0,len(lst)-size+1):
rst.append(tuple(lst[i:i+size]))
return rst
But I think this is a optimal place to use list comprehensions. So I would implement it something like this:
def slice_list3(lst,size):
return [tuple(lst[x:x+size]) for x in range(0,len(lst)-size+1)]
-
\$\begingroup\$ Actually your code is now tested: Try It Online run of both of your code variants, both which match test-case from OP \$\endgroup\$Thomas Ward– Thomas Ward2018年06月12日 14:02:55 +00:00Commented Jun 12, 2018 at 14:02
-
\$\begingroup\$ The
if size > len(lst)
test seems to be unnecessary. \$\endgroup\$200_success– 200_success2018年06月14日 17:17:04 +00:00Commented Jun 14, 2018 at 17:17