String gleichmäßig teilen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
vegan
User
Beiträge: 12
Registriert: Freitag 13. Dezember 2013, 15:30

Ich will mir ein kleines Skript bauen mit dem ich einen string einlesen kann, diesen dann base64 codiere und diesen codierten String möchte ich jetzt in drei, möglichst gleichlange, Strings zerteilen. Eventuell sollen diese drei Teile dann in einem Array gespeichert werden aber das ist erstmal nebensache, wichtig ist das teilen.

Hier mal etwas Pseudocode:

Code: Alles auswählen

import base64

eingabe = input("Bitte String eingeben: ") #EinString
encoded = base64.b64encode(eingabe) #EinStringEncoded

geteilt = stringteilen(encoded, 3) #Teilt den übergebenen String in 3 gleiche Teile

print geteilt

>>> ['EinSt' , 'ringE' , 'ncoded']

Gibt es da ne build-in Lösung und wenn nein wie geht man da am besten ran sowas selber zu machen?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
vegan
User
Beiträge: 12
Registriert: Freitag 13. Dezember 2013, 15:30

Das ist ja perfekt.
grouper() sieht aus, als würde es genau das machen was ich brauche aber es gibt mir immer eine Fehlermeldung aus.
AttributeError: 'module' object has no attribute 'grouper'

Das ist das Skript:
import base64 as b
import itertools


eingabe = input("Bitte gib einen String ein: ")
encode = b.b64encode(eingabe)

geteilt = itertools.grouper(encode, 3)

print geteilt
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

vegan hat geschrieben:AttributeError: 'module' object has no attribute 'grouper'
grouper ist eine Funktion die itertools verwendet und nicht eine Funktion die in itertools definiert wird. Kopiere dir die Funktion einfach in deinen eigenen Code.
vegan
User
Beiträge: 12
Registriert: Freitag 13. Dezember 2013, 15:30

Ah, sowas hab ich mir fast gedacht aber jetzt bekomme ich noch mehr Fehlermeldungen.

Skript:

Code: Alles auswählen

import base64 as b
import itertools

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 zip_longest(*args, fillvalue=fillvalue)


eingabe = input("Bitte gib einen String ein: ")
encode = b.b64encode(eingabe)


geteilt = grouper(encode, 3)

print eingabe
print encode
print geteilt
Fehlermeldung:
Traceback (most recent call last):
File "C:\Users\Korny\Desktop\test.py", line 15, in <module>
geteilt = grouper(encode, 3)
File "C:\Users\Korny\Desktop\test.py", line 8, in grouper
return zip_longest(*args, fillvalue=fillvalue)
NameError: global name 'zip_longest' is not defined
Zuletzt geändert von vegan am Freitag 13. Dezember 2013, 17:40, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ganz trivial ist das leider nicht zu lösen. Frei nach dem ``grouper()``-Beispiel könnte man sowas machen:

Code: Alles auswählen

from __future__ import division
from itertools import izip_longest, repeat
from math import ceil

def get_parts(s, num_parts):
    part_size = int(ceil(len(s) / num_parts))
    parts = izip_longest(*repeat(iter(s), part_size), fillvalue='')
    return [''.join(part) for part in parts]
Ausgabe:

Code: Alles auswählen

>>> s
'EinStringEncoded'
>>> get_parts(s, 3)
['EinStr', 'ingEnc', 'oded']
Dies entspricht nicht 1:1 deiner Vorgabe. Möchtest du es denn exakt wie die Vorgabe haben? Dann müsste der letzte Teil nämlich bei einer ungeraden Aufteilung mehr Buchstaben haben als seine Vorgänger und die Implementierung wird etwas komplexer.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und hier nochmal gemäß Vorgabe und ohne jegliche Imports:

Code: Alles auswählen

def iter_parts(s, num_parts):
    n = len(s) // num_parts
    for i in range(0, (len(s) - n - 1), n):
        yield s[i:(i + n)]
    yield s[(i + n):]
Ausgabe:

Code: Alles auswählen

>>> list(iter_parts(s, 3))
['EinSt', 'ringE', 'ncoded']
vegan
User
Beiträge: 12
Registriert: Freitag 13. Dezember 2013, 15:30

Ok, zuerst muss ich dir danken für die Mühe das für mich schon auszuarbeiten aber ich hätte, nebenbei bemerkt, auch mit der ersten Version leben können.

Davon abgesehn, muss ich Tränen überströmt feststellen dass ich sehr viel Python nachholen muss.

Code: Alles auswählen

    def iter_parts(s, num_parts): #grüner bereich, verstanden
        n = len(s) // num_parts #grüner bereich, verstanden
        for i in range(0, (len(s) - n - 1), n): #gelber bereich, ungefähr verstanden
            yield s[i:(i + n)] #roter bereich, bahnhof
        yield s[(i + n):] #roter bereich, bahnhof
Das ist bei mir aber schon immer so gewesen und wird auch irgendwie nicht besser.
An dem Tag, wo ich ne Documentation von irgendeiner Programiersprache lese und etwas verstehe, lad ich den ganzen Stadteil zum Essen ein. :(
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier nochmal ein bißchen robuster:

Code: Alles auswählen

def iter_parts(seq, num_parts):
    if num_parts < 0:
        raise ValueError('num_parts must be >= 0')
    if num_parts > len(seq):
        raise ValueError('num_parts must not exceed sequence length')
    total_size = len(seq)
    n = total_size // num_parts
    stop = total_size - n
    if total_size > num_parts:
        stop -= 1
    for i in range(0, stop, n):
        yield seq[i:(i + n)]
    yield seq[(i + n):]
Damit werden jetzt auch Fälle erwartungsgemäß behandelt, wo die Anzahl der Teilstücke der Anzahl der Buchstaben entspricht, oder anders gesagt: Wenn du in deinem Fall genau 16 Teilstücke angefordert hättest.

EDIT: Da sind noch ein paar Randfälle drin, die fehlerhaft ablaufen. Ich gucke mir das später nochmal an. Muss jetzt weg.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier nochmal die versprochene korrigierte Version (auch wenn es den TE vermutlich nicht mehr wirklich interessiert):

Code: Alles auswählen

def iter_parts(seq, num_parts):
    if num_parts == 0:
        return
    if num_parts < 0:
        raise ValueError('num_parts must be >= 0')
    total_size = len(seq)
    if num_parts > total_size:
        raise ValueError('num_parts must not exceed sequence length')
    part_size, remaining = divmod(total_size, num_parts)
    for i in range(num_parts - 1):
        yield seq[part_size * i : part_size * (i + 1)]
    yield seq[-(part_size + remaining):]
Die Funktion garantiert, dass alle Teilstücke bis n-1 gleich lang sind. Das letzte Teilstück setzt sich aus dem, was dann noch übrig ist, zusammen (was auf dem ersten Blick manchmal komisch aussehen kann, gemäß Definition aber korrekt ist).
vegan
User
Beiträge: 12
Registriert: Freitag 13. Dezember 2013, 15:30

Wenn mit TE ich gemeint bin dann, doch, ich bin noch sehr interessiert.
Ich muss auch nochmal sagen, ich kann dir eigentlich garnich genug für deine Mühe danken, das Skript ist echt gut.

Ich würde aber gerne noch ne blöde Frage stellen.
Wie genau heißt dieser Syntax bei

Code: Alles auswählen

yield seq[part_size * i : part_size * (i + 1)]
ist das eine Expression?
Die Frage ist bestimmt voll noobig aber das ist der Teil des Skriptes wo mein Verständniss aufhört, deswegen wollte ich mal wissen wonach ich genau googlen muss.

Davon abgesehen, vielen Dank.
BlackJack

@vegan: Hinter dem ``yield``-Schlüsselwort steht ein Ausdruck (engl. „expression”). Das ist allerdings ein sehr allgemeiner Begriff der eigentlich alles beschreibt was man zu einem Wert auswerten kann.

``yield`` macht aus einer Funktion eine Generatorfunktion. Das ist eigentlich das einzige an der Zeile was nicht unbeding in einem Anfängertutorial zu finden ist.

Das PEP zur ``yield``-Anweisung: http://www.python.org/dev/peps/pep-0255/
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

vegan hat geschrieben:Wenn mit TE ich gemeint bin dann, doch, ich bin noch sehr interessiert.
TE = Thread-Ersteller. Also ja: Vermutlich bist du gemeint. ;)
vegan hat geschrieben:Ich muss auch nochmal sagen, ich kann dir eigentlich garnich genug für deine Mühe danken, das Skript ist echt gut.
Meistens steckt man viel Mühe in Ausarbeitungen, wenn man selber Spaß daran hat. Da ist also ein gewisser Anteil an Eigennutz mit dabei. Aber trotzdem gern geschehen. :)
vegan hat geschrieben:Wie genau heißt dieser Syntax bei

Code: Alles auswählen

yield seq[part_size * i : part_size * (i + 1)]
ist das eine Expression?
Was ``yield`` macht, hat BlackJack ja schon erklärt bzw verlinkt. Dahinter folgt Slicing mit berechneten Werten für den jeweiligen Start- und Stop-Index. Ob das jetzt einfach nur ein ``i`` oder das Ergebnis einer Multiplikation ist, spielt keine Rolle, denn es kommen letztlich ja trotzdem Werte heraus, die für die angeforderte Slicing-Operation verwendbar sind. Und der Rest ist dann nur noch ein bißchen Mathematik. Mach dir ggf klar, welche Start- und Endwerte jeweils rauskämen, wenn man z.B. 3 Schleifendurchläufe hätte. Dann solltest du die Idee dahinter erkennen können.
Antworten