Seite 1 von 2
String in definierte Zeichen und sonstige Zeichen teilen
Verfasst: Freitag 22. Januar 2010, 23:47
von snafu
Ich will eine Zeichenkette in die besagten zwei Kriterien unterteilen. Bisher mache ich das so, wobei ich nicht sicher bin, ob das der beste Weg ist:
Code: Alles auswählen
import re
def get_tokens(s, *symbols):
pattern = '\s*(?:([^{symbols}]+)|(.))'.format(symbols=''.join(symbols))
scanner = re.compile(pattern).scanner(s)
while True:
match = scanner.match()
if not match:
break
yield match.group(match.lastindex)
Code: Alles auswählen
In [67]: s = 'hjkghdfjkg *sfhkguh_shgksdf-shgklh * hsfgjkfh'
In [68]: symbols = '*_'
In [69]: for token in get_tokens(s, *symbols): token
....:
Out[69]: 'hjkghdfjkg '
Out[69]: '*'
Out[69]: 'sfhkguh'
Out[69]: '_'
Out[69]: 'shgksdf-shgklh '
Out[69]: '*'
Out[69]: 'hsfgjkfh'
Wie würdet ihr verfahren, wenn die Symbole aus 2 Zeichen bestehen? Also `**` statt `*` usw.
Verfasst: Samstag 23. Januar 2010, 00:24
von jerch
Suchst Du sowas?
Code: Alles auswählen
>>> import re
>>> re.split('([*_]+)', 'hjkghdfjkg *sfhkguh__*shgksdf-shgklh ** hsfgjkfh')
['hjkghdfjkg ', '*', 'sfhkguh', '__*', 'shgksdf-shgklh ', '**', ' hsfgjkfh']
Verfasst: Samstag 23. Januar 2010, 06:21
von snafu
Nein, es geht darum, nur `**`, `__`, usw zu erhalten. Was nicht passt, soll als restliche Zeichen erkannt werden.
`"hsgjkh **dfhgjkdfh *fhgjkldf* hsfjkgh** sfgzeth"` => `["hsgjkh ", "**", "dfhgjkdfh *fhgjkldf* hsfjkgh", "**", " sfgzeth"]`.
Verfasst: Samstag 23. Januar 2010, 08:47
von BlackJack
Ich würde die Argumente in der Signatur tauschen weil es viel wahrscheinlicher ist, dass jemand die Symbole mit `functools.partial()` "festnageln" möchte als dass man das mit dem `string` tun wollte. Und die ``while`` Schleife lässt sich kürzer schreiben:
Code: Alles auswählen
def get_tokens(symbols, string):
pattern = '\s*(?:([^%s]+)|(.))' % ''.join(symbols)
scanner = re.compile(pattern).scanner(string)
return (match.group(match.lastindex) for match in iter(scanner.match, None))
Verfasst: Samstag 23. Januar 2010, 11:59
von jerch
Aber warum so kompliziert?
Code: Alles auswählen
symbols = '_*'
re.split('([%s]{2})'%symbols, "hsgjkh **dfhgjkdfh *fhgjkldf* hsfjkgh** sfgzeth")
Wofür brauchst Du denn das match-Objekt?
Vllt. kannst Du nochmal genauer beschreiben, wie sich welche Konstellation genau verhalten soll, wahrscheinlich übersehe ich da irgendwas.
Verfasst: Samstag 23. Januar 2010, 12:05
von BlackJack
@jerch: Vielleicht solltest Du keine Vorschläge machen, die bei dem von snafu gezeigten Beispiel andere Ergebnisse bringen. Denn *die* sind dann ja ganz bestimmt keine Lösung für das Problem.

Verfasst: Samstag 23. Januar 2010, 12:23
von jerch
Vllt. steht ich ja grad völlig auf dem Schlauch, was passt denn an meinem neuen Vorschlag nicht zu der Anforderung:
`"hsgjkh **dfhgjkdfh *fhgjkldf* hsfjkgh** sfgzeth"` => `["hsgjkh ", "**", "dfhgjkdfh *fhgjkldf* hsfjkgh", "**", " sfgzeth"]`
Verfasst: Samstag 23. Januar 2010, 12:23
von Dav1d
BlackJack hat geschrieben:@jerch: Vielleicht solltest Du keine Vorschläge machen, die bei dem von snafu gezeigten Beispiel andere Ergebnisse bringen. Denn *die* sind dann ja ganz bestimmt keine Lösung für das Problem.

irgendwas verstehe ich da nicht
Code: Alles auswählen
>>> symbols = '_*'
>>> import re
>>> re.split('([%s]{2})'%symbols, "hsgjkh **dfhgjkdfh *fhgjkldf* hsfjkgh** sfgzeth")
['hsgjkh ', '**', 'dfhgjkdfh *fhgjkldf* hsfjkgh', '**', ' sfgzeth']
es kommt doch genau das raus was snafu wollte
Verfasst: Samstag 23. Januar 2010, 12:56
von snafu
Danke, jerch. Sieht mir genau nach dem aus, was ich suche.
BlackJack könnte das insofern meinen, dass einzelne Zeichen übergeben werden und die innerhalb der Funktion verdoppelt werden. Ansonsten ist es ja vom Ergebnis tatsächlich wie gewünscht.
Verfasst: Samstag 23. Januar 2010, 13:01
von jerch
Nee, leider nicht ganz, da '[*_]{2}' auch '*_' findet.
Versuchs mal damit:
Code: Alles auswählen
re.split('(%s)'%'|'.join('[%s]{2}'%i for i in symbols), "hsgjkh *__dfhgjkdfh *fhgjkldf* hsfjkgh***___ sfgzeth")
Falls Dich die leeren Strings, die bei Konstellationen wie '__**' entstehen, stören, kannst Du ja noch ein filter(None, string-liste) drüberlaufen lassen.
Verfasst: Samstag 23. Januar 2010, 13:12
von snafu
Danke nochmal für die Verbesserung. Wobei man Buchstaben/Zeichen ja i.d.R. mit `c`, nicht mit `i` abkürzt. ;P
Verfasst: Samstag 23. Januar 2010, 13:30
von snafu
Um nochmal darauf zurück zu kommen: Die Lösung tut, was sie soll, aber einen Ticken flexibler fände ich es, wenn eine Sequenz übergeben wird, die auch sowas sein kann:
D.h. nur matchen wenn wirklich 3 Unterstriche vorkommen. Wie gesagt, für meinen speziellen Fall brauche ich das nicht, es ist nur aus Interesse.
Mein jämmerlicher Versuch kann das nicht.
Code: Alles auswählen
In [30]: filter(None, re.split('(%s)'%'|'.join('[%s]+'%s for s in symbols), "hsgjkh *__dfhgjkdfh *fhgjkldf* hsfjkgh***___ sfgzeth"))
Out[30]:
['hsgjkh ',
'*',
'__',
'dfhgjkdfh ',
'*',
'fhgjkldf',
'*',
' hsfjkgh',
'***',
'___',
' sfgzeth']
In [31]: symbols
Out[31]: ('**', '__')
Verfasst: Samstag 23. Januar 2010, 13:53
von jerch
Dafür könntest Du die zu suchende Sequenz angeben und nicht das einzelne Zeichen, also ohne []:
Code: Alles auswählen
>>> re.split('(%s)'%'|'.join('%s'%s for s in symbols), "hsgjkh *__dfhgjkdfh *fhgjkldf* hsfjkgh***___ sfgzeth")
['hsgjkh *__dfhgjkdfh *fhgjkldf* hsfjkgh***', '___', ' sfgzeth']
Hier mußt Du aufpassen, daß Du Regex-Ausdrücke in `symbols` "escaped"

hast.
Übrigens, wenn Du die leeren Strings drin läßt, weißt Du anhand der Position in der Liste, welche Zeichenkette "Steuerzeichen" und welche Text sind (gerader Index -> Text, ungerader Index -> Steuerzeichen)
Alternativ könntest Du auch die Steuerzeichen gleich noch gruppieren lassen, dann wäre der Weg über das match-Objekt der bessere.
Verfasst: Samstag 23. Januar 2010, 14:11
von snafu
Das letzte Beispiel bricht bei mir mit dem Fehler "nothing to repeat" ab. Was hast du denn als `symbols` genommen?
EDIT: Klappt wohl jetzt.
Code: Alles auswählen
filter(None, re.split('(%s)' % '|'.join(re.escape('%s' % symbol) for symbol in symbols), s))
Verfasst: Samstag 23. Januar 2010, 14:13
von jerch
Deinen letzten Vorschlag hierzu:
Verfasst: Samstag 23. Januar 2010, 15:02
von numerix
Hier noch eine Variante für die, die (so wie ich) keinen Vertrag mit RE haben ...
Code: Alles auswählen
def symbolsplit(parts, symbols):
if not symbols: return parts
symbol = symbols.pop()
for i in xrange(len(parts)):
parts[i:i+1] = parts[i].partition(symbol)
return symbolsplit(filter(None,parts), symbols)
symbols = ["~","##","___"]
text = "sdfasf~ fs##safff# sf#afs__# sf___tttrg_"
print symbolsplit([text],symbols)
Verfasst: Samstag 23. Januar 2010, 15:39
von snafu
Nochmal als Funktion:
Code: Alles auswählen
def seperate(delimiters, s):
expr = '(%s)' % '|'.join(re.escape(d) for d in delimiters)
return filter(None, re.split(expr, s))
@numerix: In dem speziellen Fall finde ich RE's handlicher.
Verfasst: Samstag 23. Januar 2010, 18:24
von jerch
@snafu
Ich nehme mal, das es Dir um die farbige Terminalsache geht.
Hier mal eine einfache Version:
Code: Alles auswählen
import re
ANSI_CODES = {
'**' : {1: '\x1b[1m', 0: '\x1b[22m'},
'__' : {1: '\x1b[4m', 0: '\x1b[24m'},
'#red#': {1: '\x1b[91m', 0: '\x1b[39m'},
'#bg_blue#': {1: '\x1b[104m', 0: '\x1b[49m'},
}
def tokenize(tokens, s):
return re.split('(%s)'%'|'.join(re.escape(token)
for token in tokens), s)
def parse(tokens, s_tokenized):
stack = []
status = dict(((i, 0) for i in tokens))
for i, j in enumerate(s_tokenized):
if i%2:
if status[j]:
stack.append(tokens[j][0])
status[j] = 0
else:
stack.append(tokens[j][1])
status[j] = 1
else:
stack.append(j)
for i, j in status.iteritems():
if j:
stack.append(tokens[i][0])
return stack
s = """
Ich bin **fett**, Du bist __unterstrichen__, #red#farbtest#red#
__Ueberlappung **geht__ wohl** #bg_blue#auch#bg_blue#
"""
s_tokenized = tokenize(ANSI_CODES, s)
s_parsed = parse(ANSI_CODES, s_tokenized)
print s_parsed
print 'Kodiert:', ''.join(s_parsed)
print 'Plain:', ''.join(s_tokenized[::2])
Der Parser ist ziemlich dumm, ua. müßtest Du für überlappende Farbbereiche noch Farbstacks mitschleppen und auswerten. Desweiteren fehlen Escapes, falls man doch mal ein Steuertoken als Text haben will.
Interessant wäre auch, wie man das ganze in Windows ohne ansi.sys umsetzen würde (wenn ich mich recht entsinne, müssen dort extra Terminalbefehle abgesetzt werden)
Verfasst: Samstag 23. Januar 2010, 19:14
von snafu
Das ist super. Ich werde das in der Art bei mir einbauen. Übrigens kann man auch für Dictionary-Schlüssel `True` und `False` verwenden. Dann wird's noch etwas klarer.

Verfasst: Samstag 23. Januar 2010, 19:29
von jerch
Ja hast Recht, ich wollte es erst über Bitmasken machen, war mir dann aber zu umständlich.
Hier noch eine escape-Funktion:
Code: Alles auswählen
def escape(tokens, s):
for i in set([t[0] for t in tokens]):
s = s.replace(i+'@', i)
return s
print escape(ANSI_CODE, ''.join(s_parsed))
Ist nur eine Quick-and-Dirty-Idee, mit der Du durch Einfügen eines @ in ein Steuertoken nach dem ersten Zeichen dieses escapen kannst.
Kann man alles noch schöner und ausgefeilter machen.