从许多csv文件中删除dups

给定n个csv文件,它们的总和最大为100 GB,我需要根据以下规则和条件删除重复的行:

  • csv文件编号为1.csv到n.csv,每个文件大小约为50MB。
  • 第一列是一个string键,如果它们的第一列相同,那么2行被认为是dup。
  • 我想通过保留在稍后的文件(2.csv被认为晚于1.csv)去除dups。

我的algorithm如下,我想知道是否有更好的。

我需要帮助最后一步(消除在sorting文件dups)。 还有更有效的algorithm吗?

如果你能保持内存中的线路

如果足够的数据可以放在内存中,那么awk解决方案是非常简洁的,无论你是通过awk的pipe写入sort命令,还是通过管道输出awk来在shell级别sort

如果您有100 GiB的数据,可能有3%的重复,那么您需要能够将100 GiB的数据存储在内存中。 这是很多主要的记忆。 一个64位的系统可能会用虚拟内存来处理它,但是它可能运行得很慢。

如果钥匙适合内存

如果你无法在内存中容纳足够的数据,那么前面的任务要困难得多,而且至少需要对文件进行两次扫描。 我们需要假设,你至少可以将每个密钥放在内存中,同时计算密钥出现的次数。

  1. 扫描1:读取文件。
    • 计算每个键在输入中出现的次数。
    • awk ,使用icount[$1]++
  2. 扫描2:重新读取文件。
    • 计算每个键出现的次数; ocount[$1]++
    • 如果icount[$1] == ocount[$1] ,则打印该行。

(假设您可以存储密钥并计数两次;另一种方法是在两次扫描中都使用icount (仅),在扫描1中递增,在扫描2中递减,在计数递减到零时打印该值。

我可能会使用Perl而不是awk ,如果仅仅是因为重新读取Perl文件比使用awk更容易。


甚至没有钥匙适合?

如果你甚至不能把钥匙和他们的数字放进记忆中,那么呢? 那么你正面临着一些严重的问题,不仅仅是因为脚本语言可能不会像你想的那样干净地向你报告内存不足的情况。 直到它被证明是必要的,我不会试图穿过这座桥。 如果有必要的话,我们需要一些关于文件集的统计数据来了解可能的情况:

  • 记录的平均长度。
  • 不同的密钥的数量。
  • N = 1,2,… max中每个出现N个不同密钥的数量。
  • 一键的长度。
  • 按键的数量加上可以装入内存的计数。

而且可能还有其他一些……所以,正如我所说的,我们不要试图穿越那座桥,直到它被证明是必要的。


Perl解决方案

示例数据

 $ cat x000.csv abc,123,def abd,124,deg abe,125,deh $ cat x001.csv abc,223,xef bbd,224,xeg bbe,225,xeh $ cat x002.csv cbc,323,zef cbd,324,zeg bbe,325,zeh $ perl fixdupcsv.pl x???.csv abd,124,deg abe,125,deh abc,223,xef bbd,224,xeg cbc,323,zef cbd,324,zeg bbe,325,zeh $ 

请注意,没有GB级的测试!

fixdupcsv.pl

这使用了“倒计数”技术。

 #!/usr/bin/env perl # # Eliminate duplicate records from 100 GiB of CSV files based on key in column 1. use strict; use warnings; # Scan 1 - count occurrences of each key my %count; my @ARGS = @ARGV; # Preserve arguments for Scan 2 while (<>) { $_ =~ /^([^,]+)/; $count{$1}++; } # Scan 2 - reread the files; count down occurrences of each key. # Print when it reaches 0. @ARGV = @ARGS; # Reset arguments for Scan 2 while (<>) { $_ =~ /^([^,]+)/; $count{$1}--; print if $count{$1} == 0; } 

' while (<>) '表示法破坏了@ARGV (因此在做任何事情之前复制到@ARGS ),但这也意味着如果将@ARGV重置为原始值,则将再次运行这些文件。 在Mac OS X 10.7.5上使用Perl 5.16.0和5.10.0进行测试。

这是Perl; TMTOWTDI 。 你可以使用:

 #!/usr/bin/env perl # # Eliminate duplicate records from 100 GiB of CSV files based on key in column 1. use strict; use warnings; my %count; sub counter { my($inc) = @_; while (<>) { $_ =~ /^([^,]+)/; $count{$1} += $inc; print if $count{$1} == 0; } } my @ARGS = @ARGV; # Preserve arguments for Scan 2 counter(+1); @ARGV = @ARGS; # Reset arguments for Scan 2 counter(-1); 

也有可能压缩循环体的方法,但是我发现有什么合理清晰的,并且比起极其简单的来说更加清晰。

调用

您需要以正确的顺序显示fixdupcsv.pl脚本和文件名。 由于您的文件编号从1.csv到大约2000.csv,因此不要以字母数字顺序列出它们。 其他答案建议使用GNU ls扩展选项的ls -v *.csv 。 如果可用,那是最好的选择。

 perl fixdupcsv.pl $(ls -v *.csv) 

如果不可用,则需要对名称进行数字排序:

 perl fixdupcsv.pl $(ls *.csv | sort -t. -k1.1n) 

Awk解决方案

 awk -F, ' BEGIN { for (i = 1; i < ARGC; i++) { while ((getline < ARGV[i]) > 0) count[$1]++; close(ARGV[i]); } for (i = 1; i < ARGC; i++) { while ((getline < ARGV[i]) > 0) { count[$1]--; if (count[$1] == 0) print; } close(ARGV[i]); } }' 

这忽略了awk的先天的“读”循环,并明确地读取所有的数据(你可以用END代替BEGIN,并得到相同的结果)。 逻辑在很多方面都基于Perl逻辑。 在Mac OS X 10.7.5上测试了BSD awk和GNU awk 。 有趣的是,GNU awk坚持要求close BSD awk没有的括号。 close()调用在第一个循环中是必须的,以使第二个循环可以工作。 第二个循环中的close()调用是为了保持对称性和整洁性,但是当你在一次运行中处理几百个文件时,它们也可能是相关的。

这里有一个使用GNU awk

 awk -F, '{ array[$1]=$0 } END { for (i in array) print array[i] }' $(ls -v *.csv) 

说明:读取数字排序的文件glob,我们将每个文件的第一列添加到其值为整行的关联数组中。 这样,保留的副本就是在最新文件中发生的副本。 完成后,循环访问数组的键并打印出数值。 GNU awk确实通过asort()asorti()函数提供了排序功能,但是将输出管道sort使事情更容易阅读,并且可能更快更高效。

如果您需要在第一列上进行数字排序,则可以执行此操作:

 awk -F, '{ array[$1]=$0 } END { for (i in array) print array[i] | "sort -nk 1" }' $(ls -v *.csv) 

我的回答是基于史蒂夫的

 awk -F, '!count[$1]++' $(ls -rv *.csv) 

在awk语句中隐含了{print $0}

本质上awk打印只有$ 1包含该值的第一行。 由于.csv文件是以相反的自然顺序列出的,这意味着所有具有$ 1相同值的行只会打印最新文件中的那一行。

注意 :如果你在同一个文件中有重复的话(如果你在同一个文件中有同一个键的多个实例)

关于你的排序计划,排序单个文件然后合并它们可能更实际,而不是连接然后排序。 使用sort程序排序的复杂性可能是O(n log(n)) 。 如果你说每50MB文件有20万行和2000个文件, n将是大约4亿, n log(n) ~ 10^10 。 相反,如果分别对待R记录的F文件,则排序成本为O(F*R*log(R)) ,合并成本为O(F*R*log(R)) 。 这些成本足够高,单独的排序不一定更快,但是这个过程可以分解成方便的块,所以随着事情的发展可以更容易地检查。 这是一个小规模的例子,它假定逗号可以用作排序键的分隔符。 (包含逗号的以引号分隔的键字段对于所显示的排序来说是一个问题。)请注意, -s通知sort进行稳定的排序,并按照遇到的顺序保留具有相同排序键的行。

 for i in $(seq 1 8); do sort -t, -sk1,1 $i.csv > $i.tmp; done sort -mt, -sk1,1 [1-8].tmp > 1-8.tmp 

或者如果更谨慎可能会节省一些中间结果:

 sort -mt, -sk1,1 [1-4].tmp > 1-4.tmp sort -mt, -sk1,1 [5-8].tmp > 5-8.tmp cp 1-4.tmp 5-8.tmp /backup/storage sort -mt, -sk1,1 1-4.tmp 5-8.tmp > 1-8.tmp 

此外,分开排序,然后进行合并或合并的优点是可以轻松地将工作负载分散到多个处理器或系统中。

在对所有文件进行排序和合并之后(编译成文件X),编写awk程序非常简单,在BEGIN从X读取一行并将其放入变量L.此后,每次从X读取一行,如果$ 0的第一个字段不匹配L,则写出L并将L设置为$ 0。 但是,如果$ 0与L匹配,则将L设置为$ 0。 在结束时,它写出L.