ホームページ  >  記事  >  バックエンド開発  >  Go を使用した大規模な CSV 処理

Go を使用した大規模な CSV 処理

Linda Hamilton
Linda Hamiltonオリジナル
2024-11-27 00:54:09439ブラウズ

アイデアは次のとおりです:

大規模なダミー CSV (100 万行) に顧客データのサンプルが含まれているとすると、以下の目標で処理を実行します。

  • CSV からデータを抽出します
  • データ/行数を計算します
  • 各都市の顧客数をグループ化
  • 顧客数の多い順に都市を並べ替えます
  • 処理時間を計算する
顧客のサンプル CSV はここからダウンロードできます https://github.com/datablist/sample-csv-files

データのロードと抽出

どうやら Go には CSV 処理用の標準ライブラリがあるようです。問題を解決するためにサードパーティに依存する必要がなくなりました。これは素晴らしいことです。したがって、解決策は非常に簡単です:


  // open the file to a reader interface
  c, err := os.Open("../data/customers-1000000.csv")
  if err != nil {
    log.Fatal(err)
  }
  defer c.Close()

  // load file reader into csv reader
  // Need to set FieldsPerRecord to -1 to skip fields checking
  r := csv.NewReader(c)
  r.FieldsPerRecord = -1
  r.ReuseRecord = true
  records, err := r.ReadAll()
  if err != nil {
    log.Fatal(err)
  }
    指定されたパスからファイルを開きます
  1. 開いているファイルを CSV リーダーにロードします
  2. 抽出されたすべての CSV レコード/行の値を、後で処理できるようにレコード スライスに保持します
各形式でフィールドまたは列の数が異なる可能性があるため、行のフィールドチェックをスキップしたいため、FieldsPerRecord を -1 に設定します

この状態では、CSV からすべてのデータをロードして抽出することができており、次の処理状態に進む準備ができています。また、関数 len(records) を使用すると、CSV 内の行数を知ることができます。

総顧客数を各都市にグループ化

これで、レコードを反復処理して、次のような都市名と総顧客数を含むマップを作成できるようになりました。


["Jakarta": 10, "Bandung": 200, ...]
CSV 行の都市データは 7 番目のインデックスにあり、コードは次のようになります


  // create hashmap to populate city with total customers based on the csv data rows
  // hashmap will looks like be ["city name": 100, ...]
  m := map[string]int{}
  for i, record := range records {
    // skip header row
    if i == 0 {
    continue
    }
    if _, found := m[record[6]]; found {
      m[record[6]]++
    } else {
      m[record[6]] = 1
    }
  }
都市マップが存在しない場合は、新しいマップを作成し、顧客の合計を 1 に設定します。それ以外の場合は、指定された都市の合計数を増やすだけです。

これで、都市のコレクションとその中に含まれる顧客の数を含むマップ m ができました。この時点で、都市ごとに何人の顧客をグループ化するかという問題はすでに解決しています。

最大合計顧客の並べ替え

標準ライブラリにマップを並べ替える関数があるかどうか探してみましたが、残念ながら見つかりませんでした。インデックス位置に基づいてデータの順序を並べ替えることができるため、スライスでのみソートが可能です。それでは、現在のマップからスライスを作成しましょう。


// convert to slice first for sorting purposes
dc := []CityDistribution{}
for k, v := range m {
  dc = append(dc, CityDistribution{City: k, CustomerCount: v})
}
それでは、CustomerCount を最大値から最小値までどのように並べ替えたのでしょうか?このための最も一般的なアルゴリズムは、バブルショートを使用することです。最速ではありませんが、十分な仕事はできます。

バブル ソートは、隣接する要素の順序が間違っている場合に繰り返し入れ替えることで機能する最も単純な並べ替えアルゴリズムです。このアルゴリズムは、平均および最悪の場合の時間計算量が非常に高いため、大規模なデータセットには適していません。

参考: https://www.geeksforgeeks.org/bubble-sort-algorithm/

スライスを使用して、データをループし、インデックスの次の値をチェックし、現在のデータが次のインデックスより小さい場合はそれを交換します。詳細なアルゴリズムは参考サイトで確認できます。

並べ替えプロセスは次のようになります

  // open the file to a reader interface
  c, err := os.Open("../data/customers-1000000.csv")
  if err != nil {
    log.Fatal(err)
  }
  defer c.Close()

  // load file reader into csv reader
  // Need to set FieldsPerRecord to -1 to skip fields checking
  r := csv.NewReader(c)
  r.FieldsPerRecord = -1
  r.ReuseRecord = true
  records, err := r.ReadAll()
  if err != nil {
    log.Fatal(err)
  }

ループの終わりまでに、最後のスライスからソートされたデータが得られます。

処理時間の計算

処理時間の計算は非常に簡単で、プログラムのメイン処理の実行前後のタイムスタンプを取得し、その差分を計算します。 Go では、アプローチは十分に単純である必要があります。

["Jakarta": 10, "Bandung": 200, ...]

結果

コマンドでプログラムを実行します

  // create hashmap to populate city with total customers based on the csv data rows
  // hashmap will looks like be ["city name": 100, ...]
  m := map[string]int{}
  for i, record := range records {
    // skip header row
    if i == 0 {
    continue
    }
    if _, found := m[record[6]]; found {
      m[record[6]]++
    } else {
      m[record[6]] = 1
    }
  }

出力されるのは、行数、ソートされたデータ、および処理時間です。以下はこのようなものです:

Large CSV Processing Using Go

期待通りの Go パフォーマンスで、100 万行の CSV を 1 秒以内に処理しました!

完成したコードはすべて私の Github リポジトリですでに公開されています:

https://github.com/didikz/csv-processing/tree/main/golang

学んだ教訓

  • Go での CSV 処理は標準ライブラリですでに利用可能であるため、サードパーティのライブラリを使用する必要はありません
  • データの処理は非常に簡単です。課題は、手動で行う必要があるため、データを並べ替える方法を見つけることでした

何が思い浮かびますか?

CSV を抽出したすべてのレコードをループしてマップし、ReadAll() ソースでチェックすると、指定されたファイル リーダーに基づいてスライスを作成するループも含まれているため、現在のソリューションをさらに最適化できるかもしれないと考えていました。これにより、100 万行で 100 万のデータに対して 2 つのループが生成される可能性があり、これは好ましくありません。

ファイル リーダーから直接データを読み取ることができれば、そこからマップを直接作成できるため、必要なループは 1 つだけであると考えました。ただし、レコードのスライスは他の場所で使用されますが、この場合は使用されません。

まだそれを理解する時間がありませんが、手動で行う場合のマイナス面も考えました:

  • おそらく解析プロセスのさらに多くのエラーを処理する必要があります
  • この回避策が価値があるかどうかを考えるのに、処理時間の短縮にどれだけの意味があるかわかりません

コーディングを楽しんでください!

以上がGo を使用した大規模な CSV 処理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。