许多CSV文件中删除的DUP(remove dups from many csv files)

2019-09-24 03:09发布

给定n CSV文件,其中它们的尺寸为100 GB,我需要删除基于以下规则和条件重复行:

  • 该CSV文件编号1.csv到n.csv,每个文件的大小大约为50MB。
  • 第一列是一个字符串键,2行被认为是DUP,如果他们的第一列是相同的。
  • 我想通过保持在以后的文件中的一个(2.csv被认为是晚于1.csv)去除的DUP

我的算法是下面,我想知道是否有一个更好的。

  • 所有文件合并成一个巨大的文件

     cat *.csv > one.csv 
  • 排序的CSV

     sort one.csv >one_sorted.csv 
  • 不知道如何在这一点上elimiate的DUP。 uniq有跳过第N场一-f标志,但在我的情况下,我想跳过所有,但第1场。

我需要最后一步(消除排序的文件的DUP)帮助。 也就是有一个更有效的算法?

Answer 1:

如果你能保持在内存中的行

如果有足够的数据,将适合在内存中, awk解决方案由史蒂夫是整齐漂亮,不管你写的sort中通过命令管道awk或简单地通过管道缦的输出awksort在shell水平。

如果你有100个吉布数据可能与3%的重复,那么你就需要能够存储100吉布的数据在内存中。 这是一个很大的主内存。 64位系统可能与虚拟内存处理它,但它很可能会相当缓慢运行。

如果密钥存放在内存

如果你不能满足足够的内存中的数据,那么今后的任务是非常困难,并且需要在文件中的至少两个扫描。 我们需要假设,亲TEM,那你至少可以适应每个键在内存中,随着时代的钥匙已经出现数的计数一起。

  1. 扫描1:读取文件。
    • 计数每个键出现在输入的次数。
    • awk ,使用icount[$1]++
  2. 扫描2:重读文件。
    • 计数的时间每个键已经出现数; ocount[$1]++
    • 如果icount[$1] == ocount[$1]然后打印行。

(这里假定可存储密钥和计数两次;将替代方案是使用icount在两次扫描(只),在扫描1递增和在扫描2递减,在打印时的值的计数递减到零。)

我可能会使用Perl这不是awk ,如果仅仅是因为它会更容易重读在Perl文件比awk


甚至没有按键合适?

那么如果你甚至无法适应键和它们的计数到内存? 然后,你正面临着一些严重的问题,尤其是因为脚本语言可能不是那样干净,只要你愿意的内存不足向您汇报。 我不会试图跨过这道坎,直到它的证明是必要的。 如果有必要,我们需要对文件集的一些统计数据就知道什么可能是可能的:

  • 创纪录的平均长度。
  • 独特的按键数量。
  • 不同的键与N-出现的每一个N = 1,2号,... 最大
  • 一个关键的长度。
  • 按键加计数的数量可以安装到内存中。

,可能有一些人......所以,正如我所说,我们不要试图从那个桥过,直到它被证明是必要的。


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
$ 

注意:如果没有技嘉规模测试!

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以正确的顺序文件名的脚本。 因为您是通过约2000.csv具有1.csv编号的文件,重要的是不要列出它们按照字母顺序。 其他答案建议ls -v *.csv使用GNU ls扩展选项。 如果它是可用的,这是最好的选择。

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()在第二循环中调用在那里保存的对称性和整洁-但也可能是相关的,当你避开在单次运行处理几百个文件。



Answer 2:

下面是使用一种方法GNU awk

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

说明:读取文件的数字排序的水珠,我们每个文件的第一列添加到一个关联数组,其值是整条生产线。 通过这种方式,这是不停重复的是发生在最新文件之一。 一旦完成,通过阵列的钥匙环和打印出来的值。 GNU awk确实通过提供排序能力asort()asorti()函数,但管道输出到sort使事情变得更容易阅读,并可能是更快,更高效。

如果您需要在第一列数值排序,你可以这样做:

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


Answer 3:

我的答案是基于史蒂夫的

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

{print $0}在AWK语句是隐含的。

从本质上讲awk只打印第一线,其$ 1包含了价值。 由于.csv文件采用反向自然顺序排列,这意味着所有有$ 1的值相同的线,只有一个在最新文件被打印出来。

注意 :如果你有在同一文件中重复这将无法正常工作(也就是说,如果你有相同的文件中相同的密钥的多个实例)



Answer 4:

关于你的分拣计划,它可能是更实际的个人文件进行排序,然后将它们合并,而不是串联,然后排序。 在使用分选的复杂sort程序可能是O(n log(n)) 如果你说的每50MB文件20万线,和2000年的文件, n将是约400万,而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程序,在开始从X读取一行,并把它放在变量L.此后,每次读取从X线如果$ 0的第一个字段不匹配L时,写出L和设置L到$ 0 但是,如果$ 0时匹配L,它集L到$ 0 在最后,写出L.



文章来源: remove dups from many csv files