🐈

SadServers解説#12 "Saint Paul": Merge Many CSVs files

2024/12/30に公開

https://ja.wikipedia.org/wiki/セントポール_(ミネソタ州)

問題概要

シナリオ

多数の CSV ファイルを結合する

問題詳細

/home/admin/polldayregistrations_enregistjourduscrutin?????.csv ディレクトリにある338個すべてのファイルを、1つのファイル/home/admin/all.csvに結合 (マージ) してください。
結合されたファイルは、すべてのCSVファイルの内容を任意の順序で含む必要があります。
列の名前は、ヘッダーとして1行のみ存在する必要があります。

解決判定

解答をファイルall.csvに書いて、Check My Solutionボタンをクリックしてください。
解答が正解かどうか、コマンドプロンプト上で確認することも可能です。次のコマンドを実行して、以下と同じ出力が得られた場合は正解です。

$ ./agent/check.sh
OK

 

問題解決の方針

【表示する】

今回の問題は、ファイル編集です。
問題文をきちんと理解し、ファイルの編集手順を考え、コマンドを実行していきましょう。

解決の手順を表示する
  1. ファイルの値を確認しながら問題文を正確に理解する
  2. 題意を満たすようなファイルの編集手順を考える
  3. ファイルの編集手順をコマンドで実行していく

 

ヒント

一部、SadServers公式のヒントを改変しています。

ヒント1

問題文を理解するために、ファイルの先頭を覗いてみます。

~$ ls | head
README.txt
agent
polldayregistrations.zip
polldayregistrations_enregistjourduscrutin10001.csv
polldayregistrations_enregistjourduscrutin10002.csv
polldayregistrations_enregistjourduscrutin10003.csv
polldayregistrations_enregistjourduscrutin10004.csv
polldayregistrations_enregistjourduscrutin10005.csv
polldayregistrations_enregistjourduscrutin10006.csv
polldayregistrations_enregistjourduscrutin10007.csv
~$
~$ head -3 polldayregistrations_enregistjourduscrutin1000[123].csv
==> polldayregistrations_enregistjourduscrutin10001.csv <==
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs
10001,"Avalon/Avalon","1","Freshwater",10,8,1,106
10001,"Avalon/Avalon","2","Victoria",9,0,4,330

==> polldayregistrations_enregistjourduscrutin10002.csv <==
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs
10002,"Bonavista--Burin--Trinity/Bonavista--Burin--Trinity","1","Davidsville",25,0,17,199
10002,"Bonavista--Burin--Trinity/Bonavista--Burin--Trinity","2","Frederickton",25,0,0,136

==> polldayregistrations_enregistjourduscrutin10003.csv <==
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs
10003,"Coast of Bays--Central--Notre Dame/Coast of Bays--Central--Notre Dame","1","Tilting",8,0,3,153
10003,"Coast of Bays--Central--Notre Dame/Coast of Bays--Central--Notre Dame","2","Joe Batt's Arm",8,0,2,250

どうやら、ファイルには共通して以下のヘッダーが含まれているようです。
Electoral District Number/Numéro de circonscription,...,Electors/Électeurs

 
これを踏まえると、ファイルを以下のように編集して結合すればよいはずです。

ファイル 編集内容
polldayregistrations_enregistjourduscrutin10001.csv そのまま
polldayregistrations_enregistjourduscrutin10002.csv ヘッダー行を削除
polldayregistrations_enregistjourduscrutin62001.csv ヘッダー行を削除

どうすれば、上記のような操作ができるでしょうか。

ヒント2

ファイルを結合してから先頭行だった行を削除すればよいように思えますが、そうすると一つ目のファイルの先頭行まで消えてしまいます。
そのため、今回は、先に先頭行を取っておいて、上の処理をしたファイルに先頭行をくっつける作戦でいきます。

まとめると、今回は以下の流れでファイルを編集します。
1.ファイルから先頭行を取り出す
2.ファイルを結合する
3.ファイルの先頭行だった行を削除する
4.手順1と手順3で作成したファイルを結合する

ヒント3

1.ファイルから先頭行を取り出す
ファイルから先頭行を取り出すには、headコマンドを使います。

実行コマンド
~$ head -1 polldayregistrations_enregistjourduscrutin10001.csv > header
~$ cat header
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs

所望の結果が得られました。

2.ファイルを結合する
ファイルを結合するには、catコマンドを使います。

実行コマンド
~$ cat polldayregistrations_enregistjourduscrutin*.csv > body

3.ファイルの先頭行だった行を削除する
ファイルの特定の行を削除するには、sedコマンドを使います。

実行コマンド

文章には/やアクセント符号のついた文字などが含まれていて処理が大変なので、ファイルの先頭一致した行を削除してしまいましょう。

先頭一致で置換して問題ないか確認するために、"Electoral" で始まる行がbodyファイルに何種類含まれているか確認しておきます。

~$ grep Electoral body | uniq
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs

どうやら、"Electoral" で始まる行は、もともと先頭行だった行だけのようです。
 
bodyファイルから、"Electoral" で始まる行を削除しましょう。

~$ sed -i '/Electoral/d' body
~$ grep Electoral body
~$ 

bodyファイルから、"Electoral" で始まる行がなくなりました。

4.1と3のファイルを結合する
ファイルを結合するには、手順2 と同様にcatコマンドを使います。

実行コマンド
~$ cat header body > all.csv

これで、ファイルの編集は完了したはずです。
一応、軽く中身を見ておきます

実行コマンド
~$ head all.csv 
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs
10001,"Avalon/Avalon","1","Freshwater",10,8,1,106
10001,"Avalon/Avalon","2","Victoria",9,0,4,330
10001,"Avalon/Avalon","3","Victoria",13,1,4,454
10001,"Avalon/Avalon","4","Victoria",17,0,0,342
10001,"Avalon/Avalon","5","Victoria",13,3,5,358
10001,"Avalon/Avalon","6","Carbonear",6,0,8,440
10001,"Avalon/Avalon","7","Carbonear",4,1,6,302
10001,"Avalon/Avalon","8","Carbonear",9,0,1,295
10001,"Avalon/Avalon","9","Carbonear",2,2,4,364
~$ grep Electoral all.csv
Electoral District Number/Numéro de circonscription,Electoral District Name/Nom de circonscription,Polling Station Number/Numéro du bureau de scrutin,Polling Station Name/Nom du bureau de scrutin,Additions/Ajouts,Corrections/Corrections,Deletions/Suppressions,Electors/Électeurs

all.csv は、"Electoral" で始まる行が先頭で、その後はcsv形式の行が続いています。また、"Electoral" で始まる行は1行だけです。
どうやら、想定通りのファイルができていそうです。

別解

SadServersのヒントでは、awkコマンドを使う方法が紹介されています。
awkコマンドについては以下の記事で解説しています。
(記事誠意作成中です)
 
Sadserversが今回の問題で想定しているコマンドについて解説します。
そのコマンドは以下です。

awk 'FNR==1 && NR!=1 {next} {print}' polldayregistrations_*.csv > all.csv

【解説】
polldayregistrations_???.csvのファイルの各行に対して、以下の処理をしています。

  • FNR(各ファイル内の行番号)が1、かつNR(通しの行番号)が1ではない
    →現在のレコードをスキップ ({next})
  • それ以外
    →現在のレコードを出力 ({print})

よって、各ファイルへのコマンドの動作は以下となります。

  1. 最初のファイル
    • FNR==1 && NR!=1は偽となるため、{print}が実行され、最初のファイルのすべての行が出力される
  2. 2番目以降のファイル
    • 最初の行: FNR==1 && NR!=1が真となるため、{next}が実行され、最初の行(ヘッダー行)はスキップされる
    • 2行目以降: FNR==1 && NR!=1は偽となるため、{print}が実行され、2行目以降が出力される

 
「いきなり問題を解き始めても調べるばかりになってしまう…」 「やりたいことが分かっても、コマンドが分からない…」 という方は、下記の記事でLinuxのコマンドを復習してから、SadServersの問題に取り掛かってみてはいかがでしょうか。
https://zenn.dev/comf_nakamura/articles/linux_command
 

問題一覧はこちら

https://zenn.dev/comf_nakamura/articles/sadservers_sitemap

Discussion