2013-07-25 2 views
7

В принципе, я хочу перебирать файл и помещать содержимое каждой строки в глубоко вложенный dict, структура которого определяется количеством пробелов в начале каждой строки.Создание дерева/глубоко вложенного dict из текстового текстового файла с отступом в python

По существу, цель состоит в том, чтобы взять что-то вроде этого:

a 
    b 
     c 
    d 
     e 

И превратить его в нечто вроде этого:

{"a":{"b":"c","d":"e"}} 

Или это:

apple 
    colours 
     red 
     yellow 
     green 
    type 
     granny smith 
    price 
     0.10 

в этом:

{"apple":{"colours":["red","yellow","green"],"type":"granny smith","price":0.10} 

Чтобы я мог отправить его в модуль JSON Python и сделать JSON.

На данный момент я пытаюсь сделать Dict и список ступенчато, как например:

  1. {"a":""} ["a"]
  2. {"a":"b"} ["a"]
  3. {"a":{"b":"c"}} ["a","b"]
  4. {"a":{"b":{"c":"d"}}}} ["a","b","c"]
  5. {"a":{"b":{"c":"d"},"e":""}} ["a","e"]
  6. {"a":{"b":{"c":"d"},"e":"f"}} ["a","e"]
  7. {"a":{"b":{"c":"d"},"e":{"f":"g"}}} ["a","e","f"]

т.д.

Список действует как «сухарей», показывая, где я в последний раз поставил в Словаре.

Для этого мне нужно пройти через список и создать что-то вроде dict["a"]["e"]["f"], чтобы добраться до последнего дикта. Я имел взгляд на класс AutoVivification, что кто-то сделал что выглядит очень полезно, однако я действительно уверен в:

  1. ли я, используя правильную структуру данных для этого (я планирую отправить это в библиотеку JSON для создания объекта JSON)
  2. Как использовать AutoVivification в этом случае
  3. Есть ли лучший способ в целом подойти к этой проблеме.

я придумал следующую функцию, но она не работает:

def get_nested(dict,array,i): 
if i != None: 
    i += 1 
    if array[i] in dict: 
     return get_nested(dict[array[i]],array) 
    else: 
     return dict 
else: 
    i = 0 
    return get_nested(dict[array[i]],array) 

Оценил помощь!

(Остальная часть моей крайне неполном коды здесь :)

#Import relevant libraries 
import codecs 
import sys 

#Functions 
def stripped(str): 
    if tab_spaced: 
     return str.lstrip('\t').rstrip('\n\r') 
    else: 
     return str.lstrip().rstrip('\n\r') 

def current_ws(): 
    if whitespacing == 0 or not tab_spaced: 
     return len(line) - len(line.lstrip()) 
    if tab_spaced: 
     return len(line) - len(line.lstrip('\t\n\r')) 

def get_nested(adict,anarray,i): 
    if i != None: 
     i += 1 
     if anarray[i] in adict: 
      return get_nested(adict[anarray[i]],anarray) 
     else: 
      return adict 
    else: 
     i = 0 
     return get_nested(adict[anarray[i]],anarray) 

#initialise variables 
jsondict = {} 
unclosed_tags = [] 
debug = [] 

vividfilename = 'simple.vivid' 
# vividfilename = sys.argv[1] 
if len(sys.argv)>2: 
    jsfilename = sys.argv[2] 
else: 
    jsfilename = vividfilename.split('.')[0] + '.json' 

whitespacing = 0 
whitespace_array = [0,0] 
tab_spaced = False 

#open the file 
with codecs.open(vividfilename,'rU', "utf-8-sig") as vividfile: 
    for line in vividfile: 
     #work out how many whitespaces at start 
     whitespace_array.append(current_ws()) 

     #For first line with whitespace, work out the whitespacing (eg tab vs 4-space) 
     if whitespacing == 0 and whitespace_array[-1] > 0: 
      whitespacing = whitespace_array[-1] 
      if line[0] == '\t': 
       tab_spaced = True 

     #strip out whitespace at start and end 
     stripped_line = stripped(line) 

     if whitespace_array[-1] == 0: 
      jsondict[stripped_line] = "" 
      unclosed_tags.append(stripped_line) 

     if whitespace_array[-2] < whitespace_array[-1]: 
      oldnested = get_nested(jsondict,whitespace_array,None) 
      print oldnested 
      # jsondict.pop(unclosed_tags[-1]) 
      # jsondict[unclosed_tags[-1]]={stripped_line:""} 
      # unclosed_tags.append(stripped_line) 

     print jsondict 
     print unclosed_tags 

print jsondict 
print unclosed_tags 
+4

я должен процитировать [Дзен Python] (http://www.python.org/dev/peps/pep-0020/) «Flat лучше, чем вложенное «. Я бы изменил, как вы это делаете. Всегда есть лучший способ, чем вложенные словари. Кроме того, убедитесь, что вы не попадаете в проблему [X Y] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). –

+0

Мой оригинальный способ сделать это было довольно просто создать большую длинную строку, используя различные правила. Будет ли это лучше? – Tomcat

+1

Это зависит от того, чего вы пытаетесь достичь, посмотрите на проблему [XY Problem] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) и убедитесь, что вы не делая подобной ошибки. По сути вам нужно выяснить, что вы DATA, и построить свой контейнер вокруг этого, а не строить контейнер и выяснить, как положить в него DATA. Каждый тип контейнера имеет свои преимущества, но использование строки для хранения разных наборов данных никогда не является хорошей идеей. –

ответ

4

Вот рекурсивное решение. Сначала преобразуйте вход следующим образом.

Входной сигнал:

person: 
    address: 
     street1: 123 Bar St 
     street2: 
     city: Madison 
     state: WI 
     zip: 55555 
    web: 
     email: [email protected] 

Первый шаг выход:

[{'name':'person','value':'','level':0}, 
{'name':'address','value':'','level':1}, 
{'name':'street1','value':'123 Bar St','level':2}, 
{'name':'street2','value':'','level':2}, 
{'name':'city','value':'Madison','level':2}, 
{'name':'state','value':'WI','level':2}, 
{'name':'zip','value':55555,'level':2}, 
{'name':'web','value':'','level':1}, 
{'name':'email','value':'[email protected]','level':2}] 

Это легко осуществить с split(':') и путем подсчета числа ведущих вкладок:

def tab_level(astr): 
    """Count number of leading tabs in a string 
    """ 
    return len(astr)- len(astr.lstrip('\t')) 

Потом кормить выход первого шага в следующую функцию:

def ttree_to_json(ttree,level=0): 
    result = {} 
    for i in range(0,len(ttree)): 
     cn = ttree[i] 
     try: 
      nn = ttree[i+1] 
     except: 
      nn = {'level':-1} 

     # Edge cases 
     if cn['level']>level: 
      continue 
     if cn['level']<level: 
      return result 

     # Recursion 
     if nn['level']==level: 
      dict_insert_or_append(result,cn['name'],cn['value']) 
     elif nn['level']>level: 
      rr = ttree_to_json(ttree[i+1:], level=nn['level']) 
      dict_insert_or_append(result,cn['name'],rr) 
     else: 
      dict_insert_or_append(result,cn['name'],cn['value']) 
      return result 
    return result 

где:

def dict_insert_or_append(adict,key,val): 
    """Insert a value in dict at key if one does not exist 
    Otherwise, convert value to list and append 
    """ 
    if key in adict: 
     if type(adict[key]) != list: 
      adict[key] = [adict[key]] 
     adict[key].append(val) 
    else: 
     adict[key] = val 
+1

Не могли бы вы предоставить коды для перевода ввода в 'Первый шаг вывода'? Спасибо. –

+0

Если кому-то интересно, я создал аналогичную [C# реализацию] (http://stackoverflow.com/a/36998605/107625). –

+0

Вот [очень близкий вопрос] (http://stackoverflow.com/questions/38664465/creating-a-tree-deeply-nested-dict-with-lists-from-an-indended-text-file). Вы можете помочь? – zelusp

0

Прежде всего, не следует использовать array и dict как имена переменных, потому что они зарезервированные слова в Python и повторное использование их может закончиться во всех хаосах.

ОК, поэтому, если вы правильно поняли, у вас есть дерево, указанное в текстовом файле, с родительским обозначением, обозначенным отступами, и вы хотите восстановить фактическую древовидную структуру. Правильно?

Является ли следующее действующим контуром? Потому что мне трудно помещать ваш текущий код в контекст.

result = {} 
last_indentation = 0 
for l in f.xreadlines(): 
    (c, i) = parse(l) # create parse to return character and indentation 
    if i==last_indentation: 
    # sibling to last 
    elif i>last_indentation: 
    # child to last 
    else: 
    # end of children, back to a higher level 

ОК, то ваш список в настоящее время являются родители, что это на самом деле правильно - но я бы держать их указал на словарь, которую вы создали, а не буквальным буквы

только начинают некоторые вещи здесь

result = {} 
parents = {} 
last_indentation = 1 # start with 1 so 0 is the root of tree 
parents[0] = result 
for l in f.xreadlines(): 
    (c, i) = parse(l) # create parse to return character and indentation 
    if i==last_indentation: 
     new_el = {} 
     parents[i-1][c] = new_el 
     parents[i] = new_el 
    elif i>last_indentation: 
    # child to last 
    else: 
    # end of children, back to a higher level 
+0

Да, это точно. – Tomcat

+0

ОК, тогда позвольте мне добавить некоторые вещи ... – Nicolas78

+0

спасибо! если json.dumps взял формат, который не был бы dict, я был бы счастливее: P – Tomcat

2

Следующий код примет блок-отступ и преобразует в дерево XML; это:

foo 
bar 
baz 
    ban 
    bal 

... становится:

<cmd>foo</cmd> 
<cmd>bar</cmd> 
<block> 
    <name>baz</name> 
    <cmd>ban</cmd> 
    <cmd>bal</cmd> 
</block> 

Основной метод:

  1. Установить отступ в 0
  2. Для каждой строки, получить отступа
  3. If > текущий, отступить и сохранить текущий блок/идентификатор в стеке
  4. Если == ток, добавьте к текущему блоку
  5. Если < ток, поп из стека, пока вы не дойдете до согласования отступа

Итак:

from lxml import builder 
C = builder.ElementMaker() 

def indent(line): 
    strip = line.lstrip() 
    return len(line) - len(strip), strip 

def parse_blockcfg(data): 
    top = current_block = C.config() 
    stack = [] 
    current_indent = 0 

    lines = data.split('\n') 
    while lines: 
     line = lines.pop(0) 
     i, line = indent(line) 

     if i==current_indent: 
      pass 

     elif i > current_indent: 
      # we've gone down a level, convert the <cmd> to a block 
      # and then save the current ident and block to the stack 
      prev.tag = 'block' 
      prev.append(C.name(prev.text)) 
      prev.text = None 
      stack.insert(0, (current_indent, current_block)) 
      current_indent = i 
      current_block = prev 

     elif i < current_indent: 
      # we've gone up one or more levels, pop the stack 
      # until we find out which level and return to it 
      found = False 
      while stack: 
       parent_indent, parent_block = stack.pop(0) 
       if parent_indent==i: 
        found = True 
        break 
      if not found: 
       raise Exception('indent not found in parent stack') 
      current_indent = i 
      current_block = parent_block 

     prev = C.cmd(line) 
     current_block.append(prev) 

    return top 
Смежные вопросы