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')