web-dev-qa-db-ja.com

テキストファイルの最初と最後の行を取得する最も効率的な方法は何ですか?

各行にタイムスタンプを含むテキストファイルがあります。私の目標は、時間範囲を見つけることです。すべての時間は順番に並んでいるため、最初の行が最も早い時刻になり、最後の行が最も遅い時刻になります。最初と最後の行だけが必要です。これらの行をPythonで取得する最も効率的な方法は何ですか?

注:これらのファイルの長さは比較的大きく、それぞれ約1〜2百万行であり、これを数百のファイルに対して行う必要があります。

67
pasbino

ioモジュールのドキュメント

_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 MBとしましょう)。これは、フルランの値を推定するのに役立ちます。

57
SilentGhost

読み取り用にファイルを開き、組み込みの 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.
_

最後のバイトではなく最後から2番目のバイトにジャンプすると、末尾のEOLのために直接戻ることができなくなります。 EOLの読み取りとチェックが位置を1ステップ先に進めるため、後方にステップしているときに2バイトもステップする必要があります。

seek を使用する場合、形式は fseek(offset, whence=0) です。ここで whence は、オフセットは相対的です。 docs.python.org からの引用:

  • _SEEK_SET_ または _0_ =ストリームの先頭からシーク(デフォルト); offsetは、 TextIOBase.tell() によって返される数値、またはゼロのいずれかでなければなりません。その他のオフセット値は、未定義の動作を生成します。
  • _SEEK_CUR_ または _1_ =現在の位置に「シーク」; offsetはゼロである必要があり、これは操作なしです(他のすべての値はサポートされていません)。
  • _SEEK_END_ または _2_ =ストリームの最後までシークします。オフセットはゼロでなければなりません(他のすべての値はサポートされていません)。

合計200kBの6k行のファイルでtimeitを10k回実行すると、前に提案したforループと比較すると、1.62sと6.92sが得られました。まだ6k行の1.3GBサイズのファイルを使用すると、100回は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.
_
71
Trasp

これは、あなたが望むことをする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

ここでは、行の長さの上限は必要ありません。

23
mik01aj

UNIXコマンドを使用できますか? _head -1_と_tail -n 1_を使用するのがおそらく最も効率的な方法だと思います。または、簡単なfid.readline()を使用して最初の行とfid.readlines()[-1]を取得することもできますが、これには大量のメモリが必要になる場合があります。

9
beitar

これは私のソリューションであり、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()

Traspの答え および AnotherParkerのコメント に触発されています。

6
Marco Sulla

最初にファイルを読み取りモードで開きます。次に、readlines()メソッドを使用して、行ごとに読み取ります。リストに格納されているすべての行。リストスライスを使用して、ファイルの最初と最後の行を取得できます。

    a=open('file.txt','rb')
    lines = a.readlines()
    if lines:
        first_line = lines[:1]
        last_line = lines[-1]
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は最後の反復の最後の行を取得します。

4
VipeR

逆を使用して言及した人はいません:

f=open(file,"r")
r=reversed(f.readlines())
last_line_of_file = r.next()
2

以下に、@ Traspの回答の拡張を示します。これには、1行しかないファイルのコーナーケースを処理するための追加ロジックがあります。継続的に更新されているファイルの最後の行を繰り返し読みたい場合は、この場合に対処すると便利です。これがない場合、作成されたばかりで1行しかないファイルの最後の行を取得しようとすると、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
2
tony_tiger
with open("myfile.txt") as f:
    lines = f.readlines()
    first_row = lines[0]
    print first_row
    last_row = lines[-1]
    print last_row
2
Riccardo Volpe

最初の行を取得するのは簡単です。最後の行について、行の長さのおおよその上限を知っていると仮定して、 os.lseekSEEK_END最後から2番目の行の終わりを見つけてから、 readline() 最後の行を見つけます。

1
msw
with open(filename, "r") as f:
    first = f.readline()
    if f.read(1) == '':
        return first
    f.seek(-2, 2)  # Jump to the second last byte.
    while f.read(1) != b"\n":  # Until EOL is found...
        f.seek(-2, 1)  # ...jump back the read byte plus one more.
    last = f.readline()  # Read last line.
    return last

上記の回答は、上記の回答を修正したもので、ファイルに1行しかない場合を処理します

1
user37940