2013-11-28 3 views
2

Два файла. Один со сломанными данными, другой с исправлениями. Разбитая:Возобновление вложенного цикла for

ID 0 
T5 rat cake 
~EOR~ 
ID 1 
T1 wrong segg 
T2 wrong nacob 
T4 rat tart 
~EOR~ 
ID 3 
T5 rat pudding 
~EOR~ 
ID 4 
T1 wrong sausag 
T2 wrong mspa 
T3 strawberry tart 
~EOR~ 
ID 6 
T5 with some rat in it 
~EOR~ 

Исправления:

ID 1 
T1 eggs 
T2 bacon 
~EOR~ 
ID 4 
T1 sausage 
T2 spam 
T4 bereft of loif 
~EOR~ 

ПНП означает конец записи. Обратите внимание: файл Broken имеет больше записей, чем файл исправления, в котором есть теги (T1, T2 и т. Д.) Для исправления и теги для добавления. Этот код делает именно то, что он должен делать:

# foobar.py 

import codecs 

source = 'foo.dat' 
target = 'bar.dat' 
result = 'result.dat' 

with codecs.open(source, 'r', 'utf-8_sig') as s, \ 
    codecs.open(target, 'r', 'utf-8_sig') as t, \ 
    codecs.open(result, 'w', 'utf-8_sig') as u: 

    sID = ST1 = sT2 = sT4 = '' 
    RecordFound = False 

    # get source data, record by record 
    for sline in s: 
     if sline.startswith('ID '): 
      sID = sline 
     if sline.startswith('T1 '): 
      sT1 = sline 
     if sline.startswith('T2 '): 
      sT2 = sline 
     if sline.startswith('T4 '): 
      sT4 = sline 
     if sline.startswith('~EOR~'): 
      for tline in t: 
       # copy target file lines, replacing when necesary 
       if tline == sID: 
        RecordFound = True 
       if tline.startswith('T1 ') and RecordFound: 
        tline = sT1 
       if tline.startswith('T2 ') and RecordFound: 
        tline = sT2 
       if tline.startswith('~EOR~') and RecordFound: 
        if sT4: 
         tline = sT4 + tline 
        RecordFound = False 
        u.write(tline) 
        break 

       u.write(tline) 

    for tline in t: 
     u.write(tline) 

Я пишу в новый файл, потому что я не хочу испортить другие два. Первый внешний для цикла заканчивается на последней записи в файле исправлений. В этот момент в целевом файле есть записи для записи. Вот что делает последнее предложение for-clause.

Что мне нюхало, что эта последняя строка неявно берет начало, когда первый внутренний цикл был выбит. Это как если бы он должен сказать `для остальной части tline в t '. С другой стороны, я не вижу, как это сделать с меньшим количеством (или не намного больше) строк кода (используя dicts и что у вас есть). Должен ли я беспокоиться?

Прокомментируйте, пожалуйста.

+0

Я бы создал счетчик «tPosition», который вы увеличиваете каждый раз, когда вы двигаетесь по соответствующему циклу. Затем, когда вы хотите сказать «для остальной части tline in t», вы можете указать, что хотите перебрать что-то вроде: для tline in t [tPosition:] – duhaime

ответ

1
# building initial storage 

content = {} 
record = {} 
order = [] 
current = None 

with open('broken.file', 'r') as f: 
    for line in f: 
     items = line.split(' ', 1) 
     try: 
      key, value = items 
     except: 
      key, = items 
      value = None 

     if key == 'ID': 
      current = value 
      order.append(current) 
      content[current] = record = {} 
     elif key == '~EOR~': 
      current = None 
      record = {} 
     else: 
      record[key] = value 

# patching 

with open('patches.file', 'r') as f: 
    for line in f: 
     items = line.split(' ', 1) 
     try: 
      key, value = items 
     except: 
      key, = items 
      value = None 

     if key == 'ID': 
      current = value 
      record = content[current] # updates existing records only! 
      # if there is no such id -> raises 

      # alternatively you may check and add them to the end of list 
      # if current in content: 
      #  record = content[current] 
      # else: 
      #  order.append(current) 
      #  content[current] = record = {} 

     elif key == '~EOR~': 
      current = None 
      record = {} 
     else: 
      record[key] = value 

# patched! 
# write-out 

with open('output.file', 'w') as f: 
    for current in order: 
     out.write('ID '+current+'\n') 
     record = content[current] 
     for key in sorted(record.keys()): 
      out.write(key + ' ' + (record[key] or '') + '\n') 

# job's done 

вопросы?

+0

Спасибо. Мне нравится ваш подход к обработке записей. Наверное, это действительно больше Pythonic, чем мое. (Я считаю, что Pythonicness довольно сложная тема. Так много там вы можете использовать, но не можете найти самостоятельно). Ваш код разбивается на строки EOR с «необходимостью более 1 значения для распаковки», и я думаю, что 'curent' должен быть' current', но это не важно. Там нет необходимости в обсуждении. – RolfBly

+0

@ RolfBly Я написал это на ПК без установленного python, так что .. Я не тестировал его. Извините за ошибки. Я исправлю их. – akaRem

+0

@ RolfBly Я добавил исправления – akaRem

2

Я бы не стал беспокоиться. В вашем примере t - это дескриптор файла, и вы повторяете его. Файловые дескрипторы в Python - их собственные итераторы; у них есть информация о том, где они читаются в файле, и сохранит свое место, когда вы перебираете их. Вы можете проверить документы python для file.next() для получения дополнительной информации.

См. Также другой ответ, который также говорит об итераторах: What does the "yield" keyword do in Python?. Там много полезной информации!

Редактировать: Вот еще один способ их комбинирования с использованием словарей. Этот метод может быть желательным, если вы хотите сделать другие модификации записей перед вами выход:

import sys 

def get_records(source_lines): 
    records = {} 
    current_id = None 
    for line in source_lines: 
     if line.startswith('~EOR~'): 
      continue 
     # Split the line up on the first space 
     tag, val = [l.rstrip() for l in line.split(' ', 1)] 
     if tag == 'ID': 
      current_id = val 
      records[current_id] = {} 
     else: 
      records[current_id][tag] = val 
    return records 

if __name__ == "__main__": 
    with open(sys.argv[1]) as f: 
     broken = get_records(f) 
    with open(sys.argv[2]) as f: 
     fixed = get_records(f) 

    # Merge the broken and fixed records 
    repaired = broken 
    for id in fixed.keys(): 
     repaired[id] = dict(broken[id].items() + fixed[id].items()) 

    with open(sys.argv[3], 'w') as f: 
     for id, tags in sorted(repaired.items()): 
      f.write('ID {}\n'.format(id)) 
      for tag, val in sorted(tags.items()): 
       f.write('{} {}\n'.format(tag, val)) 
      f.write('~EOR~\n') 

dict(broken[id].items() + fixed[id].items()) часть использует это: How to merge two Python dictionaries in a single expression?

+0

Спасибо! Ссылка на file.next() имеет подтверждение, которое я искал. Я уже столкнулся с объяснением урожайности. – RolfBly

+0

Нехорошо пропустить '~ EOR ~' s. Если после строки '~ EOR ~' отсутствует идентификатор, вы испортите данные. В таких ситуациях вам нужно «поднять». – akaRem

+0

В строке 'repairaired = broken',' repairaired' - это просто псевдоним для 'broken' (это ** тот же dict **), поэтому вы мутируете исходные данные. Такой стиль кода всегда приводит к ошибкам в будущем развитии. Вам нужна глубокая копия 'broken'. Или вы ** должны переименовать ** эти переменные. – akaRem

0

Для полноты картины, а просто поделиться мой энтузиазм и то, что я узнал, ниже - код, с которым я сейчас работаю. Он отвечает на мой ОП и многое другое.

Это частично основано на подходе akaRem выше. Одна функция заполняет dict. Он вызывается дважды, один раз для файла исправлений, один раз для файла для исправления.

import codecs, collections 
from GetInfiles import * 

sourcefile, targetfile = GetInfiles('dat') 
    # GetInfiles reads two input parameters from the command line, 
    # verifies they exist as files with the right extension, 
    # and then returns their names. Code not included here. 

resultfile = targetfile[:-4] + '_result.dat' 

def recordlist(infile): 
    record = collections.OrderedDict() 
    reclist = [] 

    with codecs.open(infile, 'r', 'utf-8_sig') as f: 
     for line in f: 
      try: 
       key, value = line.split(' ', 1) 

      except: 
       key = line 
       # so this line must be '~EOR~\n'. 
       # All other lines must have the shape 'tag: content\n' 
       # so if this errors, there's something wrong with an input file 

      if not key.startswith('~EOR~'): 
       try: 
        record[key].append(value) 
       except KeyError: 
        record[key] = [value] 

      else: 
       reclist.append(record) 
       record = collections.OrderedDict() 

    return reclist 

# put files into ordered dicts    
source = recordlist(sourcefile) 
target = recordlist(targetfile) 

# patching   
for fix in source: 
    for record in target: 
     if fix['ID'] == record['ID']: 
      record.update(fix) 

# write-out    
with codecs.open(resultfile, 'w', 'utf-8_sig') as f: 
    for record in target: 
     for tag, field in record.iteritems(): 
      for occ in field: 
       line = u'{} {}'.format(tag, occ) 
       f.write(line) 

     f.write('~EOR~\n') 

Это теперь упорядоченный дикт. Это было не в моем OP, но файлы нужно перекрестно проверять людьми, поэтому сохранение порядка делает это проще. (Using OrderedDict is really easy. Мои первые попытки найти эту функциональность привели меня к оговоренности, но ее документация беспокоила меня. Нет примеров, запугивающих жаргон ...)

Кроме того, теперь он поддерживает несколько вхождений любого заданного тега внутри записи. Это было не в моем ОП, но мне это нужно. (Этот формат называется «Adlib tagged», это программное обеспечение для каталогизации.)

В отличие от подхода akaRem является исправление, используя update для целевого dict. Я нахожу это, как часто с python, действительно и по-настоящему элегантным. Аналогично для startswith. Это еще две причины, по которым я не могу противостоять ее совместному использованию.

Надеюсь, это полезно.

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