Python - работа с неровными столбцами в строках

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

AB  12   43   54

DM  33   41   45   56   33   77  88

MO  88   55   66   32   34 

KL  10   90   87   47   23  48  56  12

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

Я пробовал пару других подобных вопросов, но не смог решить проблему.

Я считаю, что в Python есть способ сделать это. Может ли кто-нибудь помочь мне?

2 ответа

Решение

Я не вижу более простого способа определить максимальную длину строки, кроме как сделать один проход и найти ее. Затем мы строим 2D-массив за второй проход. Что-то вроде:

from __future__ import print_function
import numpy as np
from itertools import chain

data = '''AB 12 43 54
DM 33 41 45 56 33 77 88
MO 88 55 66 32 34
KL 10 90 87 47 23 48 56 12'''

max_row_len = max(len(line.split()) for line in data.splitlines())

def padded_lines():
    for uneven_line in data.splitlines():
        line = uneven_line.split()
        line += ['0']*(max_row_len - len(line))
        yield line

# I will get back to the line below shortly, it unnecessarily creates the array
# twice in memory:
array = np.array(list(chain.from_iterable(padded_lines())), np.dtype(object))

array.shape = (-1, max_row_len)

print(array)

Это печатает:

[['AB' '12' '43' '54' '0' '0' '0' '0' '0']
 ['DM' '33' '41' '45' '56' '33' '77' '88' '0']
 ['MO' '88' '55' '66' '32' '34' '0' '0' '0']
 ['KL' '10' '90' '87' '47' '23' '48' '56' '12']]

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

Тем не менее, numy массивы должны быть однородными. Вы хотите поместить строки (первый столбец) и целые числа (все остальные столбцы) в один и тот же 2D-массив. Я все еще думаю, что вы находитесь на неправильном пути, и вам следует переосмыслить проблему и выбрать другую структуру данных или организовать ваши данные по-другому. Я не могу помочь вам с этим, так как я не знаю, как вы хотите использовать данные.

(Я вернусь к массиву, созданному дважды, в ближайшее время.)


Как и было обещано, вот решение проблем эффективности. Обратите внимание, что мои опасения были связаны с потреблением памяти.

    def main():

        with open('/tmp/input.txt') as f:
            max_row_len = max(len(line.split()) for line in f)

        with open('/tmp/input.txt') as f:
            str_len_max = len(max(chain.from_iterable(line.split() for line in f), key=len))

        def padded_lines():
            with open('/tmp/input.txt') as f:
                for uneven_line in f:
                    line = uneven_line.split()
                    line += ['0']*(max_row_len - len(line))
                    yield line

        fmt = '|S%d' % str_len_max
        array = np.fromiter(chain.from_iterable(padded_lines()), np.dtype(fmt))

Этот код можно сделать лучше, но я оставлю это на ваше усмотрение.

Потребление памяти, измеренное с memory_profiler в случайно сгенерированном входном файле с 1000000 строками и равномерно распределенными длинами строк от 1 до 20:

Line #    Mem usage    Increment   Line Contents
================================================
     5   23.727 MiB    0.000 MiB   @profile
     6                             def main():
     7                                 
     8   23.727 MiB    0.000 MiB       with open('/tmp/input.txt') as f:
     9   23.727 MiB    0.000 MiB           max_row_len = max(len(line.split()) for line in f)
    10                                     
    11   23.727 MiB    0.000 MiB       with open('/tmp/input.txt') as f:
    12   23.727 MiB    0.000 MiB           str_len_max = len(max(chain.from_iterable(line.split() for line in f), key=len))
    13                                 
    14   23.727 MiB    0.000 MiB       def padded_lines():
    15                                     with open('/tmp/input.txt') as f:
    16   62.000 MiB   38.273 MiB               for uneven_line in f:
    17                                             line = uneven_line.split()
    18                                             line += ['0']*(max_row_len - len(line))
    19                                             yield line
    20                                 
    21   23.727 MiB  -38.273 MiB       fmt = '|S%d' % str_len_max
    22                                 array = np.fromiter(chain.from_iterable(padded_lines()), np.dtype(fmt))
    23   62.004 MiB   38.277 MiB       array.shape = (-1, max_row_len)

С кодом ответа eumiro и с тем же входным файлом:

Line #    Mem usage    Increment   Line Contents
================================================
     5   23.719 MiB    0.000 MiB   @profile
     6                             def main():
     7   23.719 MiB    0.000 MiB       with open('/tmp/input.txt') as f:
     8  638.207 MiB  614.488 MiB           arr = np.array(list(it.izip_longest(*[line.split() for line in f], fillvalue='0'))).T

Сравнение приращений потребления памяти: мой обновленный код потребляет в 16 раз меньше памяти, чем eumiro (614.488/38.273 - около 16).

Что касается скорости: мой обновленный код работает для этого ввода в течение 3.321 с, код eumiro работает за 5.687 с, то есть мой на 1.7x быстрее на моей машине. (Ваш пробег может отличаться.)

Если ваша главная задача - эффективность (как подсказывает ваш комментарий: "Привет, Эумиро, я полагаю, это более эффективно", а затем изменение принятого ответа), то, боюсь, вы приняли менее эффективное решение.

Не поймите меня неправильно, код eumiro действительно лаконичен, и я, безусловно, многому научился. Если эффективность не является моей главной задачей, я бы тоже решил использовать решение eumiro.

Ты можешь использовать itertools.izip_longest который делает поиск для самой длинной строки для вас:

import itertools as it
import numpy as np

with open('filename.txt') as f:
    arr = np.array(list(it.izip_longest(*[line.split() for line in f], fillvalue='0'))).T

arr сейчас:

array([['a', '1', '2', '0'],
       ['b', '3', '4', '5'],
       ['c', '6', '0', '0']], 
      dtype='|S1')
Другие вопросы по тегам