2016-07-29 12 views
1

Ich möchte eine Datei durchlaufen und den Inhalt jeder Zeile in ein tief verschachteltes Diktat schreiben, dessen Struktur durch führende Leerzeichen definiert ist. Dieser Wunsch ist sehr ähnlich wie das dokumentiert here. Ich habe das gelöst, aber jetzt habe ich das Problem, den Fall zu behandeln, in dem wiederholte Schlüssel überschrieben werden, anstatt in eine Liste umgewandelt zu werden.Erstellen eines Baumes/tief geschachtelten Dict mit Listen aus einer eingerückten Textdatei

Im Wesentlichen:

a: 
    b:  c 
    d:  e 
a: 
    b:  c2 
    d:  e2 
    d:  wrench 

in {"a":{"b":"c2","d":"wrench"}} werfen, wenn sie in

{"a":[{"b":"c","d":"e"},{"b":"c2","d":["e2","wrench"]}]} 

ein in sich geschlossenes Beispiel gegossen werden sollte:

import json 

def jsonify_indented_tree(tree): 
    #convert indentet text into json 
    parsedJson= {} 
    parentStack = [parsedJson] 
    for i, line in enumerate(tree): 
     data = get_key_value(line) 
     if data['key'] in parsedJson.keys(): #if parent key is repeated, then cast value as list entry 
      # stuff that doesn't work 
#   if isinstance(parsedJson[data['key']],list): 
#    parsedJson[data['key']].append(parsedJson[data['key']]) 
#   else: 
#    parsedJson[data['key']]=[parsedJson[data['key']]] 
      print('Hey - Make a list now!') 
     if data['value']: #process child by adding it to its current parent 
      currentParent = parentStack[-1] #.getLastElement() 
      currentParent[data['key']] = data['value'] 
      if i is not len(tree)-1: 
       #determine when to switch to next branch 
       level_dif = data['level']-get_key_value(tree[i+1])['level'] #peek next line level 
       if (level_dif > 0): 
        del parentStack[-level_dif:] #reached leaf, process next branch 
     else: 
     #group node, push it as the new parent and keep on processing. 
      currentParent = parentStack[-1] #.getLastElement() 
      currentParent[data['key']] = {} 
      newParent = currentParent[data['key']] 
      parentStack.append(newParent) 
    return parsedJson 

def get_key_value(line): 
    key = line.split(":")[0].strip() 
    value = line.split(":")[1].strip() 
    level = len(line) - len(line.lstrip()) 
    return {'key':key,'value':value,'level':level} 

def pp_json(json_thing, sort=True, indents=4): 
    if type(json_thing) is str: 
     print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) 
    else: 
     print(json.dumps(json_thing, sort_keys=sort, indent=indents)) 
    return None 

#nested_string=['a:', '\tb:\t\tc', '\td:\t\te', 'a:', '\tb:\t\tc2', '\td:\t\te2'] 
#nested_string=['w:','\tgeneral:\t\tcase','a:','\tb:\t\tc','\td:\t\te','a:','\tb:\t\tc2','\td:\t\te2'] 
nested_string=['a:', 
'\tb:\t\tc', 
'\td:\t\te', 
'a:', 
'\tb:\t\tc2', 
'\td:\t\te2', 
    '\td:\t\twrench'] 

pp_json(jsonify_indented_tree(nested_string)) 
+0

Wollen Sie sie immer eine Liste sein, oder einfach nur einen dict, wenn es nur ein Element ist? Ich, würde ich immer eine Liste tun und einfach 'defaultdict' verwenden –

+1

die Eingabe sieht mehr wie eine gerichtete Multigraph, nicht Baum, während die Ausgabe ist ein Diktat, nicht Json. Das letzte Element in einem Array wird nicht als Blatt bezeichnet. Sie haben nicht angegeben, was bei mehrstufiger Einrückung passieren soll, nur dass zwei Ebenen ein Diktat einer Liste von Zeichenfolgen bilden sollten. Könnten Sie bitte versuchen, Ihre Absicht (Geschäftslogik) klarer zu machen? – Aprillion

+0

hat das Eingabeformat irgendwo einen Namen? – Aprillion

Antwort

0

Dieser Ansatz ist (logisch) ein viel einfacher (wenn auch länger):

  1. Verfolgen Sie die level und key - value Paar jeder Zeile in Ihrem mehrzeiligen String
  2. speichern diese Daten in einer level verkeilten dict von Listen: {level1: [dict1, dict2]}
  3. Anfügen nur eine Zeichenkette, die den Schlüssel in einem Schlüssel nur Linie darstellt: {level1: [dict1, dict2, "nestKeyA"]}
  4. Da ein Key-only Zeile bedeutet, dass die nächste Zeile eine Ebene tiefer ist, verarbeiten Sie das auf der nächsten Ebene: {level1: [dict1, dict2, "nestKeyA"], level2: [...]}. Der Inhalt einer tieferen Ebene level2 kann ichduersiees Linie nur eine weitere Schlüssel nur (und die nächste Schleife wird eine neue Ebene level3 so hinzufügen, dass es sein wird {level1: [dict1, dict2, "nestKeyA"], level2: ["nestKeyB"] , level3: [...]}) oder eine neue dict dict3 so dass {level1: [dict1, dict2, "nestKeyA"], level2: [dict3]
  5. Schritte 1-4 fort, bis die aktuelle Zeile eingerückt ist, weniger als der vorherige (bedeutet eine Rückkehr zu einem früheren Bereich). So sieht die Datenstruktur in meinem Beispiel pro Zeile Iteration aus.

    0, {0: []} 
    1, {0: [{'k': 'sds'}]} 
    2, {0: [{'k': 'sds'}, 'a']} 
    3, {0: [{'k': 'sds'}, 'a'], 1: [{'b': 'c'}]} 
    4, {0: [{'k': 'sds'}, 'a'], 1: [{'b': 'c'}, {'d': 'e'}]} 
    5, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: []} 
    6, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: [{'b': 'c2'}]} 
    7, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: [{'b': 'c2'}, {'d': 'e2'}]} 
    

    Dann müssen zwei Dinge passieren. : Die Liste der Diktate muss daraufhin überprüft werden, ob sie doppelte Schlüssel und die in einer Liste zusammengefassten duplizierten Werte enthalten - dies wird in Kürze demonstriert. : wie kann zwischen Iteration 4 und 5, die Liste der dicts aus der tiefsten Ebene (hier 1) kombiniert in einem dict ... Schließlich gesehen werden, um zu zeigen, doppelte Handhabung sind zu beachten:

    [7b, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, 'a'], 1: [{'b': 'c2'}, {'d': 'e2'}, {'d': 'wrench'}]}] 
    [7c, {0: [{'k': 'sds'}, {'a': {'d': 'e', 'b': 'c'}}, {'a': {'d': ['wrench', 'e2'], 'b': 'c2'}}], 1: []}] 
    

    wo wrench und e2 werden in einer Liste platziert, die selbst in ein Diktat geht, das mit ihrem ursprünglichen Schlüssel codiert ist.

  6. Wiederholen Sie die Schritte 1-5, indem Sie einen tieferen Bereich hochziehen und auf ihre übergeordneten Tasten legen, bis der Bereich (Ebene) der aktuellen Zeile erreicht ist.

  7. Behandeln Sie die Abbruchbedingung, um die Liste des Diktats auf der nullten Ebene zu einem Diktat zu kombinieren.

Hier ist der Code:

import json 

def get_kvl(line): 
    key = line.split(":")[0].strip() 
    value = line.split(":")[1].strip() 
    level = len(line) - len(line.lstrip()) 
    return {'key':key,'value':value,'level':level} 

def pp_json(json_thing, sort=True, indents=4): 
    if type(json_thing) is str: 
     print(json.dumps(json.loads(json_thing), sort_keys=sort, indent=indents)) 
    else: 
     print(json.dumps(json_thing, sort_keys=sort, indent=indents)) 
    return None 

def jsonify_indented_tree(tree): #convert shitty sgml header into json 
    level_map= {0:[]} 
    tree_length=len(tree)-1 
    for i, line in enumerate(tree): 
     data = get_kvl(line) 
     if data['level'] not in level_map.keys(): 
      level_map[data['level']]=[] # initialize 
     prior_level=get_kvl(tree[i-1])['level'] 
     level_dif = data['level']-prior_level # +: line is deeper, -: shallower, 0:same 
     if data['value']: 
      level_map[data['level']].append({data['key']:data['value']}) 
     if not data['value'] or i==tree_length: 
      if i==tree_length: #end condition 
       level_dif = -len(list(level_map.keys()))   
      if level_dif < 0: 
       for level in reversed(range(prior_level+level_dif+1,prior_level+1)): # (end, start) 
        #check for duplicate keys in current deepest (child) sibling group, 
        # merge them into a list, put that list in a dict 
        key_freq={} #track repeated keys 
        for n, dictionary in enumerate(level_map[level]): 
         current_key=list(dictionary.keys())[0] 
         if current_key in list(key_freq.keys()): 
          key_freq[current_key][0]+=1 
          key_freq[current_key][1].append(n) 
         else: 
          key_freq[current_key]=[1,[n]] 
        for k,v in key_freq.items(): 
         if v[0]>1: #key is repeated 
          duplicates_list=[] 
          for index in reversed(v[1]): #merge value of key-repeated dicts into list 
           duplicates_list.append(list(level_map[level].pop(index).values())[0]) 
          level_map[level].append({k:duplicates_list}) #push that list into a dict on the same stack it came from 
        if i==tree_length and level==0: #end condition 
         #convert list-of-dict into dict 
         parsed_nest={k:v for d in level_map[level] for k,v in d.items()} 
        else: 
         #push current deepest (child) sibling group onto parent key 
         key=level_map[level-1].pop() #string 
         #convert child list-of-dict into dict 
         level_map[level-1].append({key:{k:v for d in level_map[level] for k,v in d.items()}}) 
         level_map[level]=[] #reset deeper level 
      level_map[data['level']].append(data['key']) 
    return parsed_nest 

nested_string=['k:\t\tsds', #need a starter key,value pair otherwise this won't work... fortunately I always have one 
'a:', 
'\tb:\t\tc', 
'\td:\t\te', 
'a:', 
'\tb:\t\tc2', 
'\td:\t\te2', 
'\td:\t\twrench'] 

pp_json(jsonify_indented_tree(nested_string))