제약 조건을 고려하면서 효율적으로 CSV 분할을 수행합니다.

제약 조건을 고려하면서 효율적으로 CSV 분할을 수행합니다.

1000큰 CSV 파일을 각각의 작은 파일에 제한된 수의 레코드가 있는 여러 개의 작은 파일로 분할해야 합니다. 하지만 그 큰 CSV 파일에 얼마나 많은 레코드가 들어 있는지 미리 알 수 없습니다. 이 분할을 효율적으로 다음과 같이 해야 하는데,

  1. 대규모 CSV에 1000개의 레코드가 있는 경우 분할이 수행되지 않습니다.
  2. 큰 CSV에 2000개의 레코드가 있는 경우 - 각각 1000개의 레코드를 포함하는 2개의 파일을 만듭니다.
  3. 대규모 CSV에 1200개의 레코드가 있는 경우 1000개의 레코드가 포함된 파일 1개와 200개의 레코드가 포함된 두 번째 파일을 만드는 대신 각각 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

관련 정보