web-dev-qa-db-ja.com

整数のリストをpythonの範囲に変換する

pythonに存在するもので、増加する整数のリストを範囲リストに変換できるものはありますか?

例えば。セット{0、1、2、3、4、7、8、9、11}を指定すると、{{0,4}、{7,9}、{11,11}}を取得します。

これを行うプログラムを作成できますが、Pythonに組み込み関数があるかどうかを知りたいです

36
Akhil

itertools.groupby() を使用すると、簡潔ですがトリッキーな実装が生成されます。

import itertools

def ranges(i):
    for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
        b = list(b)
        yield b[0][1], b[-1][1]

print(list(ranges([0, 1, 2, 3, 4, 7, 8, 9, 11])))

出力:

[(0, 4), (7, 9), (11, 11)]
41
user97370

list comprehensiongenerator expression と組み合わせて、 enumerate()itertools.groupby() を組み合わせて使用​​できます。 =:

_>>> import itertools
>>> l = [0, 1, 2, 3, 4, 7, 8, 9, 11]
>>> [[t[0][1], t[-1][1]] for t in
... (Tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x))]
[[0, 4], [7, 9], [11, 11]]
_

まず、enumerate()はリスト項目とそれぞれのインデックスからタプルを作成します:

_>>> [t for t in enumerate(l)]
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 7), (6, 8), (7, 9), (8, 11)]
_

次に、groupby()は、それらのタプルを、それらのインデックスとそれらの値(連続する値では等しい)の差を使用してグループ化します。

_>>> [Tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x)]
[((0, 0), (1, 1), (2, 2), (3, 3), (4, 4)), ((5, 7), (6, 8), (7, 9)), ((8, 11),)]
_

そこからは、各グループの最初と最後のタプルの値からリストを作成するだけで済みます(グループに項目が1つだけ含まれている場合も同じです)。

[(t[0][1], t[-1][1]) ...]を使用して、ネストされたリストの代わりに範囲タプルのリストを作成することも、_((t[0][1], t[-1][1]) ...)_を使用して式全体を反復可能なgeneratorにして、範囲を遅延して作成することもできますその場でタプル。

12

これは非常にエレガントな @ juanchopanza answer に対する改善です。これはnon-uniqueおよびnon-sorted入力をカバーし、python3互換性もあります:

import itertools

def to_ranges(iterable):
    iterable = sorted(set(iterable))
    for key, group in itertools.groupby(enumerate(iterable),
                                        lambda t: t[1] - t[0]):
        group = list(group)
        yield group[0][1], group[-1][1]

例:

>>> x
[44, 45, 2, 56, 23, 11, 3, 4, 7, 9, 1, 2, 2, 11, 12, 13, 45]

>>> print( list(to_ranges(x))) 
[(1, 4), (7, 7), (9, 9), (11, 13), (23, 23), (44, 45), (56, 56)]
6
luca

このジェネレータ:

def ranges(p):
    q = sorted(p)
    i = 0
    for j in xrange(1,len(q)):
        if q[j] > 1+q[j-1]:
            yield (q[i],q[j-1])
            i = j
    yield (q[i], q[-1])

sample = [0, 1, 2, 3, 4, 7, 8, 9, 11]
print list(ranges(sample))
print list(ranges(reversed(sample)))
print list(ranges([1]))
print list(ranges([2,3,4]))
print list(ranges([0,2,3,4]))
print list(ranges(5*[1]))

これらの結果を生成します:

[(0, 4), (7, 9), (11, 11)]
[(0, 4), (7, 9), (11, 11)]
[(1, 1)]
[(2, 4)]
[(0, 0), (2, 4)]
[(1, 1)]

繰り返される数値の実行は圧縮になることに注意してください。それがあなたの望みかどうかは分かりません。そうでない場合は、>から!=

あなたの質問を理解しました。私はitertoolsを調べて、Pythonの数行で実行できる解決策を考えてみました。これは"ほぼ組み込み"として修飾されましたが、できませんでした何も思いつきません。

3
Apalala

範囲ペアの生成:

def ranges(lst):
    s = e = None
    r = []
    for i in sorted(lst):
        if s is None:
            s = e = i
        Elif i == e or i == e + 1:
            e = i
        else:
            r.append((s, e))
            s = e = i
    if s is not None:
        r.append((s, e))
    return r

例:

>>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30]
>>> print repr(ranges(lst))
[(1, 1), (5, 7), (12, 12), (15, 18), (30, 30)]

ジェネレータとして:

def gen_ranges(lst):
    s = e = None
    for i in sorted(lst):
        if s is None:
            s = e = i
        Elif i == e or i == e + 1:
            e = i
        else:
            yield (s, e)
            s = e = i
    if s is not None:
        yield (s, e)

例:

>>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30]
>>> print repr(','.join(['%d' % s if s == e else '%d-%d' % (s, e) for (s, e) in gen_ranges(lst)]))
'1,5-7,12,15-18,30'
2
Curt

他の答えは理解するのが難しく、おそらく非効率的だと思います。これが簡単かつ高速であることを願っています。

def ranges(ints):
    ints = sorted(set(ints))
    range_start = previous_number = ints[0]
    for number in ints[1:]:
        if number == previous_number + 1:
            previous_number = number
        else:
            yield range_start, previous_number
            range_start = previous_number = number
    yield range_start, previous_number
1
Mike Amy

組み込みのものや、私が知っているライブラリには何もありません。あまり役に立たないと思いますが、私はあなたが望むものに出くわしたことはありません。

以下に、少なくともプログラムのいくつかのアイデアを示します(C++では、他のいくつかのアイデアが得られます)。

整数のセットを範囲に変換

1
Mark Loeser

Pythonにそのような機能がない場合、ここに実装があります

p = []
last = -2                                                            
start = -1

for item in list:
    if item != last+1:                        
        if start != -1:
            p.append([start, last])
        start = item
    last = item

p.append([start, last])
1
Akhil

もっと短くして:

ranges=lambda l:map(lambda x:(x[0][1],x[-1][1]),map(lambda (x,y):list(y),itertools.groupby(enumerate(l),lambda (x,y):x-y)))
1
Neuer

2年ほど前から新しい答えがなかったので、これはゾンビ愛好家のための答えです!

Itertoolsやジェネレータを使用したくない場合は、以下でlogic(!)を使用します。入力にはセット(質問を参照!)を使用し、結果として適切な範囲のリストを返します。しかし、それに合わせてコードを調整するのは簡単です。

def ranges(l_set: set) ->list:
    rb_set = sorted(l_set - {i +1 for i in l_set})
    re_set = sorted(l_set - {i -1 for i in l_set})
    return [range(rb_set[i], re_set[i]+1) for i in range(len(rb_set))]

例えば:

>>>ranges({6, 9, 10, 7, 8, 2, 3, 14})
[range(2, 4), range(6, 11), range(14, 15)]

>>>ranges({6, 7, 3, 15, 8, 5, 12, 0, 12, 7, 15, 6, 14, 8, 16})
[range(0, 1), range(3, 4), range(5, 9), range(12, 13), range(14, 17)]
1
Konchog

関連する質問 1以外のステップサイズに関心がある場合 およびこの質問のほぼ重複 ここ 。どちらの場合もうまく機能する解決策が here に示されています。

0
smichr