基于重复键合并CSV行,并使用Perl Text :: CSV组合唯一值

时间:2015-02-20 11:15:36

标签: perl csv

我有一个大的制表符分隔文件,其中包含重复的产品,但颜色和数量不同。我正在尝试基于密钥合并数据,以便最终得到一个产品,并且用分隔符(在这种情况下为逗号)分隔的组合颜色和数量。

我使用Text::CSV模块以便我有更好的控制权,因为它允许我输出具有不同分隔符的文件(从分号到管道)。

我的问题是,如何正确合并数据?我不希望它只是简单地组合颜色和数量,但也删除重复的值。所以我在考虑Id / AmountId / Colour的关键/值。但是Id并不是唯一的,所以我该怎么做?我是创建数组还是使用哈希?

以下是一些示例源数据,其中制表符分隔符由分号;替换。请注意,标记的行没有Color,因此空值不会合并到结果中。

Cat_id;Cat_name;Id;Name;Amount;Colour;Bla;    
101;Fruits;50020;Strawberry;500;Red;1;
101;Fruits;50020;Strawberry;1000;Red;1;
201;Vegetables;60090;Tomato;50;Green;1;
201;Vegetables;60080;Onion;1;Purple;1;
201;Vegetables;60090;Tomato;100;Red;1;
201;Vegetables;60010;Carrot;100;Purple;1;
201;Vegetables;60050;Broccoli;500;Green;1;
201;Vegetables;60050;Broccoli;1000;Green;1;
201;Vegetables;60090;Tomato;500;Yellow;1;
101;Fruits;50060;Apple;500;Green;1;
101;Fruits;50010;Grape;500;Red;1;
201;Vegetables;60010;Carrot;500;White;1;
201;Vegetables;60050;Broccoli;2000;Green;1;
201;Vegetables;60090;Tomato;1000;Red;1;
101;Fruits;50020;Strawberry;100;Red;1;
101;Fruits;50060;Apple;1000;Red;1;
201;Vegetables;60010;Carrot;250;Yellow;1;
101;Fruits;50010;Grape;100;White;1;
101;Fruits;50030;Banana;500;Yellow;1;
201;Vegetables;60010;Carrot;1000;Yellow;1;
101;Fruits;50030;Banana;1000;Green;1;
101;Fruits;50020;Strawberry;200;Red;1;
101;Fruits;50010;Grape;200;White;1;
201;Vegetables;60010;Carrot;50;Orange;1;
201;Vegetables;60080;Onion;2;White;1;

我想要的结果是:

101;Fruits;50010;Grape;100,500,200;Red,White;1;
201;Vegetables;60090;Tomato;50,500,1000,10;Yellow,Green,Red;1;
101;Fruits;50060;Apple;500,1000;Red,Green;1;    
201;Vegetables;60010;Carrot;250,50,500,1000,100;Orange,Yellow,White,Purple;1;
201;Vegetables;60050;Broccoli;1000,500,2000;Green;1;
101;Fruits;50020;Strawberry;100,1000,200,500;Red;1;
101;Fruits;50030;Banana;500,1000;Yellow,Green;1;
201;Vegetables;60080;Onion;2,1;White,Purple;1;

到目前为止,这是我的脚本。它没有完成(并且没有工作),因为我不确定如何继续。我不认为这可以正常工作,因为我试图使用不同颜色的相同密钥。

use strict;
use warnings;
use Text::CSV;
use List::MoreUtils 'uniq';

my $inputfile  = shift || die "Give input and output names!\n";
my $outputfile = shift || die "Give output name!\n";

open my $infile,  '<', $inputfile   or die "Sourcefile in use / not found :$!\n";
open my $outfile, '>', $outputfile  or die "Outputfile in use :$!\n";

binmode($outfile, ":encoding(utf8)");

my $csv_in  = Text::CSV->new({binary => 1,sep_char => ";",eol => $/});
my $csv_out = Text::CSV->new({binary => 1,sep_char => "|",always_quote => 1,eol => $/}); #,quote_null => 0 #

my %data;

while (my $elements = $csv_in->getline($infile)){
    my $id = $elements->[2];
    push @{ $data{$id} }, \@elements;       
    }

for my $id ( sort keys %data ){
    my $set = $data{$id};
    my @elements = @{ $set->[0] };
    $elements[4] = join ',', uniq map { $_->[4] } @$set;
    $elements[5] = join ',', uniq map { $_->[5] } @$set;
    $csv_in->combine(@$elements);
    $csv_out->print($outfile, $elements);
    }   

编辑:我使用data :: dumper进行测试,但最终还是希望将其写入文件。

2 个答案:

答案 0 :(得分:0)

哈希处理唯一键。正如你所猜测的那样 - 如果你覆盖了#39;颜色,然后...旧值被替换。

但是哈希可以包含数组(ref)s。所以你可以这样做:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;
my $id = 50010;
my %hash;
$hash{$id}{'colour'} = [ "red", "green", "blue" ];
push( @{ $hash{$id}{'colour'} }, "orange" );

print Dumper \%hash;

如果你没有任何重复的颜色,这将工作。 (例如,只有一行白葡萄与该ID。)。

您可能需要使用join进行后处理,才能将数组转换为字符串。

或者作为替代方案,您可以将颜色连接到现有颜色:

if ( defined $hash->{$id}->{colour} ) {
    $hash->{$id}->{colour} .= ",$colour";
}

我还要注意 - 我不清楚你在$elements->[10]做了什么,因为没有10列。我还强烈建议使用变量的通用名称 - 比如%hash - 因为它只是一个坏习惯。模糊的变量名称是糟糕的风格,虽然它在很大程度上是学术性的,当你查看一小部分代码时,养成习惯清楚地表达你对特定变量的期望是什么。 (如果不清楚数据类型,尤其如此)

答案 1 :(得分:0)

我没有时间撰写适当的评论,但这个程序似乎可以满足您的需求。它使用List::MoreUtils模块中的uniq函数。它不是核心模块,因此可能需要安装。我相信Amounts和Colors在组合字段中出现的顺序并不重要吗?

use strict;
use warnings;

use List::MoreUtils 'uniq';

print scalar <DATA>;

my %data;
while (<DATA>) {
  chomp;
  my @fields = split /;/;
  my $id = $fields[2];
  push @{ $data{$id} }, \@fields;
}

for my $id ( sort keys %data ) {
  my $set = $data{$id};
  my @fields = @{ $set->[0] };
  $fields[4] = join ',', uniq map { $_->[4] } @$set;
  $fields[5] = join ',', uniq map { $_->[5] } @$set;
  print join(';', @fields, ''), "\n";
}

__DATA__
Cat_id;Cat_name;Id;Name;Amount;Colour;Bla;    
101;Fruits;50020;Strawberry;500;Red;1;
101;Fruits;50020;Strawberry;1000;Red;1;
201;Vegetables;60090;Tomato;50;Green;1;
201;Vegetables;60080;Onion;1;Purple;1;
201;Vegetables;60090;Tomato;100;Red;1;
201;Vegetables;60010;Carrot;100;Purple;1;
201;Vegetables;60050;Broccoli;500;Green;1;
201;Vegetables;60050;Broccoli;1000;Green;1;
201;Vegetables;60090;Tomato;500;Yellow;1;
101;Fruits;50060;Apple;500;Green;1;
101;Fruits;50010;Grape;500;Red;1;
201;Vegetables;60010;Carrot;500;White;1;
201;Vegetables;60050;Broccoli;2000;Green;1;
201;Vegetables;60090;Tomato;1000;Red;1;
101;Fruits;50020;Strawberry;100;Red;1;
101;Fruits;50060;Apple;1000;Red;1;
201;Vegetables;60010;Carrot;250;Yellow;1;
101;Fruits;50010;Grape;100;White;1;
101;Fruits;50030;Banana;500;Yellow;1;
201;Vegetables;60010;Carrot;1000;Yellow;1;
101;Fruits;50030;Banana;1000;Green;1;
101;Fruits;50020;Strawberry;200;Red;1;
101;Fruits;50010;Grape;200;White;1;
201;Vegetables;60010;Carrot;50;Orange;1;
201;Vegetables;60080;Onion;2;White;1;

<强>输出

Cat_id;Cat_name;Id;Name;Amount;Colour;Bla;    
101;Fruits;50010;Grape;500,100,200;Red,White;1;
101;Fruits;50020;Strawberry;500,1000,100,200;Red;1;
101;Fruits;50030;Banana;500,1000;Yellow,Green;1;
101;Fruits;50060;Apple;500,1000;Green,Red;1;
201;Vegetables;60010;Carrot;100,500,250,1000,50;Purple,White,Yellow,Orange;1;
201;Vegetables;60050;Broccoli;500,1000,2000;Green;1;
201;Vegetables;60080;Onion;1,2;Purple,White;1;
201;Vegetables;60090;Tomato;50,100,500,1000;Green,Red,Yellow;1;