String in definierte Zeichen und sonstige Zeichen 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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Suchst Du sowas?

Code: Alles auswählen

>>> import re
>>> re.split('([*_]+)', 'hjkghdfjkg *sfhkguh__*shgksdf-shgklh ** hsfgjkfh')
['hjkghdfjkg ', '*', 'sfhkguh', '__*', 'shgksdf-shgklh ', '**', ' hsfgjkfh']
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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"]`.
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))
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

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.
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. ;-)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

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"]`
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

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
the more they change the more they stay the same
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Danke nochmal für die Verbesserung. Wobei man Buchstaben/Zeichen ja i.d.R. mit `c`, nicht mit `i` abkürzt. ;P
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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:

Code: Alles auswählen

symbols = ('~', '##', '___')
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]: ('**', '__')
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

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" :roll: 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.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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))
Zuletzt geändert von snafu am Samstag 23. Januar 2010, 14:22, insgesamt 1-mal geändert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Deinen letzten Vorschlag hierzu:

Code: Alles auswählen

symbols = ('~', '##', '___')
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

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)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@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)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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. ;)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

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.
Antworten