制約を考慮しながら効率的にCSV分割を行います。

制約を考慮しながら効率的にCSV分割を行います。

1000大きなCSVファイルを、各小さなファイルに限られた数のレコードを持つ複数の小さなファイルに分割する必要があります。しかし、その大きなCSVファイルにどのくらいのレコードが含まれているかを事前に知ることはできません。この分割を効率的に次のようにしなければならないが、

  1. 大規模CSVに1000レコードがある場合、分割は行われません。
  2. 大きなCSVに2000のレコードがある場合 - それぞれ1000のレコードを含む2つのファイルを作成します。
  3. 大規模CSVに1200のレコードがある場合は、1000のレコードを含む1つのファイルと200のレコードを含む2番目のファイルを作成する代わりに、それぞれ600のレコードを含む2つのファイルを作成します。

このパーティショニングはできるだけ効率的でなければなりません。つまり、できるだけ少ないファイルを生成しますが、ファイルのレコード数の上限である1000個に達せずに、各ファイルのレコードをほぼ同じに保つ必要があります。

この数学方程式がシェルスクリプトでどのように見えるか疑問に思います。

calculate_number_of_files() {
    max_limit=1000
    total_records=$1

    ... math logic here
}

答え1

CSVフィールドに改行が含まれておらず(ファイルの行数がCSVレコードの数に等しい)、各ファイルにコピーする必要があるヘッダー行がないと仮定すると、次のことができます(splitここでGNUと仮定)。 ):

#! /bin/zsh -
ret=0 max=1000
for file do
  if lines=$(wc -l < $file); then
    if (( lines > max )); then
      (( nfiles = lines / max + ! ! (lines % max) ))
      (( lines_per_file = lines / nfiles + ! ! (lines % nfiles) ))
      split --verbose \
            --lines=$lines_per_file \
            --additional-suffix=.csv \
            --numeric-suffixes=1 -- $file $file:r. || ret=$?
    else
      print -ru2 - $file has $lines lines, no splitting.
    fi
  else
    ret=$?
  fi
done
exit $ret

として呼び出されthat-script foo.csv bar.csv、必要に応じて生成されますfoo.01.csv。 /を/に変更する機能foo.02.csvが追加されました--suffix-length=3(99以上の出力ファイルを許可)。0102001002

これは(整数除算、丸め)(( x = y / n + !! (y % n) ))と同じです。 〜のように(( x = ceil(y / n) ))(( x = (y + n - 1) / n ))@LSerniが表示しました動作します。

zshceil()関数には機能がありますが、zsh/mathfunc数値を整数/浮動小数点から整数/浮動小数点に変換する必要があるため、最終結果は同様の作業量です。

#! /bin/zsh -
zmodload zsh/mathfunc || exit
ret=0 max=1000

for file do
  if lines=$(wc -l < $file); then
    if (( lines > max )); then
      (( nfiles = ceil(lines * 1. / max) ))
      # lines is integer, nfiles is float
      (( lines_per_file = int(ceil(lines / nfiles)) ))
      split --verbose \
            --lines=$lines_per_file \
            --additional-suffix=.csv \
            --numeric-suffixes=1 -- $file $file:r. || ret=$?
    else
      print -ru2 - $file has $lines lines, no splitting.
    fi
  else
    ret=$?
  fi
done
exit $ret

4001ラインファイルは、例えば、801、801、801、801、797ラインに分割されるが、801、800、800、800、800ラインが好ましいかもしれないので、これは完全に最適な分割ではない。このコマンドでできる分割ではありませんsplit

答え2

ステップ1:行数を計算する(Stéphane Chazelasが提案したように)

ROWS=$( wc -l < "$FILE" )

ステップ2:分割する正しい行数を見つけます。round(TotalLines/1000)ファイルが分割されるファイルの数(2000は2で1200も同じ)。

ROWS=$( echo "scale=0;$ROWS/(($ROWS+999)/1000)" | bc )

ステップ3:split -lファイルを$ROWS次のサイズのチャンクに切り捨てるために使用されます。

split --lines "$ROWS" "$FILE"

答え3

このソリューションはどうですか?私はこの解決策をよりよくすることができると思います。

calculate_number_of_files() {
  declare maxLimit=1000
  declare numberOfRecords=3475
  declare filesNeeded=0

  if [[ $numberOfRecords -le $maxLimit ]]; then
    filesNeeded=1
  else 
    filesNeeded=$(echo $(( numberOfRecords / maxLimit )))
    filesNeeded=$(echo $(( filesNeeded + 1 )))
  fi

  echo "Number of files needed --> " $filesNeeded
  lastFile=$(echo $(( filesNeeded - 1 )))

  for ((i=0; i<$filesNeeded; i++)) do
    recordsInEachFile=$(echo $(( numberOfRecords / filesNeeded )))


    if [[ $i == $lastFile ]]; then
      recordsInEachFile=$(echo $(( numberOfRecords - (recordsInEachFile * i) )))
      echo "Number of records in file " $i " --> $recordsInEachFile";
      break
    fi

    echo "Number of records in file " $i " --> $recordsInEachFile";
  done
}

この印刷、

Number of files needed -->  4
Number of records in file  0  --> 868
Number of records in file  1  --> 868
Number of records in file  2  --> 868
Number of records in file  3  --> 871

関連情報