2016-08-19 5 views
1

"a{1;4:6}""a{1;2}b{2:4}"の文字列が与えられます。;は2つの異なる数字を表し、:は一連の数字を表します。ブレース内にはセミコロンとコロンの任意の組み合わせがあります。展開された文字列に文字列を展開します

私はそのようなこれらは上記の2つの例を拡大した結果であることを展開したい:

  • "a{1;4:6}"私が対処しなければならなかったことがありません= "a1a4a5a6"
  • "a{1;2}b{2:4}" = "a1b2b3b4a2b2b3b4"

私は通常、簡単に解析可能な既製の形式の文字列を与えられています。この場合、私は手動で文字列を解析する必要があります。

私の試みは、コロンまたはセミコロンのいずれかがある場合にヒットするまで手動で何度も手動で文字列を分割し、そこから文字列を作成することです。これはひどく非効率的であり、私はこのアプローチに関する考えを感謝します。

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

最初に近いブレースで分割の考え方があること、それが保証されていることである:ここでは、コードがどのように見える(私はちょうど全体でより迅速にポイントを得るために、それの多くを省略)どのような本質的です新しい識別子が関連する範囲とともにそれの後に現れます。どこが間違っていますか?私のアプローチは、最初の例のような単純な文字列に対しては機能しますが、2番目の例ではありません。さらに、それは非効率的である。私はこの問題に関するどんなインプットについても感謝しています。

+3

あなたは 'evalをを使用して、いくつかの策略()'でこれを行うにもすることができますPyParsing – dawg

+0

に見たいと思うかもしれません - あなたはそれがセキュリティ上の配慮を与え使用して慣れている場合。興味があれば、私は例を手にします。あなたの[DSL](https://en.wikipedia.org/wiki/Domain-specific_language)は再帰的なものではないので、正規表現のビットはまた仕事をし、より良い選択肢かもしれません。 – FujiApple

+0

なぜa1とa2の間に 'b2b3b4'があるのですか? –

答えて

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

うわー、ありがとう! –

7

(前の回答からpack_tokensを取った)私はそのためpyparsingしようと、私見それはかなり読みやすいコードを生成します。

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

ジャスト(@ialcuazはコメントで尋ねたとして)evalを使用してこれを行うための技術を実証します。もう一度このようにすることをお勧めしませんが、他の答えはより適切です。このテクニックは、完全な吹き抜けのパーサを必要としないときに構造が複雑(すなわち、括弧などで再帰的に)の場合に便利です。

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)) 

が生成されます

('a{1;4:6}', '->', a1a4a5a6) 
('a{1;2}b{2:4}', '->', a1b2b3b4a2b2b3b4) 
('a{1;2}b{2:4}c{3;6}', '->', a1b2c3c6b3c3c6b4c3c6a2b2c3c6b3c3c6b4c3c6) 
関連する問題