2
\$\begingroup\$

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)]
\$\endgroup\$
  • 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\$ – Mathias Ettinger Jun 12 '18 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 Jun 13 '18 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\$ – Mathias Ettinger Jun 13 '18 at 7:46
5
\$\begingroup\$

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 return num 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.

\$\endgroup\$
  • 3
    \$\begingroup\$ There is a package that can replace tuplewize, more_itertools.windowed \$\endgroup\$ – Peilonrayz Jun 12 '18 at 14:39
  • \$\begingroup\$ @Peilonrayz your answer is great. \$\endgroup\$ – Jack Jun 13 '18 at 1:12
0
\$\begingroup\$

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 ) ]
\$\endgroup\$
0
\$\begingroup\$

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)]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.