2

我正在尝试将包含 3 列 ( ATTRIBUTE_NAME,ATTRIBUTE_VALUE,ID) 的 csv 文件转换为每行为 ( ) 的平面表ID,Attribute1,Attribute2,Attribute3,....。最后提供了此类表格的示例。

Python、Perl 或 SQL 都可以。非常感谢您,我非常感谢您的时间和努力!

事实上,我的问题与这篇文章非常相似,除了在我的情况下,属性的数量非常大(~300)并且每个 ID 之间并不一致,因此对每个属性进行硬编码可能不是一个实际的解决方案。

对我来说,具有挑战性/困难的部分是:

  • 大约有 2.7 亿行输入,输入表的总大小约为 60 GB。
  • 一些单个值(字符串)中包含逗号(,),整个字符串将用双引号(")括起来,以使读者意识到这一点。例如"JPMORGAN CHASE BANK, NA, TX"在 ID=53 中。
  • ID 的属性集不同。比如整体属性的个数是8,但是ID=53、17、23分别只有7、6、5。ID=17 没有属性string_countryand string_address,所以blank/nothing在逗号后输出。

输入属性值表如下所示。在这个示例输入和输出中,我们有 3 个 ID,其属性的数量可以不同,具体取决于我们是否可以从服务器获取这些属性。

ATTRIBUTE_NAME,ATTRIBUTE_VALUE,ID
num_integer,100,53
string_country,US (United States),53
string_address,FORT WORTH,53
num_double2,546.0,53
string_acc,My BankAcc,53
string_award,SILVER,53
string_bankname,"JPMORGAN CHASE BANK, NA, TX",53
num_integer,61,17
num_double,34.32,17
num_double2,200.541,17
string_acc,Your BankAcc,17
string_award,GOLD,17
string_bankname,CHASE BANK,17
num_integer,36,23
num_double,78.0,23
string_country,CA (Canada),23
string_address,VAN COUVER,23
string_acc,Her BankAcc,23

输出表应如下所示。(列中属性的顺序不固定,可以按字母顺序排序,也可以按出现顺序排序。)

ID,num_integer,num_double,string_country,string_address,num_double2,string_acc,string_award,string_bankname
53,100,,US (United States),FORT WORTH,546.0,My BankAcc,SILVER,"JPMORGAN CHASE BANK, NA, TX"
17,61,34.32,,,200.541,Your BankAcc,GOLD,CHASE BANK
23,36,78.0,CA (Canada),VAN COUVER,,Her BankAcc,,
4

2 回答 2

2

该程序将按照您的要求进行。它期望输入文件的名称作为命令行上的参数。

更新更仔细地查看数据,我发现并非所有数据字段都可用于每个 ID。如果要按照与它们在文件中出现的顺序保持相同的顺序,这会使事情变得更加复杂。

该程序通过扫描文件并将所有输出数据累积到 hash中来工作%data。同时它构建了一个 hash %headers,用于保持每个标头在每个 ID 值的数据中出现的位置。

扫描文件后,通过查找包含两个标题信息的每对的第一个 ID 来对收集的标题进行排序。该对在完整集合中的排序顺序必须与它们在该 ID 的数据中出现的顺序相同,因此只需使用 比较两个位置值即可<=>

一旦创建了一组排序的标头,%data就会转储散列,使用散列切片访问每个 ID 的完整值列表。

更新 2现在我意识到您的数据的庞大规模,我可以看到我的第二次尝试也存在缺陷,因为它试图在输出之前将所有信息读入内存。除非你有一台拥有大约 1TB 内存的巨型机器,否则这是行不通的!

你可能会从这个版本中获得一些里程。它扫描文件两次,第一次读取数据以便可以创建和排序完整的标题名称集,然后再次读取每个 ID 的数据并输出。

如果它不适合你,请告诉我,因为我仍然可以做一些事情来提高内存效率。

use strict;
use warnings;
use 5.010;

use Text::CSV;
use Fcntl 'SEEK_SET';

my $csv = Text::CSV->new;

open my $fh, '<', $ARGV[0] or die qq{Unable to open "$ARGV[0]" for input: $!};

my %headers = ();
my $last_id;
my $header_num;
my $num_ids;

while (my $row = $csv->getline($fh)) {
  next if $. == 1;

  my ($key, $val, $id) = @$row;

  unless (defined $last_id and $id eq $last_id) {
    ++$num_ids;
    $header_num = 0;
    $last_id = $id;
    print STDERR "Processing ID $id\n";
  }

  $headers{$key}[$num_ids-1] = ++$header_num;
}

sub by_position {
  for my $id (0 .. $num_ids-1) {
    my ($posa, $posb) = map $headers{$_}[$id], our $a, our $b;
    return $posa <=> $posb if $posa and $posb;
  }
  0;
}

my @headers = sort by_position keys %headers;
%headers = ();
print STDERR "List of headers complete\n";

seek $fh, 0, SEEK_SET;
$. = 0;

$csv->combine('ID', @headers);
print $csv->string, "\n";

my %data = ();
$last_id = undef;

while () {
  my $row = $csv->getline($fh);
  next if $. == 1;

  if (not defined $row or defined $last_id and $last_id ne $row->[2]) {
    $csv->combine($last_id, @data{@headers});
    print $csv->string, "\n";
    %data = ();
  }

  last unless defined $row;
  my ($key, $val, $id) = @$row;
  $data{$key} = $val;
  $last_id = $id;
}

输出

ID,num_integer,num_double,string_country,string_address,num_double2,string_acc,string_award,string_bankname
53,100,,"US (United States)","FORT WORTH",546.0,"My BankAcc",SILVER,"JPMORGAN CHASE BANK, NA, TX"
17,61,34.32,,,200.541,"Your BankAcc",GOLD,"CHASE BANK"
23,36,78.0,"CA (Canada)","VAN COUVER",,"Her BankAcc",,
于 2013-07-11T18:54:36.657 回答
0

Text::CSV 从 CPAN使用:

#!/usr/bin/env perl

use strict;
use warnings;

# --------------------------------------

use charnames qw( :full :short   );
use English   qw( -no_match_vars );  # Avoids regex performance penalty

use Text::CSV;

my $col_csv     = Text::CSV->new();
my $id_attr_csv = Text::CSV->new({ eol=>"\n", });

$col_csv->column_names( $col_csv->getline( *DATA ));
while( my $row = $col_csv->getline_hr( *DATA )){

  # do all the keys but skip if ID
  for my $attribute ( keys %$row ){
    next if $attribute eq 'ID';

    $id_attr_csv->print( *STDOUT, [ $attribute, $row->{$attribute}, $row->{ID}, ]);

  }
}

__DATA__
ID,num_integer,num_double,string_country,string_address,num_double2,string_acc,string_award,string_bankname
53,100,,US (United States),FORT WORTH,546.0,My BankAcc,SILVER,"JPMORGAN CHASE BANK, NA, TX"
17,61,34.32,,,200.541,Your BankAcc,GOLD,CHASE BANK
23,36,78.0,CA (Canada),VAN COUVER,,Her BankAcc,,
于 2013-07-11T18:42:11.510 回答