대략 크기의 텍스트 파일이 있습니다. 25GB. 두 번째 열의 값을 기준으로 중복 행을 삭제하고 싶습니다. 파일에서 중복 항목이 발견되면 열에서 해당 값을 가진 모든 행을 삭제하고 네 번째 열에서 가장 높은 값을 가진 행 하나만 유지하고 싶습니다. 파일은 CSV 형식이며 이미 정렬되어 있습니다.
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482422,45,0.18,-1
2,10482422,45,0.4,-1
2,10482423,45,0.15,-1
2,10482423,45,0.43,-1
2,10482424,45,0.18,-1
2,10482424,45,0.49,-1
2,10482425,45,0.21,-1
2,10482425,45,0.52,-1
2,10482426,45,0.27,-1
2,10482426,45,0.64,-1
2,10482427,45,0.09,-1
2,10482427,45,0.34,-1
2,10482427,45,0.73,-1
Cell_Id
위의 예에서는 다른 중복 행을 삭제하여 각각에 대해 하나의 최대 서지 값을 원합니다.
예상 출력은 다음과 같습니다.
2,10482422,45,0.4,-1
2,10482423,45,0.43,-1
2,10482424,45,0.49,-1
2,10482425,45,0.52,-1
2,10482426,45,0.64,-1
2,10482427,45,0.73,-1
답변1
입력이 이미 두 번째 열을 기준으로 그룹화/정렬된 것으로 나타나므로 이는 매우 간단하고그렇지 않다전체 데이터 세트를 메모리에 유지하고 정렬해야 하며 한 번에 두 개의 레코드만 필요합니다. 1
나는 처음에 Awk 솔루션을 생각했지만 배열과 공백이 아닌 필드 구분 기호를 처리하는 것이 서투르다는 것을 알았습니다. 그런 다음 나는 짧은 Python 프로그램을 결정했습니다.
#!/usr/bin/python3
import sys
DELIMITER = ','
def remove_duplicates(records):
prev = None
for r in records:
r = (int(r[0]), int(r[1]), int(r[2]), float(r[3]), int(r[4]))
if prev is None:
prev = r
elif r[1] != prev[1]:
yield prev
prev = r
elif r[3] > prev[3]:
prev = r
if prev is not None:
yield prev
def main():
for r in remove_duplicates(
l.rstrip('\n').rsplit(DELIMITER) for l in sys.stdin
):
print(*r, sep=',')
if __name__ == '__main__':
main()
내 시스템에서는 최대 250,000개의 레코드 또는 CPU 초당 5MB의 처리량을 갖습니다.
용법
python3 remove-duplicates.py < input.txt > output.txt
프로그램은 열 헤더를 처리할 수 없으므로 이를 제거해야 합니다.
tail -n +2 < input.txt | python3 remove-duplicates.py > output.txt
결과에 다시 추가하려면 다음을 수행하세요.
{ read -r header && printf '%s\n' "$header" && python3 remove-duplicates.py; } < input.txt > output.txt
1 이는 다른 것보다 큰 장점 중 하나입니다.월티네이터의그리고스틸드라이버 메인 메모리에 맞지 않는 데이터 세트에 대한 접근 방식.
답변2
정리를 했다면감소하는네 번째 필드의 순서를 지정하려면 연관 배열이나 해시를 사용하여 각 두 번째 필드 값의 첫 번째 항목을 간단히 가져올 수 있습니다. 예: awk -F, '!seen[$2]++' file
또는perl -F, -ne 'print $_ unless $seen{$F[1]}++'
값을 오름차순으로 지정하면 효율적인 단일 패스로 수행하는 것이 조금 더 까다로워집니다. 키 값이 변경될 때마다 이전 줄을 인쇄하면 약간의 설정으로 그렇게 할 수 있습니다.
awk -F, '
NR==1 {print; next} # print the header line
NR==2 {key=$2; next} # initialize the comparison
$2 != key {
print lastval; key = $2 # print the last (largest) value of the previous key group
}
{lastval = $0} # save the current line
END {print lastval} # clean up
' file
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.4,-1
2,10482423,45,0.43,-1
2,10482424,45,0.49,-1
2,10482425,45,0.52,-1
2,10482426,45,0.64,-1
2,10482427,45,0.73,-1
답변3
고유한 Cell_id가 너무 많지 않은 경우 Perl 연관 배열에서 이미 표시된 항목을 추적할 수 있습니다. 너무 많은 경우(그리고 Perl 스크립트의 메모리가 부족하면) C
비트 필드에 고유한 항목을 유지하는 프로그램을 작성하십시오. 여기 펄이 있습니다.
#!/usr/bin/perl -w
use strict;
my %seen = (); # key=Cell_ID, value=1
my @cols=(); # for splitting input
while( <> ) { # read STDIN
@cols = split ',',$_;
next if ( defined $seen{$cols[1]}); # skip if we already saw this Cell_Id
$seen{$cols[1]} = 1;
print;
}
내 테스트는 다음과 같습니다.
walt@bat:~(0)$ cat u.dat
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482422,45,0.18,-1
2,10482422,45,0.4,-1
2,10482423,45,0.15,-1
2,10482423,45,0.43,-1
2,10482424,45,0.18,-1
2,10482424,45,0.49,-1
2,10482425,45,0.21,-1
2,10482425,45,0.52,-1
2,10482426,45,0.27,-1
2,10482426,45,0.64,-1
2,10482427,45,0.09,-1
2,10482427,45,0.34,-1
2,10482427,45,0.73,-1
walt@bat:~(0)$ perl ./unique.pl u.dat
storm_id,Cell_id,Windspeed,Storm_Surge,-1
2,10482422,45,0.06,-1
2,10482423,45,0.15,-1
2,10482424,45,0.18,-1
2,10482425,45,0.21,-1
2,10482426,45,0.27,-1
2,10482427,45,0.09,-1