Как сделать вложенный словарь из текстового файла в python?

avatar
Trent Xavier
9 августа 2021 в 02:58
142
3
1

У меня есть текстовый файл со следующей структурой:

SOURCE: RCM
DESTINATIONS BEGIN
JCK SF3
DESTINATIONS END
SOURCE: TRO
DESTINATIONS BEGIN
GFN SF3
SYD SF3 DH4
DESTINATIONS END

Я пытаюсь создать вложенный словарь, в котором результирующий словарь будет выглядеть так:

handout_routes = {
'RCM': {'JCK': ['SF3']},
'TRO': {'GFN': ['SF3'], 'SYD': ['SF3', 'DH4']}
}

Это всего лишь пример данных, но при чтении данных мы можем предположить следующее: Самая первая строка начинается с SOURCE: за которым следует трехбуквенный код аэропорта IATA. Строка после каждой строки, которая начинается с SOURCE: это DESTINATIONS BEGIN. Между DESTINATIONS BEGIN и DESTINATIONS END есть одна или несколько строк. После каждой строки с DESTINATIONS BEGIN идет соответствующая строка с DESTINATIONS END. Строки между DESTINATIONS BEGIN и DESTINATIONS END начинаются с трехбуквенного кода аэропорта IATA, за которым следует один или несколько трехзначных буквенно-цифровых кодов самолетов. Каждый код отделяется пробелом. Строки после DESTINATIONS END будут начинаться с SOURCE:, иначе вы достигли конца файла.

Пока я пытался

with open ("file_path", encoding='utf-8') as text_data:
    answer = {}
    for line in text_data:
        line = line.split()
        if not line:  # empty line?
            continue
        answer[line[0]] = line[1:]
    print(answer)

Но он возвращает такие данные:

{'SOURCE:': ['WYA'], 'DESTINATIONS': ['END'], 'KZN': ['146'], 'DYU': ['320']}

Думаю, именно так я структурировал код для чтения файла. Любая помощь будет оценена. Возможно, мой код слишком прост для того, что нужно сделать с файлом. Спасибо.

Источник

Ответы (3)

avatar
Matthew B
9 августа 2021 в 03:22
1

Вот программа, которую я написал, работает очень хорошо:

def unpack(file):
  contents:dict = {}
  source:str
  
  for line in file.split('\n'):

    if line[:12] == 'DESTINATIONS':
      pass
    #these lines don't affect the program so we ignore them

    elif not line:
      pass
    #empty line so we ignore it
    
    elif line[:6] == 'SOURCE':
      source = line.rpartition(' ')[-1]
      if source not in contents:
        contents[source] = {}
      
    else:
      idx, *data = line.split(' ')
      contents[source][idx] = list(data)

  return contents
      

with open('file.txt') as file:
  handout_routes = unpack(file.read())
  print(handout_routes)
Trent Xavier
9 августа 2021 в 03:40
0

Это направляет меня на правильный путь, но возвращает только это: {'AER': {}} Возможно, я неправильно реализую ваш код? Что это возвращает для вас?

Matthew B
9 августа 2021 в 03:45
0

Это странно, для меня он возвращает {'RCM': {'JCK': ['SF3']}, 'TRO': {'GFN': ['SF3'], 'SYD': ['SF3', 'DH4 ']}}, именно тот дикт, который, как вы сказали, должен вернуться. Не могли бы вы показать мне файл, который вы пытаетесь открыть?

Trent Xavier
9 августа 2021 в 03:57
0

Конечно, это файл .dat. Это большой набор данных, так как я могу показать вам?

Trent Xavier
9 августа 2021 в 04:00
0

Неважно, это была моя реализация, которая была неправильной. Это отлично работает! Спасибо!

avatar
frogcoder
9 августа 2021 в 06:24
0
from itertools import takewhile
import re


def destinations(lines):
    if next(lines).startswith('DESTINATIONS BEGIN'):
        dest = takewhile(lambda l: not l.startswith('DESTINATIONS END'), lines)
        yield from map(str.split, dest)


def sources(lines):
    source = re.compile('SOURCE:\s*(\w+)')
    while m := source.match(next(lines, '')):
        yield (m.group(1),
               {dest: crafts for dest, *crafts in destinations(lines)})


handout_routes = {s: d for s, d in sources(open('file_path', encoding='utf-8'))}
print(handout_routes)
avatar
David Culbreth
9 августа 2021 в 04:29
0

Я знаю, что уже есть принятый ответ, но я использовал подход, который действительно может помочь вам найти ошибки форматирования в вашем файле, а не просто игнорировать лишние биты:

from tokenize import TokenInfo, tokenize, ENCODING, ENDMARKER, NEWLINE, NAME
from typing import Callable, Generator

class TripParseException(Exception):
    pass

def assert_token_string(token:TokenInfo, expected_string: str):
    if token.string != expected_string:
        raise TripParseException("Unable to parse trip file: expected {}, found {} in line {} ({})".format(
            expected_string, token.string, str(token.start[0]), token.line
        ))
def assert_token_type(token:TokenInfo, expected_type: int):
    if token.type != expected_type:
        raise TripParseException("Unable to parse trip file: expected type {}, found type {} in line {} ({})".format(
            expected_type, token.type, str(token.start[0]), token.line
        ))

def parse_destinations(token_stream: Generator[TokenInfo, None, None])->dict:
    destinations = dict()
    assert_token_string(next(token_stream), "DESTINATIONS")
    assert_token_string(next(token_stream), "BEGIN")
    assert_token_type(next(token_stream), NEWLINE)
    current_token = next(token_stream)
    while(current_token.string != "DESTINATIONS"):
        assert_token_type(current_token, NAME)
        destination = current_token.string
        plane_codes = list()
        current_token = next(token_stream)
        while(current_token.type != NEWLINE):
            assert_token_type(current_token, NAME)
            plane_codes.append(current_token.string)
            current_token = next(token_stream)
        destinations[destination] = plane_codes
        # current token is NEWLINE, get the first token on the next line.
        current_token = next(token_stream)


    # Just parsed "DESTINATIONS", expecting "DESTINATIONS END"
    assert_token_string(next(token_stream), "END")
    assert_token_type(next(token_stream), NEWLINE)
    return destinations

def parse_trip(token_stream: Generator[TokenInfo, None, None]):
    current_token = next(token_stream)
    if(current_token.type == ENDMARKER):
        return None, None
    assert_token_string(current_token, "SOURCE")
    assert_token_string(next(token_stream), ":")
    tok_origin = next(token_stream)
    assert_token_type(tok_origin, NAME)
    assert_token_type(next(token_stream), NEWLINE)
    destinations = parse_destinations(token_stream)

    return tok_origin.string, destinations

def parse_trips(readline: Callable[[], bytes]) -> dict:
    token_gen = tokenize(readline)
    assert_token_type(next(token_gen), ENCODING)
    trips = dict()
    while(True):
        origin, destinations = parse_trip(token_gen)
        if(origin is not None and destinations is not None):
            trips[origin] = destinations
        else:
            break

    return trips

Тогда ваша реализация будет выглядеть так:

import pprint

with open("trips.dat", "rb") as trips_file:
    trips = parse_trips(trips_file.readline)
    pprint.pprint(
        trips
    )

что дает ожидаемый результат:

{'RCM': {'JCK': ['SF3']}, 'TRO': {'GFN': ['SF3'], 'SYD': ['SF3', 'DH4']}}

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