it-swarm.com.de

Steigen Sie mit mehreren Werten, während Sie ein Array in Python schneiden

Ich versuche, m -Werte zu erhalten, während ich durch jedes n -Element eines Arrays schrittweise gehe. Zum Beispiel für m = 2 und n = 5 und angegeben

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Ich möchte abrufen

b = [1, 2, 6, 7]

Gibt es eine Möglichkeit, dies durch Schneiden zu tun? Ich kann dies mit einem verschachtelten Listenverständnis tun, aber ich habe mich gefragt, ob es einen Weg gibt, dies nur mit den Indizes zu tun. Als Referenz ist der Listenverständnisweg:

 b = [k for j in [a[i:i+2] for i in range(0,len(a),5)] for k in j]
25
Paul W

Ich stimme mit wim überein, dass Sie es nicht mit dem Schneiden tun können. Aber Sie können es mit nur einem Listenverständnis machen:

>>> [x for i,x in enumerate(a) if i%n < m]
[1, 2, 6, 7]
24
Kevin

Nein, das ist beim Schneiden nicht möglich. Beim Schneiden werden nur Start, Stopp und Schritt unterstützt. Es gibt keine Möglichkeit, Schritte mit "Gruppen" mit einer Größe größer als 1 darzustellen.

6
wim

Kurz gesagt, nein, das geht nicht. Sie können jedoch itertools verwenden, um die Notwendigkeit von Zwischenlisten zu beseitigen:

from itertools import chain, islice

res = list(chain.from_iterable(islice(a, i, i+2) for i in range(0, len(a), 5)))

print(res)

[1, 2, 6, 7]

Ausleihen von @ Kevins Logik: Wenn Sie eine vektorisierte Lösung benötigen, um eine for-Schleife zu vermeiden, können Sie die Drittanbieter-Bibliothek numpy verwenden:

import numpy as np

m, n = 2, 5
a = np.array(a)  # convert to numpy array
res = a[np.where(np.arange(a.shape[0]) % n < m)]
5
jpp

Es gibt andere Möglichkeiten, die in einigen Fällen alle Vorteile haben, aber keine sind "nur in Scheiben schneiden".


Die allgemeinste Lösung besteht wahrscheinlich darin, Ihre Eingaben zu gruppieren, die Gruppen zu trennen und die Segmente dann wieder zu reduzieren. Ein Vorteil dieser Lösung besteht darin, dass Sie sie träge ausführen können, ohne große Zwischenlisten zu erstellen, und dass Sie sie für alle Iterationen ausführen können, einschließlich eines trägen Iterators, nicht nur für eine Liste.

# from itertools recipes in the docs
def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.Zip_longest(*args, fillvalue=fillvalue)
groups = grouper(a, 5)
truncated = (group[:2] for group in groups)
b = [elem for group in truncated for elem in group]

Und Sie können das in einen ziemlich einfachen Einzeiler umwandeln, obwohl Sie immer noch die Funktion grouper benötigen:

b = [elem for group in grouper(a, 5) for elem in group[:2]]

Eine andere Möglichkeit besteht darin, eine Liste von Indizes zu erstellen und mit itemgetter alle Werte abzurufen. Dies ist für eine kompliziertere Funktion möglicherweise besser lesbar als nur "die ersten 2 von 5", für etwas so Einfaches wie Ihre Verwendung jedoch wahrscheinlich weniger lesbar:

indices = [i for i in range(len(a)) if i%5 < 2]
b = operator.itemgetter(*indices)(a)

… Die sich in einen Einzeiler verwandeln lässt:

b = operator.itemgetter(*[i for i in range(len(a)) if i%5 < 2])(a)

Und Sie können die Vorteile der beiden Ansätze kombinieren, indem Sie Ihre eigene Version von itemgetter schreiben, die einen langsamen Indexiterator benötigt - was ich nicht zeigen werde, da Sie noch besser vorgehen können, wenn Sie eine Version schreiben, die einen Indexfilter verwendet Funktion stattdessen:

def indexfilter(pred, a):
    return [elem for i, elem in enumerate(a) if pred(i)]
b = indexfilter((lambda i: i%5<2), a)

(Um indexfilter faul zu machen, ersetzen Sie einfach die Klammern durch parens.)

… Oder als Einzeiler:

b = [elem for i, elem in enumerate(a) if i%5<2]

Ich denke, das letzte ist vielleicht das am besten lesbare. Und es funktioniert mit allen iterablen und nicht nur mit Listen, und es kann faul gemacht werden (ersetzen Sie einfach die Klammern durch parens). Aber ich denke immer noch nicht, dass es einfacher ist als Ihr ursprüngliches Verständnis, und es ist nicht nur das Schneiden.

3
abarnert

Die Frage stellt Array dar, und wenn wir über NumPy-Arrays sprechen, können wir sicherlich einige offensichtliche NumPy-Tricks und einige weniger offensichtliche Tricks verwenden. Wir können slicing verwenden, um unter bestimmten Bedingungen eine 2D-Ansicht in die Eingabe zu bekommen.

Nun, basierend auf der Arraylänge, nennen wir es l und m. Wir haben drei Szenarien:

Szenario 1: l ist durch n teilbar

Wir können Slicing und Reshaping verwenden, um einen Blick in das Eingabe-Array zu bekommen und somit konstante Laufzeit zu erhalten.

Überprüfen Sie das Ansichtskonzept:

In [108]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

In [109]: m = 2; n = 5

In [110]: a.reshape(-1,n)[:,:m]
Out[110]: 
array([[1, 2],
       [6, 7]])

In [111]: np.shares_memory(a, a.reshape(-1,n)[:,:m])
Out[111]: True

Überprüfen Sie die Timings auf einem sehr großen Array und damit einen konstanten Laufzeitanspruch:

In [118]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

In [119]: %timeit a.reshape(-1,n)[:,:m]
1000000 loops, best of 3: 563 ns per loop

In [120]: a = np.arange(10000000)

In [121]: %timeit a.reshape(-1,n)[:,:m]
1000000 loops, best of 3: 564 ns per loop

Um eine abgeflachte Version zu erhalten:

Wenn wir have haben, um ein abgeflachtes Array als Ausgabe zu erhalten, müssen wir nur einen Abflachungsvorgang mit .ravel() verwenden, wie so -

In [127]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

In [128]: m = 2; n = 5

In [129]: a.reshape(-1,n)[:,:m].ravel()
Out[129]: array([1, 2, 6, 7])

Timings zeigen, dass es nicht zu schlecht ist im Vergleich zu den anderen Loops und vektorisierten numpy.where-Versionen anderer Posts -

In [143]: a = np.arange(10000000)

# @Kevin's soln
In [145]: %timeit [x for i,x in enumerate(a) if i%n < m]
1 loop, best of 3: 1.23 s per loop

# @jpp's soln
In [147]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
10 loops, best of 3: 145 ms per loop

In [144]: %timeit a.reshape(-1,n)[:,:m].ravel()
100 loops, best of 3: 16.4 ms per loop

Szenario 2: l ist nicht durch n teilbar, aber die Gruppen enden am Ende mit einer vollständigen Gruppe

Wir gehen zu den nicht naheliegenden NumPy-Methoden mit np.lib.stride_tricks.as_strided , die es erlauben, über die Speicherblockgrenzen hinauszugehen (daher müssen wir hier vorsichtig sein, um nicht in diese zu schreiben), um eine Lösung mit slicing zu ermöglichen. Die Implementierung würde in etwa so aussehen -

def select_groups(a, m, n):
    a = np.asarray(a)
    strided = np.lib.stride_tricks.as_strided

    # Get params defining the lengths for slicing and output array shape    
    nrows = len(a)//n
    add0 = len(a)%n
    s = a.strides[0]
    out_shape = nrows+int(add0!=0),m

    # Finally stride, flatten with reshape and slice
    return strided(a, shape=out_shape, strides=(s*n,s))

Ein Beispiellauf, um zu überprüfen, ob die Ausgabe eine view ist.

In [151]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])

In [152]: m = 2; n = 5

In [153]: select_groups(a, m, n)
Out[153]: 
array([[ 1,  2],
       [ 6,  7],
       [11, 12]])

In [154]: np.shares_memory(a, select_groups(a, m, n))
Out[154]: True

Um eine abgeflachte Version zu erhalten, fügen Sie .ravel() hinzu.

Lassen Sie uns einige Zeitvergleiche erhalten -

In [158]: a = np.arange(10000003)

In [159]: m = 2; n = 5

# @Kevin's soln
In [161]: %timeit [x for i,x in enumerate(a) if i%n < m]
1 loop, best of 3: 1.24 s per loop

# @jpp's soln
In [162]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
10 loops, best of 3: 148 ms per loop

In [160]: %timeit select_groups(a, m=m, n=n)
100000 loops, best of 3: 5.8 µs per loop

Wenn wir eine abgeflachte Version brauchen, ist es immer noch nicht so schlimm -

In [163]: %timeit select_groups(a, m=m, n=n).ravel()
100 loops, best of 3: 16.5 ms per loop

Szenario # 3: l ist nicht durch n teilbar, und die Gruppen enden am Ende mit unvollständige

Für diesen Fall würden wir am Ende ein zusätzliches Slicing über das, was wir in der vorherigen Methode hatten, benötigen.

def select_groups_generic(a, m, n):
    a = np.asarray(a)
    strided = np.lib.stride_tricks.as_strided

    # Get params defining the lengths for slicing and output array shape    
    nrows = len(a)//n
    add0 = len(a)%n
    lim = m*(nrows) + add0
    s = a.strides[0]
    out_shape = nrows+int(add0!=0),m

    # Finally stride, flatten with reshape and slice
    return strided(a, shape=out_shape, strides=(s*n,s)).reshape(-1)[:lim]

Probelauf -

In [166]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

In [167]: m = 2; n = 5

In [168]: select_groups_generic(a, m, n)
Out[168]: array([ 1,  2,  6,  7, 11])

Timings -

In [170]: a = np.arange(10000001)

In [171]: m = 2; n = 5

# @Kevin's soln
In [172]: %timeit [x for i,x in enumerate(a) if i%n < m]
1 loop, best of 3: 1.23 s per loop

# @jpp's soln
In [173]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
10 loops, best of 3: 145 ms per loop

In [174]: %timeit select_groups_generic(a, m, n)
100 loops, best of 3: 12.2 ms per loop
2
Divakar

Mit itertools können Sie einen Iterator erhalten mit:

from itertools import compress, cycle

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
n = 5
m = 2

it = compress(a, cycle([1, 1, 0, 0, 0]))
res = list(it)
0
Jacques Gaudin

Ich weiß, dass Rekursion nicht beliebt ist, aber würde so etwas funktionieren? Unsicher ist auch, ob das Hinzufügen von Rekursionen zu den Mixzählungen nur durch das Verwenden von Slices erfolgt.

def get_elements(A, m, n):
    if(len(A) < m):
        return A
    else:
        return A[:m] + get_elements(A[n:], m, n)

A ist das Array, m und n sind wie in der Frage definiert. Das erste if deckt den Basisfall ab, bei dem Sie ein Array mit einer geringeren Länge als die Anzahl der Elemente haben, die Sie abzurufen versuchen, und das zweite if den rekursiven Fall. Ich bin etwas neu in Python, bitte verzeihen Sie mein schlechtes Verständnis der Sprache, wenn dies nicht richtig funktioniert, obwohl ich es getestet habe und es scheint gut zu funktionieren.

0
GnoveltyGnome