2016-06-20 3 views
1

Sagen wir, ich habe eine Reihe von ~ 100.000 verschiedenen Zahlen. Einige sind sequenziell, andere nicht.Erstellen einer "Slice Notation" -Stil Liste aus einer Reihe von Zahlen in Python

Um das Problem, eine kleine Teilmenge dieser Zahlen zu zeigen, könnte sein:

(a) {1,2,3,4,5,6,7,8,9,11,13, 15,45,46,47,3467}

Ein effizienter Weg, um diese Teilmenge des Schreibens ist wie folgt:

(b) 1: 9: 1,11: 15: 2,45: 47: 1.3467

Dies ist eine erweiterte Version der Slice-Notation von Python und Matlab.

Meine Frage ist: Wie kann ich effizient eine Liste in der letzteren Notation in Python erhalten, aus einer Liste des früheren Typs?

I.e., gegeben (a), wie kann ich (b) in Python effizient erhalten?

+2

starten, indem zum Beispiel suchen http://stackoverflow.com/q/3149440/3001761 – jonrsharpe

+0

Nützlicher Thread, der mich zumindest auf die richtige Spur gesetzt hat. Soweit ich das beurteilen kann, gibt es mir jedoch noch nicht die Schritt-Information, wie oben gezeigt. – Kobs

+1

@Kobye Sie haben eine Antwort akzeptiert, die von Slice geht -> Array, dachte ich, Sie wollten in die andere Richtung gehen? –

Antwort

1

ich glaube, ich habe es aber der folgende Code war nicht sehr thoroughl y getestet und kann Fehler enthalten.

Grundsätzlich get_partial_slices werden versuchen partial_slice Objekte zu erstellen, wenn die nächste Nummer in der (sortiert) Satz nicht .fit() in die Scheibe ist .end() ed und die nächste Scheibe gestartet wird.

Wenn eine Scheibe nur 1 Punkt hat in ihm (oder 2 Artikel und step!=1) wird als separate Zahlen statt einer Scheibe (daher die Notwendigkeit für yield from current.end() repräsentiert die Scheibe seit endete in zwei Zahlen statt einer Scheibe zur Folge hat.)

class partial_slice: 
    """heavily relied on by get_partial_slices 
This attempts to create a slice from repeatedly adding numbers 
once a number that doesn't fit the slice is found use .end() 
to generate either the slice or the individual numbers""" 
    def __init__(self, n): 
     self.start = n 
     self.stop = None 
     self.step = None 
    def fit(self,n): 
     "returns True if n fits as the next element of the slice (or False if it does not" 
     if self.step is None: 
      return True #always take the second element into consideration 
     elif self.stop == n: 
      return True #n fits perfectly with current stop value 
     else: 
      return False 

    def add(self, n): 
     """adds a number to the end of the slice, 
    will raise a ValueError if the number doesn't fit""" 
     if not self.fit(n): 
      raise ValueError("{} does not fit into the slice".format(n)) 
     if self.step is None: 
      self.step = n - self.start 
     self.stop = n+self.step 

    def to_slice(self): 
     "return slice(self.start, self.stop, self.step)" 
     return slice(self.start, self.stop, self.step) 
    def end(self): 
     "generates at most 3 items, may split up small slices" 
     if self.step is None: 
      yield self.start 
      return 
     length = (self.stop - self.start)//self.step 
     if length>2: 
      #always keep slices that contain more then 2 items 
      yield self.to_slice() 
      return 
     elif self.step==1 and length==2: 
      yield self.to_slice() 
      return 
     else: 
      yield self.start 
      yield self.stop - self.step 


def get_partial_slices(set_): 
    data = iter(sorted(set_)) 
    current = partial_slice(next(data)) 
    for n in data: 
     if current.fit(n): 
      current.add(n) 
     else: 
      yield from current.end() 
      current = partial_slice(n) 
    yield from current.end() 


test_case = {1,2,3,4,5,6,7,8,9,11,13,15,45,46,47,3467} 
result = tuple(get_partial_slices(test_case)) 

#slice_set_creator is from my other answer, 
#this will verify that the result was the same as the test case. 
assert test_case == slice_set_creator[result] 

def slice_formatter(obj): 
    if isinstance(obj,slice): 
     # the actual slice objects, like all indexing in python, doesn't include the stop value 
     # I added this part to modify it when printing but not when created because the slice 
     # objects can actually be used in code if you want (like with slice_set_creator) 
     inclusive_stop = obj.stop - obj.step 
     return "{0.start}:{stop}:{0.step}".format(obj, stop=inclusive_stop) 
    else: 
     return repr(obj) 

print(", ".join(map(slice_formatter,result))) 
+0

Das macht genau das, was ich will. Um es in Python 2.7 zum Laufen zu bringen, musste ich "yield from current.end()" in "for bar in current.end(): yield bar" ändern. Derzeit habe ich immer einen Schritt zu viel, aber ich sollte in der Lage sein, dieses Problem ziemlich einfach selbst zu beheben. Vielen Dank, dass Sie sich diese saubere Lösung ausgedacht haben. – Kobs

+1

Kein Problem, und der eine Schritt zu viele ist, weil ich Python-Slices verwenden, die den Endpunkt nicht enthalten, haben Sie vielleicht bemerkt, dass ich dafür in 'slice_formatter' für Druckzwecke kompensiert, aber vorausgesetzt, Sie verlassen die tatsächlichen Slice-Objekte als sie Sie könnten sie theoretisch im tatsächlichen Code verwenden. (sonst würdest du einfach 'to_slice' ändern) –

1

Haftungsausschluss: Ich habe die Frage falsch gelesen und dachte, Sie wollten von der Slice-Notation in die Set-Version gehen, das beantwortet nicht Ihre Frage, aber ich dachte, es war es wert verlassen gepostet. Es scheint auch, dass numpy._r die gleiche (oder zumindest sehr ähnliche) Sache macht.

Zunächst einmal zur Kenntnis, dass, wenn Sie mit Python 3.5 + PEP 3132 gibt es eine Option, die *unpacking Methode in Satz Literale zu verwenden:

>>> {*range(1,9), *range(11,15,2), *range(45,47), 3467} 
{1, 2, 3, 4, 5, 6, 7, 8, 11, 3467, 13, 45, 46} 

Ansonsten ist die Notation 11:15:2 nur verwendet, wenn __getitem__ oder __setitem__ verwendet wird, auf ein Objekt, so würden Sie müssen nur ein Objekt einrichten, die Ihre Sets generieren:

def slice_to_range(slice_obj): 
    assert isinstance(slice_obj, slice) 
    assert slice_obj.stop is not None, "cannot have stop of None" 
    start = slice_obj.start or 0 
    stop = slice_obj.stop 
    step = slice_obj.step or 1 
    return range(start,stop,step) 

class Slice_Set_Creator: 
    def __getitem__(self,item): 
     my_set = set() 
     for part in item: 
      if isinstance(part,slice): 
       my_set.update(slice_to_range(part)) 
      else: 
       my_set.add(part) 
     return my_set 

slice_set_creator = Slice_Set_Creator() 

desired_set = slice_set_creator[1:9:1,11:15:2,45:47:1,3467] 

>>> desired_set 
{1, 2, 3, 4, 5, 6, 7, 8, 11, 3467, 13, 45, 46} 
+1

oh shoot, ich dachte du wolltest den anderen Weg gehen .... –

+0

In der Tat würde ich gerne in die andere Richtung gehen! Trotzdem danke, dass Sie sich die Zeit genommen haben, die gegenteilige Antwort zu geben. Könnte für einen anderen Benutzer eines Tages nützlich sein. – Kobs

+1

Ja Entschuldigung, Gelernt, ich konnte mit dieser Notation Range machen und bin aufgeregt, ich könnte es zeigen, dass ich irgendwie alles andere aus den Augen verloren habe (errötet) Der Übergang von einem Set zu den Slices scheint viel mehr involviert zu sein Für einen würden Sie wahrscheinlich die Zahlen in der Reihenfolge brauchen, was bedeutet, dass die 100 000+ Nummern "sortiert" werden. Auch wie man einsame Zahlen gegen eine Scheibe von zwei Zahlen bestimmt (oder würde jede Scheibe mindestens 3 Zahlen sein müssen?) Was ist mit '{2,4,6,7,8,9}' wäre es '[2: 7: 2, 8: 10: 1 'oder' [2: 5: 2, 7: 10: 1] oder etwas ganz anderes? –

1

Die einfachste Möglichkeit ist die Verwendung von numpys r_[] Syntax. Also für Ihr Beispiel wäre es nur sein:

>>> from numpy import r_ 
>>> 
>>> a = r_[1:10, 11:17:2, 45:48, 3467] 

Beachten Sie, dass Python Scheiben enthalten nicht die letzte Nummer, und die x: y: 1 impliziert. Und dieser Ansatz wird im Produktionscode nicht so schnell sein wie eine andere, anspruchsvollere Lösung, aber er ist gut für den interaktiven Gebrauch.

können Sie sehen, dass dies gibt Ihnen eine numpy Array mit den Zahlen, die Sie wollen:

>>> print(a) 
[ 1 2 3 4 5 6 7 8 9 11 13 15 45 46 47 
3467] 
Verwandte Themen