2016-08-19 5 views
1

Ich habe eine Zeichenfolge im folgenden Format: "a{1;4:6}" und "a{1;2}b{2:4}", wobei die ; zwei verschiedene Zahlen darstellt, und eine : eine Sequenz von Zahlen darstellt. Innerhalb der Klammer kann eine beliebige Anzahl von Kombinationen aus Semikolons und Doppelpunkten stehen.Packen Sie eine Zeichenfolge in eine erweiterte Zeichenfolge

Ich möchte um es erweitern, so dass diese die Ergebnisse der Erweiterung der beiden oben genannten Beispiele:

  • "a{1;4:6}" = "a1a4a5a6"
  • "a{1;2}b{2:4}" = "a1b2b3b4a2b2b3b4"

Ich habe noch nie zu tun hatte mit so etwas wie vorher, da ich normalerweise Strings in einem vorgefertigten Format bekomme, das leicht zu lesen ist. In diesem Fall muss ich die Zeichenfolge manuell analysieren.

Mein Versuch ist, die Zeichenfolge manuell aufzuteilen, immer und immer wieder, bis Sie einen Fall treffen, wo es einen Doppelpunkt oder ein Semikolon gibt, dann beginnen Sie die Zeichenfolge von dort zu erstellen. Das ist schrecklich ineffizient, und ich würde mich über jeden Gedanken zu diesem Ansatz freuen. Hier ist im Wesentlichen, was der Code aussieht (ich eine Menge davon weggelassen, nur den Punkt über schneller zu bekommen):

>>> s = "a{1;4:6}" 
>>> splitted = s.split("}") 
>>> splitted 
['a{1;4:6', ''] 
>>> splitted2 = [s.split("{") for s in splitted] 
>>> splitted2 
[['a', '1;4:6'], ['']] 
>>> splitted3 = [s.split(";") for s in splitted2[0]] 
>>> splitted3 
[['a'], ['1', '4:6']] 

# ... etc, then build up the strings manually once the ranges are figured out. 

Der Gedanke hinter Spaltung am Ende Klammer zunächst ist, dass es, dass ein garantiert Ein neuer Bezeichner mit einem zugehörigen Bereich erscheint danach. Wo gehe ich falsch? Mein Ansatz funktioniert für einfache Strings wie das erste Beispiel, aber nicht für das zweite Beispiel. Außerdem ist es ineffizient. Ich wäre dankbar für jeden Beitrag zu diesem Problem.

+3

Sie könnten in PyParsing aussehen wollen – dawg

+0

Sie können auch mit einigen Tricks tun dies 'eval()' - wenn Sie sind komfortabel mit, dass die Sicherheit berücksichtigt. Bei Interesse koche ich ein Beispiel. Da deine [DSL] (https://en.wikipedia.org/wiki/Domain-specific_language) nicht rekursiv ist, wird auch ein bisschen Regexing die Aufgabe erledigen und könnte eine bessere Option sein. – FujiApple

+0

Warum ist 'b2b3b4' zwischen' a1' und 'a2'? –

Antwort

4
import re 

def expand(compressed): 

    # 'b{2:4}' -> 'b{2;3;4}' i.e. reduce the problem to just one syntax 
    normalized = re.sub(r'(\d+):(\d+)', lambda m: ';'.join(map(str, range(int(m.group(1)), int(m.group(2)) + 1))), compressed) 

    # 'a{1;2}b{2;3;4}' -> ['a{1;2}', 'b{2;3;4}'] 
    elements = re.findall(r'[a-z]\{[\d;]+\}', normalized) 

    tokens = [] 

    # ['a{1;2}', 'b{2;3;4}'] -> [['a1', 'a2'], ['b2', 'b3', 'b4']] 
    for element in elements: 
     match = re.match(r'([a-z])\{([\d;]+)\}', element) 

     alphanumerics = [] # match result already guaranteed by re.findall() 

     for number in match.group(2).split(';'): 
      alphanumerics.append(match.group(1) + number) 

     tokens.append(alphanumerics) 

    # [['a1', 'a2'], ['b2', 'b3', 'b4']] -> 'a1b2b3b4a2b2b3b4' 
    def pack_tokens(tokens): 

     current, *rest = tokens 

     if not rest: 
      return ''.join(current) # base case 

     return ''.join(token + pack_tokens(rest) for token in current) 

    return pack_tokens(tokens) 

strings = ['a{1;4:6}', 'a{1;2}b{2:4}', 'a{1;2}b{2:4}c{3;6}'] 

for string in strings: 
    print(string, '->', expand(string)) 

OUTPUT

a{1;4:6} -> a1a4a5a6 
a{1;2}b{2:4} -> a1b2b3b4a2b2b3b4 
a{1;2}b{2:4}c{3;6} -> a1b2c3c6b3c3c6b4c3c6a2b2c3c6b3c3c6b4c3c6 
+0

Wow, vielen Dank! –

7

Ich habe versucht, für das pyparsing und es erzeugt IMHO ziemlich lesbaren Code (nahm pack_tokens aus der vorherigen Antwort).

from pyparsing import nums, Literal, Word, oneOf, Optional, OneOrMore, Group, delimitedList 
from string import ascii_lowercase as letters 

# transform a '123' to 123 
number = Word(nums).setParseAction(lambda s, l, t: int(t[0])) 

# parses 234:543 ranges 
range_ = number + Literal(':').suppress() + number 

# transforms the range x:y to a list [x, x+1, ..., y] 
range_.setParseAction(lambda s, l, t: list(range(t[0], t[1]+1))) 

# parse the comma delimited list of ranges or individual numbers 
range_list = delimitedList(range_|number,",") 

# and pack them in a tuple 
range_list.setParseAction(lambda s, l, t: tuple(t)) 

# parses 'a{2,3,4:5}' group 
group = Word(letters, max=1) + Literal('{').suppress() + range_list + Literal('}').suppress() 

# transform the group parsed as ['a', [2, 4, 5]] to ['a2', 'a4' ...] 
group.setParseAction(lambda s, l, t: tuple("%s%d" % (t[0],num) for num in t[1])) 

# the full expression is just those group one after another 
expression = OneOrMore(group) 

def pack_tokens(s, l, tokens): 
    current, *rest = tokens 
    if not rest: 
     return ''.join(current) # base case 
    return ''.join(token + pack_tokens(s, l, rest) for token in current) 

expression.setParseAction(pack_tokens) 


parsed = expression.parseString('a{1,2,3}')[0] 
print(parsed) 
parsed = expression.parseString('a{1,3:7}b{1:5}')[0] 
print(parsed) 
2

nur eine Technik, dies zu tun eval mit demonstrieren (wie @ialcuaz in den Kommentaren gefragt). Wieder würde ich nicht empfehlen, es so zu machen, die anderen Antworten sind passender. Diese Technik kann nützlich sein, wenn die Struktur komplexer ist (d. H. Rekursiv mit Klammern usw.), wenn Sie keinen vollständigen Parser haben möchten.

import re 
import functools 

class Group(object): 
    def __init__(self, prefix, items): 
     self.groups = [[prefix + str(x) for x in items]] 

    def __add__(self, other): 
     self.groups.extend(other.groups) 
     return self 

    def __repr__(self): 
     return self.pack_tokens(self.groups) 

    # adapted for Python 2.7 from @cdlane's code 
    def pack_tokens(self, tokens): 
     current = tokens[:1][0] 
     rest = tokens[1:] 
     if not rest: 
      return ''.join(current) 
     return ''.join(token + self.pack_tokens(rest) for token in current) 

def createGroup(str, *items): 
    return Group(str, items) 

def expand(compressed): 

    # Replace a{...}b{...} with a{...} + b{...} as we will overload the '+' operator to help during the evaluation 
    expr = re.sub(r'(\}\w+\{)', lambda m: '} + ' + m.group(1)[1:-1] + '{', compressed) 

    # Expand : range to explicit list of items (from @cdlane's answer) 
    expr = re.sub(r'(\d+):(\d+)', lambda m: ';'.join(map(str, range(int(m.group(1)), int(m.group(2)) + 1))), expr) 

    # Convert a{x;y;..} to a(x,y, ...) so that it evaluates as a function 
    expr = expr.replace('{', '(').replace('}', ')').replace(";", ",") 

    # Extract the group prefixes ('a', 'b', ...) 
    groupPrefixes = re.findall(ur'(\w+)\([\d,]+\)', expr) 

    # Build a namespace mapping functions 'a', 'b', ... to createGroup() capturing the groupName prefix in the closure 
    ns = {prefix: functools.partial(createGroup, prefix) for prefix in groupPrefixes} 

    # Evaluate the expression using the namespace 
    return eval(expr, ns) 

tests = ['a{1;4:6}', 'a{1;2}b{2:4}', 'a{1;2}b{2:4}c{3;6}'] 
for test in tests: 
    print(test, '->', expand(test)) 

Produziert:

('a{1;4:6}', '->', a1a4a5a6) 
('a{1;2}b{2:4}', '->', a1b2b3b4a2b2b3b4) 
('a{1;2}b{2:4}c{3;6}', '->', a1b2c3c6b3c3c6b4c3c6a2b2c3c6b3c3c6b4c3c6) 
Verwandte Themen