У меня есть текстовый файл, который не подтверждает стандарты. Поэтому я знаю (конец, начало) позиции каждого значения столбца.
Пример текстового файла:
# # # #
Techy Inn Val NJ
Найденную позицию # с помощью этого кода:
1 f = open('sample.txt', 'r')
2 i = 0
3 positions = []
4 for line in f:
5 if line.find('#') > 0:
6 print line
7 for each in line:
8 i += 1
9 if each == '#':
10 positions.append(i)
1 7 11 15 = > Позиции
До сих пор так хорошо! Теперь, как получить значения из каждой строки на основе позиций, которые я выбрал? Я пытаюсь построить эффективный цикл, но любые указатели очень оценены ребятами! Спасибо (:
Сверху моей головы:
f = open(.......)
header = f.next() # get first line
posns = [i for i, c in enumerate(header + "#") if c = '#']
for line in f:
fields = [line[posns[k]:posns[k+1]] for k in xrange(len(posns) - 1)]
Обновить с проверенным, фиксированным кодом:
import sys
f = open(sys.argv[1])
header = f.next() # get first line
print repr(header)
posns = [i for i, c in enumerate(header) if c == '#'] + [-1]
print posns
for line in f:
posns[-1] = len(line)
fields = [line[posns[k]:posns[k+1]].rstrip() for k in xrange(len(posns) - 1)]
print fields
Входной файл:
# # #
Foo BarBaz
123456789abcd
Отладочный вывод:
'# # #\n'
[0, 7, 10, -1]
['Foo', 'Bar', 'Baz']
['1234567', '89a', 'bcd']
Заметки о безопасности:
#
в строке заголовка; ему не нужна строка заголовка, которая должна быть дополнена пробелами или чем-либо еще.#
. Финальное (?) обновление: Предложение Leapfrooging @gnibbler использовать slice()
: настроить срезы один раз перед циклом.
import sys
f = open(sys.argv[1])
header = f.next() # get first line
print repr(header)
posns = [i for i, c in enumerate(header) if c == '#']
print posns
slices = [slice(lo, hi) for lo, hi in zip(posns, posns[1:] + [None])]
print slices
for line in f:
fields = [line[sl].rstrip() for sl in slices]
print fields
fields = [line[slice(*x)] for x in zip(posns, posns[1:])]
Здесь можно прочитать поля фиксированной ширины с помощью regexp
>>> import re
>>> s="Techy Inn Val NJ"
>>> var1,var2,var3,var4 = re.match("(.{5}) (.{3}) (.{3}) (.{2})",s).groups()
>>> var1
'Techy'
>>> var2
'Inn'
>>> var3
'Val'
>>> var4
'NJ'
>>>
Хорошо, чтобы быть немного по-другому, и чтобы дать запрос в обобщенном комментарии, я использую строку заголовка вместо функции среза и генератора. Кроме того, я разрешил первым столбцам комментировать, не помещая имя поля в первый столбец и используя имена многочленных полей вместо "#".
Минус-точка заключается в том, что в одном поле char нет заголовков заголовков, а в строке заголовка есть только "#" (которые всегда считаются в предыдущих решениях как начало поля, даже после букв в заголовке)
sample="""
HOTEL CAT ST DEP ##
Test line Techy Inn Val NJ FT FT
"""
data=sample.splitlines()[1:]
def fields(header,line):
previndex=0
prevchar=''
for index,char in enumerate(header):
if char == '#' or (prevchar != char and prevchar == ' '):
if previndex or header[0] != ' ':
yield line[previndex:index]
previndex=index
prevchar = char
yield line[previndex:]
header,dataline = data
print list(fields(header,dataline))
Выход
['Techy Inn ', 'Val ', 'NJ ', 'FT ', 'F', 'T']
Одно практическое использование этого - использовать при анализе данных фиксированной длины поля, не зная длины, просто поместив копию dataline со всеми полями и без комментариев, и пробелы заменяются чем-то другим, как "_", и значения полей одного символа заменены на..
Заголовок строки с образцом:
' Techy_Inn Val NJ FT ##'
Адаптировано из ответа Джона Мачина
>>> header = "# # # #"
>>> row = "Techy Inn Val NJ"
>>> posns = [i for i, c in enumerate(header) if c == '#']
>>> [row[slice(*x)] for x in zip(posns, posns[1:]+[None])]
['Techy ', 'Inn ', 'Val ', 'NJ']
Вы также можете написать последнюю строку, подобную этой
>>> [row[i:j] for i,j in zip(posns, posns[1:]+[None])]
В другом примере, который вы дадите в комментариях, вам просто нужно иметь правильный заголовок
>>> header = "# # # #"
>>> row = "Techiyi Iniin Viial NiiJ"
>>> posns = [i for i, c in enumerate(header) if c == '#']
>>> [row[slice(*x)] for x in zip(posns, posns[1:]+[None])]
['Techiyi ', 'Iniin ', 'Viial ', 'NiiJ']
>>>
def parse(your_file):
first_line = your_file.next().rstrip()
slices = []
start = None
for e, c in enumerate(first_line):
if c != '#':
continue
if start is None:
start = e
continue
slices.append(slice(start, e))
start = e
if start is not None:
slices.append(slice(start, None))
for line in your_file:
parsed = [line[s] for s in slices]
yield parsed
Как насчет этого?
with open('somefile','r') as source:
line= source.next()
sizes= map( len, line.split("#") )[1:]
positions = [ (sum(sizes[:x]),sum(sizes[:x+1])) for x in xrange(len(sizes)) ]
for line in source:
fields = [ line[start,end] for start,end in positions ]
Это то, что вы ищете?
f = open('sample.txt', 'r')
pos = [m.span() for m in re.finditer('#\s*', f.next())]
pos[-1] = (pos[-1][0], None)
for line in f:
print [line[i:j].strip() for i, j in pos]
f.close()
#
(предположительно, часть данных), найденные после первой строки, приведет к добавлению предположительно ложной записи кpositions
.