String trennen

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.
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

Hallo, komme nicht mehr weiter.
wie kann man ein String mit Zahl und Text zusammengeschrieben, trennen?

Habe schon mit Regulären Ausdrücken versucht, aber das Problem ist, das ich um die 1000 Zeilen habe. In den Zeilen stehen verschiedene Formen des Strings drin.
Es gibt Zeilen nur mit Text (Abcd) , Text und Zahl (Abcd123 / 123Abcd / Abcd123Abcd), mit Zahl und Sonderzeichen (1 +1 / 2*2) aber auch Zahl Buchstabe Zahl (2x2).
ich möchte alle zahlen und Text trennen, so dass ein Leerzeichen dazwischen ist. Aber Zahlen mit einem Buchstaben oder Sonderzeichen dazwischen soll so bleiben.

Beispiel:
  • 123Abcd ---> 123 Abcd
    Abcd123---> Abcd 123
    Abcd123Abcd--->Abcd 123 Abcd
    1+1--->1+1
    2x2--->2x2
    2x2Abcd--->2x2 Abcd usw.
Und dann habe ich noch zusammengeschriebene Wörter die mit Großbuchstaben anfangen, die müssen auch getrennt werden.

Beispiel :
  • DasIstEinText---> Das Ist Ein Text.
wie kann man das am einfachsten realisieren?

Danke im Voraus.
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
wie kann man das am einfachsten realisieren?
"Einfach" ist ja relativ ;-)

Ich würde es erstmal wahrscheinlich so angehen, dass ich eine Satz Regeln zum Einfügen eines Leerzeichens definieren würde, wobei immer das aktuelle Zeichen und das vorhergehende betrachtet wird.

Dann würde ich zeichenweise über den String iterieren und dabei den neuen String inkl. Leerzeichen bauen.

So kriegt man das auf jeden Fall hin - was nicht heißt, dass es nicht eine elegantere Lösung gibt :-)

Gruß, noisefloor
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

ja daran habe ich auch schon gedacht, dachte nur dass jemand was besseres kennt.
dann versuche ich es mal.
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Mal so als erster Versuch:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8

import re

def main():
    lines = [
        '123Abcd',
        'Abcd123',
        'Abcd123Abcd',
        '1+1',
        '2x2',
        '2x2Abcd',
        'DasIstEinText',
        '123xyz'
    ]
    expected = [
        '123 Abcd',
        'Abcd 123',
        'Abcd 123 Abcd',
        '1+1',
        '2x2',
        '2x2 Abcd',
        'Das Ist Ein Text',
        '123 xyz'
    ]
    pattern = '\d+(?:[x+](?![A-Za-z]))?\d*|[A-Z][a-z]*|[a-z]+'
    result = [' '.join(re.findall(pattern, line)) for line in lines]
    print result
    assert result == expected

if __name__ == '__main__':
    main()
Ergebnis:

Code: Alles auswählen

['123 Abcd', 'Abcd 123', 'Abcd 123 Abcd', '1+1', '2x2', '2x2 Abcd', 'Das Ist Ein Text', '123 xyz']
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Unter der Annahme, dass die mathematischen Ausdrücke nicht komplexer werden als gezeigt (erkennt dabei "+", "-", "x" bzw "*" sowie ":" bzw "/"):

Code: Alles auswählen

from __future__ import print_function
import re

PATTERNS = {
    'word': r'[A-Z][a-z]*',
    'digits': r'\d+',
    'formula': r'\d+[+-:x/*]\d+',
}

def split(patterns, lines):
    if isinstance(patterns, dict):
        patterns = patterns.values()
    pattern = re.compile('|'.join(patterns))
    for line in lines:
        yield pattern.findall(line)

def main():
    lines = [
        '123Abcd',
        'Abcd123',
        'Abcd123Abcd',
        '1+23',
        '34x5TextBeispiel',
        '30*13',
        '4000/4Murx',
        '5453:45BlaBla4534-25',
        '235x256Abcd',
        'DasIstEin123*456Text',
    ]
    for parts in split(PATTERNS, lines):
        print(*parts)
 
if __name__ == '__main__':
    main()
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

hallo, sorry dass ich erst jetzt antworte, hatte keine Zeit.

Ich beides ausprobiert und es funktioniert wunderbar. Allerdings ist meine Liste etwas komplexer.

Liste.txt
http://pastebin.com/LssMP088

Hier der angepasste Code:

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import print_function
import io
import re
 
PATTERNS = [
    r'\+\d',                            # +2, +7
    r'\d[+x]\d',                        # 1+1, 2x2
    r'(?<=\b)[A-ZА-Я]{1,2}\d{1,2}\b',   # A1, AB12
    r'\b[JT][ui][CJ][ei]',              # JuCe TV, TiJi
    r'[A-ZА-Я][a-zа-я]+\.?',            # Abzde, int.
    r'[A-Za-zА-Яа-я][A-ZА-Яa-zа-я]+\.?',# ABSDE, Int.
    r'(?<=\b)[A-ZА-Я]{1,2}(?=\b)',      # A, AB, ABC
    r'\d[A-ZА-Я]\b',                    # 1A
    r'\d\.?\d+',                        # 1234, 2.0, 3.123
    r'\d',
]
 
def split(patterns, datei):
    if isinstance(patterns, dict):
        patterns = patterns.values()
    pattern = re.compile('|'.join(patterns))
    for line in datei:
        line = re.sub('[\[\(][^\(\)\[\]]*[\]\)]', '', line)         # Klammer und deren Inhalt entfernen
        line = re.sub('(?<!(INT))(?<!(int))[_.-](?!\d)', ' ', line) # Unnötige Sonderzeichen entfernen
        line = line.rstrip()
        yield pattern.findall(line)
        
 
def main():
    
    filename_liste = 'Liste.txt'
    with io.open(filename_liste, 'r', encoding='utf-8-sig') as datei:
        for parts in split(PATTERNS, datei):
            
            for teil in range(len(parts)):
                if len(parts[teil]) <= 3:
                    if parts[teil] == 'Geo' or parts[teil] == 'GEO' or parts[teil] == 'geo'\
                        or parts[teil] == 'Nat' or parts[teil] == 'NAT' or parts[teil] == 'nat'\
                        or parts[teil] == 'Дом' or parts[teil] == 'ДОМ' or parts[teil] == 'дом'\
                        or parts[teil] == 'Моя' or parts[teil] == 'МОЯ' or parts[teil] == 'моя'\
                        or parts[teil] == 'Кто' or parts[teil] == 'КТО' or parts[teil] == 'кто':
                        parts[teil] = parts[teil].title()
                    else:
                        parts[teil] = parts[teil].upper()
                else:
                    if parts[teil] != 'JuCe' and parts[teil] != 'TiJi':
                        parts[teil] = parts[teil].title()

            print(*parts)
if __name__ == '__main__':
    main()
Wie man sieht sind es die Sendernamen der IPTV-Sender, die ich sortieren möchte. Es gibt alle möglichen Sender, hauptsächlich aber die russischen. Siehe Liste.txt.
Das funktioniert alles soweit ganz gut, aber es kann bestimmt eleganter gelöst werden?

Code:

Code: Alles auswählen

            for teil in range(len(parts)):
                if len(parts[teil]) <= 3:
                    if parts[teil] == 'Geo' or parts[teil] == 'GEO' or parts[teil] == 'geo'\
                        or parts[teil] == 'Nat' or parts[teil] == 'NAT' or parts[teil] == 'nat'\
                        or parts[teil] == 'Дом' or parts[teil] == 'ДОМ' or parts[teil] == 'дом'\
                        or parts[teil] == 'Моя' or parts[teil] == 'МОЯ' or parts[teil] == 'моя'\
                        or parts[teil] == 'Кто' or parts[teil] == 'КТО' or parts[teil] == 'кто':
                        parts[teil] = parts[teil].title()
                    else:
                        parts[teil] = parts[teil].upper()
                else:
                    if parts[teil] != 'JuCe' and parts[teil] != 'TiJi':
                        parts[teil] = parts[teil].title()
Wie kann ich den Code mit RegEx umschreiben? Da es noch mehr Ausnahmen kommen könnten, ist es evtl. besser mit RegEx zu schreiben. Aber leider ist mein Python-Kenntnis nicht ganz so groß.
Hoffe ihr könnt mir helfen.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kreser06: statt den Inhalt einer Liste zu ändern, erzeugt man am besten eine neue Liste. Für Dein Problem böte es sich an, die verschiedenen Ausnahmen in Sets zu speichern:

Code: Alles auswählen

SHORT_NAMES_TO_TITLE = {'geo', 'nat', 'дом', 'моя', 'кто'}
LONG_NAMES_TO_IGNORE = {'JuCe', 'TiJi'}

[...]

renamed_parts = []
for part in parts:
    if len(part) <= 3:
        if part.lower() in SHORT_NAMES_TO_TITLE:
            renamed_parts.append(part.title())
        else:
            renamed_parts.append(part.upper())
    elif part in LONG_NAMES_TO_IGNORE:
        renamed_parts.append(part)
    else:
        renamed_parts.append(part.title())

print(*renamed_parts)
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

Cool, vielen Dank. Das sieht sehr übersichtlich aus.
Aber was soll das bedeuten?

Code: Alles auswählen

[...]
Ist das ein Platzhalter für den anderen Code?
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Kreser06 hat geschrieben:

Code: Alles auswählen

r'\+\d',                            # +2, +7,
r'\d\.?\d+',                        # 1234, 2.0, 3.123
Falls es eine allgemeine Regex zum Matchen von Floats sein darf:

Code: Alles auswählen

r'[+-]?\d*\.?\d+'
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

hallo noch mal,
komme wieder mal nicht weiter. :(
Mit euerer Hilfe habe ich nun jetzt einen funktionierenden Code.
http://pastebin.com/UqAFm4kP

Nun wollte ich es weiterverarbeiten und als Modul benutzen. Habe mir zwei Dateien erstellt:
  1. main.py - Hauptcode. http://pastebin.com/QcZpPEYy
  2. playlist2userbouquet.py - da werden/sollten die verschiedenen Aufgaben erledigt. http://pastebin.com/Jc5R59re
Statt datei.txt habe ich vorher eine Dictionary erstellt mit Namen und dazugehörenden Links (m3u_to_m3u_dict). Diese Dictionary bzw. die Namen wollte ich mit dem Code von oben anpassen.
Jetzt wollte ich aus der main.py auf den Code zugreifen, aber leider funktioniert das irgendwie nicht. Habe schon alles mögliche(was mein Python-Wissen zulässt) ausprobiert. Ich bekomme den Fehler:
[codebox=pycon file=Unbenannt.txt]Traceback (most recent call last):
File "/Users/elenakretschmann/Documents/=PYTHON=/Playlist/BBEditRunTemp-main.py", line 34, in <module>
main()
File "/Users/elenakretschmann/Documents/=PYTHON=/Playlist/BBEditRunTemp-main.py", line 31, in main
m3u_dict_anpassen()
File "/Users/elenakretschmann/Documents/=PYTHON=/Playlist/BBEditRunTemp-main.py", line 25, in m3u_dict_anpassen
for parts in anpassen.split(anpassen.PATTERNS, liste):
TypeError: split() takes 2 positional arguments but 3 were given[/code]
Wie man sieht habe ich aus Dictionary die Namen extrahiert, da ich dachte dass es evtl. daran liegt.

Code: Alles auswählen

 liste = []
    for key in playlist.m3u_dict.keys():
        liste.append(key)
Eigentlich wollte ich direkt mit der Dict. weiter machen und wenn der Code(Anpassen) fertig ist, wollte ich die Dict. sozusagen aktualisieren mit den den richtigen/angepassten Namen und dazu gehörenden Links. Und später weiter machen.

Was mache ich falsch?
Danke schon mal für eure Mühe.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast eine Methode split geschrieben, der fehlt aber das obligatorische self-Argument als erstes.

Nachtrag: das gilt auch fuer deine andere Methode, gross_klein_schreibung.
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

ohh man, Danke. :D
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier noch ein Verbesserungvorschlag, um den Code etwas pythonischer zu gestalten:

Code: Alles auswählen

def m3u_to_m3u_dict(self, filename_m3u):
    try:
        with io.open(filename_m3u, 'r', encoding='utf-8-sig') as m3u:
            for zeile in m3u:
                if zeile.startswith('#EXTINF'):
                    if zeile[11] != '=' and not zeile.startswith('='):
                        name = zeile.rstrip().replace('#EXTINF:-1,','')
                        link = m3u.readline().rstrip()
                        self.m3u_dict[name] = link
    except IOError as error:
        print('Fehler beim Öffnen:', error)
Ich verwende hier startswith() und habe bei name und link mehrere Methoden hintereinander geschaltet. Außerdem fange ich den etwas aussagekräftigeren IOError ab. Die Fehlermeldung wurde angepasst, da auch andere Fehlerarten, wie z.B. verweigerter Lesezugriff auftreten können. Wenn man trotzdem eine eigene Meldung bei einer nicht existierenden Datei haben möchte:

Code: Alles auswählen

try:
    # ...
except FileNotFoundError:
    msg = 'Die Datei {!r} existiert nicht!'
    print(msg.format(filename_m3u))
except IOError as error:
    print('Fehler beim Öffnen:', error)
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

Cool, Danke.
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

Hallo allerseits,
habe ein nächstes Problem und zwar möchte den Code etwas ändern, aber leider funktioniert das nicht so wie ich es erwartet hatte.

Hier habe ich die Dictionary (name) an SPLIT weitergegeben:

Code: Alles auswählen

def NAME_DICT_ANPASSEN():
    name = playlist.m3u_name_dict
    for parts in anpassen.SPLIT(name):
        print ('xxxxxxxxxxxxx')
        anpassen.GROSS_KLEIN_SCHREIBUNG(parts)
und hier wird es verarbeitet. (for line in name.values():)

Code: Alles auswählen

def SPLIT(self, name):
        playlist = PLAYLIST()
        patterns = self.PATTERNS
        if isinstance(patterns, dict):
            patterns = patterns.values()
        pattern = re.compile('|'.join(patterns))
        for line in name.values():
            print ('XXXXX ' + line)
            line = re.sub('[\[\(][^\(\)\[\]]*[\]\)]', '', line)         # Klammer und deren Inhalt entfernen
            line = re.sub('(?<!(INT))(?<!(int))[_.-](?!\d)', ' ', line) # Unnötige Sonderzeichen entfernen
            line = line.rstrip()
            yield pattern.findall(line)
Allerdings muss ich jetzt nicht die komplette Dict. weitergeben, sondern jeweils den Wert davon z.B. :

Code: Alles auswählen

for name in playlist.m3u_name_dict.values():
        print ('v: ' + name)
        anpassen.SPLIT(name)
aber leider passiert mit dem code (SPLIT) nichts. Warum?

hier nochmal Beides:

Code: Alles auswählen

def NAME_DICT_ANPASSEN():
    
    for name in playlist.m3u_name_dict.values():
        print ('v: ' + name)
        anpassen.SPLIT(name)
und:

Code: Alles auswählen

def SPLIT(self, name):
        playlist = PLAYLIST()
        patterns = self.PATTERNS
        if isinstance(patterns, dict):
            patterns = patterns.values()
        pattern = re.compile('|'.join(patterns))
        for line in name:
            print ('XXXXX ' + line)
            line = re.sub('[\[\(][^\(\)\[\]]*[\]\)]', '', line)         # Klammer und deren Inhalt entfernen
            line = re.sub('(?<!(INT))(?<!(int))[_.-](?!\d)', ' ', line) # Unnötige Sonderzeichen entfernen
            line = line.rstrip()
            yield pattern.findall(line)
im zweitem Beispiel wird nur print ('v: ' + name) ausgegeben.
Habe ich was übersehen?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kreser06: Du solltest dringend Deine Variablenschreibweise an die üblichen Konventionen anpassen; der Code ist deshalb nur schwer lesbar. Warum nichts ausgegeben wird? Du solltest nochmal nachlesen, wie ein Generator funktioniert.
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

@Sirius3:
Hallo, die Variablenschreibweise werde ich natürlich anpassen, weil ich noch am testen bin ist es etwas lästig alles zu ändern, aber danke für den Tipp.
Und jetzt zu der yield-Anweisung. In meinem Fall werden ja alle Übereinstimmungen im Text gesucht und nacheinander ausgegeben.

Code: Alles auswählen

        for value in name.values():
            value = re.sub('[\[\(][^\(\)\[\]]*[\]\)]', '', value) # Klammer und deren Inhalt entfernen
            value = re.sub('(?<!(INT))(?<!(int))[_.-](?!\d)', ' ', value) # Unnötige Sonderzeichen entfernen
            value = value.strip()
            yield pattern.findall(value)
Hier wird ja die Dictionary übergeben und mit for value in name.values(): der Wert ausgelesen z.B. 'Text 123 TEXTText1'. Danach im yield pattern.findall(value) werden die passenden RegEx Übereinstimmungen gesucht und alle an yield übergeben. Habe ich das richtig verstanden?

Und wenn ich vom Anfang an nicht die Dict., sondern Direkt den string value z.B. 'Text 123 TEXTText1' an yield weitergebe dann passiert nichts.

Kannst du/ihr mir bitte auf die Sprünge Helfen? Ich komme wirklich nicht weiter. Bin halt Anfänger. :K
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Kreser06: es geht nicht darum, was der Generator macht, sondern wie Du ihn benutzt/aufrufst.
Kreser06
User
Beiträge: 33
Registriert: Samstag 21. Januar 2017, 11:49

ich hab's.

Code: Alles auswählen

    for value in playlist.m3u_name_dict.values():
        print (value)
        gen = anpassen.SPLIT(value)
        test = next(gen)
        print (test)
Danke das du es so spanned gemacht hast :D . Aber jetzt verstehe ich das auch. Danke
BlackJack

@Kreser06: Das sieht nicht wirklich sinnvoll aus‽
Antworten