0

I have a startnumber and an endnumber.
From these numbers I need to pick a sequence of numbers.
The sequences is not always the same.

Example:

startnumber = 1
endnumber = 32

I need to create a list of numbers with a certain sequence
p.e.
3 numbers yes, 2 numbers no, 3 numbers yes, 2 numbers no.. etc

Expected output:

[[1-3],[6-8],[11-13],[16-18],[21-23],[26-28],[31-32]]

(at the end there are only 2 numbers remaining (31 and 32))

Is there a simple way in python to select sequences of line from a range of numbers?

Reman
  • 7,931
  • 11
  • 55
  • 97

3 Answers3

4
numbers = range(1,33)
take = 3
skip = 2
seq = [list(numbers[idx:idx+take]) for idx in range(0, len(numbers),take+skip)]
Batman
  • 8,571
  • 7
  • 41
  • 80
  • that's clever. Just edit it a bit to make the `take 3, drop 2` pattern visible and customisable and hide the trick converting the 33 to 35 ☺ – Ma0 Jan 24 '17 at 17:00
  • Also this seems to be Python 2. In Python 3 (the one tagged) one would cast the `range` to `list`. – Ma0 Jan 24 '17 at 17:08
  • Thanks, this is a great solution. However it gives an output with ranges and tuples `[range(1, 4), range(6, 9), range(11, 14), range(16, 19), range(21, 24), range(26, 29), range(31, 33)]` and the 2nd value in the range is not the one I expected. – Reman Jan 24 '17 at 17:13
  • Sorry. I just saw the `python-3.x` tag that @Ev.Kounis pointed out above. I've edited the answer to cast the range back to a list. – Batman Jan 24 '17 at 17:33
  • @Batman, no problem, What would be the list comprehension if the skip and takes are inverted? First skip 2 numbers, than take 4 numbers, than skip 2 numbers than take 4 numbers ...etc – Reman Jan 24 '17 at 18:03
  • It would take 2, skip 3, etc. so `[[1,2],[6,7],[11,12], ...]`. – Batman Jan 24 '17 at 18:30
  • 1
    @Reman for skip first `seq =[list(numbers[idx+skip:idx+take+skip]) for idx in range(0, len(numbers)-skip,take+skip)]` – pii_ke Jan 24 '17 at 19:11
1

Extrapolating this out:

def get_data(data, filterfunc=None):
    if filterfunc is None:
        filterfunc = lambda: True  # take every line

    result = []
    sub_ = []
    for line in data:
        if filterfunc():
            sub_.append(line)
        else:
            if sub_:
                result.append(sub_)
                sub_ = []

    return result

# Example filterfunc
def example_filter(take=1, leave=1):
    """example_filter is a less-fancy version of itertools.cycle"""

    while True:
        for _ in range(take):
            yield True
        for _ in range(leave):
            yield False

# Your example
final = get_data(range(1, 33), example_filter(take=3, leave=2))

As alluded to in the docstring of example_filter, the filterfunc for get_data is really just expecting a True or False based on a call. You could change this easily to be of the signature:

def filterfunc(some_data: object) -> bool:

So that you can determine whether to take or leave based on the value (or even the index), but it currently takes no arguments and just functions as a less magic itertools.cycle (since it should return its value on call, not on iteration)

Adam Smith
  • 52,157
  • 12
  • 73
  • 112
1
from itertools import islice
def grouper(iterable, n, min_chunk=1):
    it = iter(iterable)
    while True:
       chunk = list(islice(it, n))
       if len(chunk) < min_chunk:
           return
       yield chunk

def pick_skip_seq(seq, pick, skip, skip_first=False):
    if skip_first:
        ret = [ x[skip:] for x in grouper(seq, pick+skip, skip+1) ]
    else:
        ret = [ x[:pick] for x in grouper(seq, pick+skip) ]
    return ret

pick_skip_seq(range(1,33), 3, 2) gives required list.

In pick_skip_seq(seq, pick, skip, skip_first=False), seq is sequence to pick/skip from, pick/skip are no. of elements to pick/skip, skip_first is to be set True if such behavior is desired.

grouper returns chunks of n elements, it ignores last group if it has less than min_chunk elements. It is derived from stuff given in https://stackoverflow.com/a/8991553/1921546.

Demo:

# pick 3 skip 2 
for i in range(30,35):
    print(pick_skip_seq(range(1,i), 3, 2))

# skip 3 pick 2 
for i in range(30,35):
    print(pick_skip_seq(range(1,i), 3, 2, True))

An alternative implementation of pick_skip_seq:

from itertools import chain,cycle,repeat,compress
def pick_skip_seq(seq, pick, skip, skip_first=False):
    if skip_first:
        c = cycle(chain(repeat(0, skip), repeat(1, pick)))
    else:
        c = cycle(chain(repeat(1, pick), repeat(0, skip)))
    return list(grouper(compress(seq, c), pick))

All things used are documented here: https://docs.python.org/3/library/itertools.html#itertools.compress

Community
  • 1
  • 1
pii_ke
  • 2,811
  • 2
  • 20
  • 30