2010-07-27 1 views
46

У меня есть текстовый файл, который содержит метку времени на каждой строке. Моя цель - найти временной диапазон. Все время в порядке, поэтому первая строка будет самой ранней, а последняя строка будет в последний раз. Мне нужна только первая и последняя строка. Что было бы самым эффективным способом получить эти строки в python?Каков наиболее эффективный способ получить первую и последнюю строку текстового файла?

Примечание: Эти файлы относительно большие по длине, около 1-2 миллионов строк каждый, и я должен сделать это для нескольких сотен файлов.

ответ

49

docs for io module

with open(fname, 'rb') as fh: 
    first = next(fh).decode() 

    fh.seek(-1024, 2) 
    last = fh.readlines()[-1].decode() 

переменное значение здесь 1024: она представляет собой среднюю длину строки. Например, я выбираю 1024. Если у вас есть средняя длина строки, вы можете просто использовать это значение раз. 2.

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

for line in fh: 
    pass 
last = line 

Вам не нужно беспокоиться о бинарном флаге, который вы могли бы использовать только open(fname).

ETA: Поскольку у вас есть много файлов для работы, вы можете создать образец пару десятков файлов с помощью random.sample и запустить этот код на них, чтобы определить длину последней строки. При априорном большом значении сдвига позиции (скажем, 1 МБ). Это поможет вам оценить значение для полного запуска.

+0

Пока длина линий не превышает 1024 символов. – FogleBird

+0

Нет гарантий, что строки не длиннее 1024 символов, может быть какой-то другой мусор, кроме временных меток на линии. – pasbino

+0

@pasbino: у вас есть * некоторые * верхняя граница? – SilentGhost

8

Можете ли вы использовать команды unix? Я думаю, что использование head -1 и tail -n 1, вероятно, являются наиболее эффективными методами. В качестве альтернативы вы можете использовать простой fid.readline(), чтобы получить первую строку и fid.readlines()[-1], но это может занять слишком много памяти.

+0

Хм создавал бы подпроцесс для выполнения этих команд, чтобы быть наиболее эффективным способом? – pasbino

+9

Если у вас есть unix, тогда 'os.popen (" tail -n 1% s "% filename) .read()' получает последнюю строку красиво. –

+1

+1 для головы -1 и хвоста -1. fid.readlines() [- 1] не является хорошим решением для огромных файлов. –

0

Получение первой линии тривиально легко. Для последней строки, предполагая, что вы знаете приблизительную верхнюю границу длины линии, os.lseek некоторое количество от SEEK_END, найдите окончание второй и последней строки, а затем readline() последней строки.

+0

У меня нет приблизительной верхней границы длины строки – pasbino

22

Вот измененная версия ответа SilentGhost, которая будет делать то, что вы хотите.

with open(fname, 'rb') as fh: 
    first = next(fh) 
    offs = -100 
    while True: 
     fh.seek(offs, 2) 
     lines = fh.readlines() 
     if len(lines)>1: 
      last = lines[-1] 
      break 
     offs *= 2 
    print first 
    print last 

Не требуется верхняя граница для длины линии здесь.

50

Вы можете открыть файл для чтения и прочитать первую строку с использованием встроенного readline(), а затем найти конец файла и шаг назад, пока не найдете строку EOL и прочитайте последнюю строку оттуда.

with open(file, "rb") as f: 
    first = f.readline()  # Read the first line. 
    f.seek(-2, os.SEEK_END)  # Jump to the second last byte. 
    while f.read(1) != b"\n": # Until EOL is found... 
     f.seek(-2, os.SEEK_CUR) # ...jump back the read byte plus one more. 
    last = f.readline()   # Read last line. 

Переход к второму последнему байту вместо последнего предотвращает возврат непосредственно из-за конечного EOL. В то время как вы отступаете назад, вам также нужно выполнить два байта, так как чтение и проверка EOL подталкивает позицию вперед на один шаг.

При использовании seek формат: fseek(offset, whence=0), где whence означает, к чему относится смещение.Цитата docs.python.org:

  • SEEK_SET or 0 = seek from the start of the stream (the default); offset must either be a number returned by TextIOBase.tell() , or zero. Any other offset value produces undefined behaviour.
  • SEEK_CUR or 1 = “seek” to the current position; offset must be zero, which is a no-operation (all other values are unsupported).
  • SEEK_END or 2 = seek to the end of the stream; offset must be zero (all other values are unsupported).

Запуск через раз timeit 10k на файл с 6k линий на общую сумму 200Кб дал мне 1.62s против 6.92s, когда по сравнению с для петли под этим было предложено ранее. Используя файл размером 1,3 ГБ, все еще с 6-килограммовыми линиями, сто раз приводил к 8,93 против 86,95.

with open(file, "rb") as f: 
    first = f.readline()  # Read the first line. 
    for last in f: pass  # Loop through the whole file reading it all. 
+2

Это наиболее сжатое решение , и мне нравится это. Приятная часть того, чтобы не догадываться о блоках, заключается в том, что она хорошо работает с небольшими тестовыми файлами. Я добавил несколько строк и завернул его в функцию, которую я с любовью называю 'tail_n'. – MarkHu

+1

Я люблю его на бумаге, но не могу заставить его работать. 'Файл 'mapper1.2.2.py", строка 17, в get_last_line f.seek (-2, 2) IOError: [Errno 22] Недопустимый аргумент' –

+1

Nevermind, файл был пуст, derp. Лучший ответ в любом случае. +1 –

2

Сначала откройте файл для чтения mode.Then readlines использования() метод для чтения построчно line.All строки, хранящейся в list.Now, вы можете использовать список ломтики, чтобы получить первые и последние строки файла ,

a=open('file.txt','rb') 
    lines = a.readlines() 
    if lines: 
     first_line = lines[:1] 
     last_line = lines[-1] 
+1

Я искал именно это, мне не нужна первая и последняя строка, поэтому строки [1, -2] дают текст между заголовком и нижним колонтитулом. – guneysus

+3

Эта опция не может обрабатывать пустые файлы. – un33k

+4

И сбой для очень больших файлов – akarapatis

2
w=open(file.txt, 'r') 
print ('first line is : ',w.readline()) 
for line in w: 
    x= line 
print ('last line is : ',x) 
w.close() 

for петля проходит через линии и x получает последнюю строку на последней итерации.

+0

Это должен быть принятый ответ. Я не знаю, почему все эти беспорядки с низким уровнем io в других ответах? – GreenAsJade

+1

@GreenAsJade Я понимаю, что «возиться» - это не читать весь файл от начала до конца. Это может быть неэффективно для большого файла. – bli

1
with open("myfile.txt") as f: 
    lines = f.readlines() 
    first_row = lines[0] 
    print first_row 
    last_row = lines[-1] 
    print last_row 
+0

Можете ли вы объяснить, почему ваше решение будет лучше? – Zulu

+0

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

+0

Ну, он должен читать и обрабатывать весь файл, поэтому он кажется наименее эффективным. – rakslice

2

Это мое решение, совместимое также с Python3. Она также управлять пограничными делами, но он пропускает UTF-16 поддержку:

def tail(filepath): 
    """ 
    @author Marco Sulla ([email protected]) 
    @date May 31, 2016 
    """ 

    try: 
     filepath.is_file 
     fp = str(filepath) 
    except AttributeError: 
     fp = filepath 

    with open(fp, "rb") as f: 
     size = os.stat(fp).st_size 
     start_pos = 0 if size - 1 < 0 else size - 1 

     if start_pos != 0: 
      f.seek(start_pos) 
      char = f.read(1) 

      if char == b"\n": 
       start_pos -= 1 
       f.seek(start_pos) 

      if start_pos == 0: 
       f.seek(start_pos) 
      else: 
       char = "" 

       for pos in range(start_pos, -1, -1): 
        f.seek(pos) 

        char = f.read(1) 

        if char == b"\n": 
         break 

     return f.readline() 

Это ispired по Trasp's answer и AnotherParker's comment.

0

Это расширение ответа @ Trasp, которое имеет дополнительную логику для обработки углового случая файла, который имеет только одну строку. Может быть полезно обработать этот случай, если вы повторно хотите прочитать последнюю строку файла, который постоянно обновляется. Без этого, если вы попытаетесь захватить последнюю строку файла, который только что был создан и имеет только одну строку, будет поднят IOError: [Errno 22] Invalid argument.

def tail(filepath): 
    with open(filepath, "rb") as f: 
     first = f.readline()  # Read the first line. 
     f.seek(-2, 2)    # Jump to the second last byte. 
     while f.read(1) != b"\n": # Until EOL is found... 
      try: 
       f.seek(-2, 1)  # ...jump back the read byte plus one more. 
      except IOError: 
       f.seek(-1, 1) 
       if f.tell() == 0: 
        break 
     last = f.readline()  # Read last line. 
    return last 
Смежные вопросы