формат pythonic для индексов

Modula-2 является неосновным языком, который я нашел самыми интересными. Господствующая тенденция взглядов, но не вполне работает как то, к чему мы привыкли. Наследовался много Паскалю и все же достаточно отличается для обеспечения интересных возможностей изучения.

6
задан hoju 27 September 2009 в 01:06
поделиться

5 ответов

If you're into something Pythonic, I think 1:3,6,8:10,16 would be a better choice, as x:y is a standard notation for index range and the syntax allows you to use this notation on objects. Note that the call

z[1:3,6,8:10,16]

gets translated into

z.__getitem__((slice(1, 3, None), 6, slice(8, 10, None), 16))

Even though this is a TypeError if z is a built-in container, you're free to create the class that will return something reasonable, e.g. as NumPy's arrays.

You might also say that by convention 5: and :5 represent infinite index ranges (this is a bit stretched as Python has no built-in types with negative or infinitely large positive indexes).

And here's the parser (a beautiful one-liner that suffers from slice(16, None, None) glitch described below):

def parse(s):
    return [slice(*map(int, x.split(':'))) for x in s.split(',')]

There's one pitfall, however: 8:10 by definition includes only indices 8 and 9 -- without upper bound. If that's unacceptable for your purposes, you certainly need a different format and 1-3,6,8-10,16 looks good to me. The parser then would be

def myslice(start, stop=None, step=None):
    return slice(start, (stop if stop is not None else start) + 1, step)

def parse(s):
    return [myslice(*map(int, x.split('-'))) for x in s.split(',')]

Update: here's the full parser for a combined format:

from sys import maxsize as INF

def indices(s: 'string with indices list') -> 'indices generator':
    for x in s.split(','):
        splitter = ':' if (':' in x) or (x[0] == '-') else '-'
        ix = x.split(splitter)
        start = int(ix[0]) if ix[0] is not '' else -INF
        if len(ix) == 1:
            stop = start + 1
        else:
            stop = int(ix[1]) if ix[1] is not '' else INF
        step = int(ix[2]) if len(ix) > 2 else 1
        for y in range(start, stop + (splitter == '-'), step):
            yield y

This handles negative numbers as well, so

 print(list(indices('-5, 1:3, 6, 8:15:2, 20-25, 18')))

prints

[-5, 1, 2, 6, 7, 8, 10, 12, 14, 20, 21, 22, 23, 24, 25, 18, 19]

Yet another alternative is to use ... (which Python recognizes as the built-in constant Ellipsis so you can call z[...] if you want) but I think 1,...,3,6, 8,...,10,16 is less readable.

3
ответ дан 8 December 2019 в 17:24
поделиться

Для этого вам не нужна строка. Это настолько просто, насколько это возможно:

from types import SliceType

class sequence(object):
    def __getitem__(self, item):
        for a in item:
            if isinstance(a, SliceType):
                i = a.start
                step = a.step if a.step else 1
                while True:
                    if a.stop and i > a.stop:
                        break
                    yield i
                    i += step
            else:
                yield a

print list(sequence()[1:3,6,8:10,16])

Вывод:

[1, 2, 3, 6, 8, 9, 10, 16]

Я использую мощность среза Python для выражения диапазонов последовательностей . Я также использую генераторы для экономии памяти.

Обратите внимание, что я добавляю 1 к остановке среза, иначе диапазоны будут другими, потому что остановка в срезах не включена.

Он поддерживает шаги:

>>> list(sequence()[1:3,6,8:20:2])
[1, 2, 3, 6, 8, 10, 12, 14, 16, 18, 20]

И бесконечные последовательности:

sequence()[1:3,6,8:]
1, 2, 3, 6, 8, 9, 10, ...

Если вам нужно дать ему строку, вы можете комбинировать @ilya n. парсер с этим решением. Продлю @ilya n. синтаксический анализатор для поддержки индексов, а также диапазонов:

def parser(input):
    ranges = [a.split('-') for a in input.split(',')]
    return [slice(*map(int, a)) if len(a) > 1 else int(a[0]) for a in ranges]

Теперь вы можете использовать его так:

>>> print list(sequence()[parser('1-3,6,8-10,16')])
[1, 2, 3, 6, 8, 9, 10, 16]
7
ответ дан 8 December 2019 в 17:24
поделиться
import sys

class Sequencer(object):
    def __getitem__(self, items):
        if not isinstance(items, (tuple, list)):
            items = [items]
        for item in items:
            if isinstance(item, slice):
                for i in xrange(*item.indices(sys.maxint)):
                    yield i
            else:
                yield item


>>> s = Sequencer()
>>> print list(s[1:3,6,8:10,16])
[1, 2, 6, 8, 9, 16]

Обратите внимание, что я использую встроенную функцию xrange для генерации последовательности. Поначалу это кажется неудобным, потому что по умолчанию не включается верхнее количество последовательностей, однако это оказывается очень удобным. Вы можете делать что-то вроде:

>>> print list(s[1:10:3,5,5,16,13:5:-1])
[1, 4, 7, 5, 5, 16, 13, 12, 11, 10, 9, 8, 7, 6]

Это означает, что вы можете использовать step часть xrange .

1
ответ дан 8 December 2019 в 17:24
поделиться

Это, вероятно, примерно так же лениво, как это возможно, то есть это будет нормально даже для очень больших списков:

def makerange(s):
    for nums in s.split(","): # whole list comma-delimited
        range_ = nums.split("-") # number might have a dash - if not, no big deal
        start = int(range_[0])
        for i in xrange(start, start + 1 if len(range_) == 1 else int(range_[1]) + 1):
            yield i

s = "1-3,6,8-10,16"
print list(makerange(s))

output:

[1, 2, 3, 6, 8, 9, 10, 16]
2
ответ дан 8 December 2019 в 17:24
поделиться

Это выглядело как забавная головоломка, которую можно было бы добавить к кофе сегодня утром. Если вы остановитесь на заданном синтаксисе (который мне кажется нормальным, с некоторыми примечаниями в конце), вот конвертер pyparsing, который возьмет вашу входную строку и вернет список целых чисел:

from pyparsing import *

integer = Word(nums).setParseAction(lambda t : int(t[0]))
intrange = integer("start") + '-' + integer("end")
def validateRange(tokens):
    if tokens.from_ > tokens.to:
        raise Exception("invalid range, start must be <= end")
intrange.setParseAction(validateRange)
intrange.addParseAction(lambda t: list(range(t.start, t.end+1)))

indices = delimitedList(intrange | integer)

def mergeRanges(tokens):
    ret = set()
    for item in tokens:
        if isinstance(item,int):
            ret.add(item)
        else:
            ret += set(item)
    return sorted(ret)

indices.setParseAction(mergeRanges)

test = "1-3,6,8-10,16"
print indices.parseString(test)

Это также позаботится о любом перекрытии или повторяющиеся записи, такие как «3-8,4,6,3,4», и возвращает список только уникальных целых чисел.

Синтаксический анализатор заботится о проверке того, что диапазоны типа «10-3» не разрешены. Если вы действительно хотите разрешить это и получить что-то вроде «1,5-3,7», верните 1,5,4,3,7, тогда вы можете настроить действия синтаксического анализа intrange и mergeRanges, чтобы получить этот более простой результат (и вообще отказаться от действия синтаксического анализа validateRange).

Вы, скорее всего, получите пробелы в ваших выражениях, я предполагаю, что это несущественно. «1, 2, 3-6» будут обрабатываться так же, как «1,2,3-6». Pyparsing делает это по умолчанию, поэтому вы не видите специальной обработки пробелов в приведенном выше коде (но он есть ...)

Этот синтаксический анализатор не обрабатывает отрицательные индексы, но если это было необходимо, просто измените определение of integer to:

integer = Combine(Optional('-') + Word(nums)).setParseAction(lambda t : int(t[0]))

В вашем примере не было перечислено никаких отрицательных значений, поэтому я оставил его на данный момент.

Python использует ':' в качестве разделителя диапазона, поэтому исходная строка могла выглядеть как «1: 3, 6,8: 10,16 ", а Паскаль использовал" .. "для диапазонов массива, давая" 1..3,6,8..10,

1
ответ дан 8 December 2019 в 17:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: