RAMに収まらない一部のCSVファイルで作業しています。
2つのCSVファイルの構造は次のとおりです。
最初の.csv
ID | 名前 | タイムスタンプ |
---|---|---|
TVシリーズ | ステル | 年 - 月 - 日時:分:秒 |
2番目.csv
ID | 名前 | 日付 |
---|---|---|
TVシリーズ | ステル | 年月日 |
目標は、次からfirst.csv
行を選択することですsecond.csv
。
name
同じtimestamp
範囲は[date
-1、date
+1]です。
これらすべての行を繰り返した後、出力を単一の出力ファイルに結合できます。
答え1
シェルでこれがどのように可能かはわかりませんが、作成するのは難しく、後で読むのも難しいと思います。
基本的なCSV操作(列の選択/削除、行のフィルタリング)に対してGoとawkをテストしましたが、Goが高速です(時には「はるかに高速です」)。
あなたの投稿のために、私は8,640,001行と約271MBのテストファイルを作成し、2つのサンプルプロセッサを作成しました。 1つはPythonを使用し、もう1つはGoを使用して読み書きモードを利用するため、中間ストレージはありません(そして両方とも大容量ファイルの効率を向上させることができるバッファ付きIOを使用します)。
- Pythonスクリプト:約70秒間実行され、約6.5MBのメモリを使用します。
- バイナリに変換:約3.5秒程度実行され、約10MBのメモリを使用します。
しかし、まず、それが仕事をすることができますか?
基本設定
私は開発のために次の2つの小さなサンプルを作成しました。
最初の.csv
id,name,timestamp
1,foo,2000-01-01 00:00:00
2,foo,2000-01-02 00:00:00
3,foo,2000-01-03 00:00:00
4,foo,2000-01-04 00:00:00
5,foo,2000-01-05 00:00:00
6,bar,2000-02-01 00:00:00
7,bar,2000-02-02 00:00:00
8,bar,2000-02-03 00:00:00
9,bar,2000-02-04 00:00:00
2番目.csv
id,name,date
10,foo,2000-01-03
11,bar,2000-02-02
「日付-1」と「日付+1」が何を意味するのかは明確ではないので、「1日加算または減算」が必要であるとします。
これらのファイルに対してGoまたはPythonコードを実行すると、次のような結果が表示されます。
2,foo,2000-01-02 00:00:00
3,foo,2000-01-03 00:00:00
4,foo,2000-01-04 00:00:00
6,bar,2000-02-01 00:00:00
7,bar,2000-02-02 00:00:00
8,bar,2000-02-03 00:00:00
お客様の要件とコメントを解釈した結果、次のようになります。
foo 2000-01-03
そしてbar 2000-02-02
テストファイル
私は100日間、1秒間隔でfooのレコードのみを生成するテストジェネレータを作成しました。
import csv
from datetime import datetime, timedelta
dt_start = datetime(2000, 1, 1)
with open('test.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['id', 'name', 'timestamp'])
# 1 line per second for 100 days
for i in range(86400 * 100):
plus_secs = timedelta(seconds=i + 1)
writer.writerow([i + 1, 'foo', dt_start + plus_secs])
これは何ですか?テスト.csv良い:
% ll test.csv
-rw-r--r-- 1 alice bob 271M Nov 19 22:19 test.csv
% wc -l test.csv
8640001 test.csv
テストファイルをfirst.csvに接続すると、次のln -fs test.csv first.csv
コマンドを実行する準備が整いました。
Python
import csv
import sys
from datetime import datetime, timedelta
DATE_FMT = f'%Y-%m-%d'
DATETIME_FMT = f'%Y-%m-%d %H:%M:%S'
# Create lookup from second
# {name: [date-1day, date+1day]}
lookup = {}
with open('second.csv', newline='') as f:
reader = csv.reader(f)
header = next(reader)
nm_col = header.index('name')
dt_col = header.index('date')
for row in reader:
name = row[nm_col]
dt_str = row[dt_col]
dt = datetime.strptime(dt_str, DATE_FMT)
min_dt = dt - timedelta(days=1)
max_dt = dt + timedelta(days=1) # - timedelta(seconds=1)
lookup[name] = [min_dt, max_dt]
# Create on-demand writer, and iterate over first, writing when we need to
writer = csv.writer(sys.stdout)
with open('first.csv', newline='') as f:
reader = csv.reader(f)
header = next(reader)
nm_col = header.index('name')
dt_col = header.index('timestamp')
writer.writerow(header)
for row in reader:
name = row[nm_col]
if name not in lookup:
continue
dt_str = row[dt_col]
dt = datetime.strptime(dt_str, DATETIME_FMT)
min_dt = lookup[name][0]
max_dt = lookup[name][1]
if dt < min_dt or dt > max_dt:
continue
writer.writerow(row)
スクリプトを実行します。
% time python3 main.py > result.csv
python3 main.py > result.csv 69.93s user 0.40s system 98% cpu 1:11.07 total
% head -n5 result.csv
id,name,timestamp
86400,foo,2000-01-02 00:00:00
86401,foo,2000-01-02 00:00:01
86402,foo,2000-01-02 00:00:02
86403,foo,2000-01-02 00:00:03
% tail -n5 result.csv
259196,foo,2000-01-03 23:59:56
259197,foo,2000-01-03 23:59:57
259198,foo,2000-01-03 23:59:58
259199,foo,2000-01-03 23:59:59
259200,foo,2000-01-04 00:00:00 # is this right?
私の考えにはこの言葉が合うようです。照会日を中心に48時間の範囲内のコンテンツのみを記録します。私が見つけた最後の項目が何であるかよくわかりません。 4回目の瞬間からでした。それがコメントアウトされました- timedelta(seconds=1)
。
行く
package main
import (
"encoding/csv"
"io"
"os"
"time"
)
type LookupEntry struct {
oneDayBefore time.Time
oneDayAfter time.Time
}
const DATE_FMT = "2006-01-02"
const DATETIME_FMT = "2006-01-02 15:04:05"
var lookup = make(map[string]LookupEntry)
func main() {
makeLookupTable()
findMatchingEntries()
}
func makeLookupTable() {
f, _ := os.Open("second.csv")
defer f.Close()
r := csv.NewReader(f)
r.Read() // Discard header
for {
record, err := r.Read()
if err == io.EOF {
break
}
dt, _ := time.Parse(DATE_FMT, record[2])
oneDayBefore := dt.AddDate(0, 0, -1)
oneDayAfter := dt.AddDate(0, 0, 1) // .Add(-time.Millisecond * 1000)
lookup[record[1]] = LookupEntry{oneDayBefore, oneDayAfter}
}
}
func findMatchingEntries() {
f1, _ := os.Open("first.csv")
defer f1.Close()
w := csv.NewWriter(os.Stdout)
r := csv.NewReader(f1)
header, _ := r.Read()
w.Write(header)
for {
record, err := r.Read()
if err == io.EOF {
break
}
lookupEntry, ok := lookup[record[1]]
if !ok {
continue
}
dt, _ := time.Parse(DATETIME_FMT, record[2])
if dt.Before(lookupEntry.oneDayBefore) || dt.After(lookupEntry.oneDayAfter) {
continue
}
w.Write(record)
}
w.Flush()
}
テストをビルドして実行します。
% go build main.go
% time ./main > result.csv
./main > result.csv 3.53s user 0.14s system 104% cpu 3.504 total
% head -n5 result.csv
86400,foo,2000-01-02 00:00:00
86401,foo,2000-01-02 00:00:01
86402,foo,2000-01-02 00:00:02
86403,foo,2000-01-02 00:00:03
86404,foo,2000-01-02 00:00:04
% tail -n5 result.csv
259196,foo,2000-01-03 23:59:56
259197,foo,2000-01-03 23:59:57
259198,foo,2000-01-03 23:59:58
259199,foo,2000-01-03 23:59:59
259200,foo,2000-01-04 00:00:00