根据某列排序CSV?

时间:2010-11-19 01:43:55

标签: perl sorting

我确信我过去已经做过这件事了,我忘记了一些小事,但我怎样才能在某一列上对CSV文件进行排序?我对有和没有第三方Perl模块的答案感兴趣。主要是方法没有,因为我并不总是有权安装额外的模块。

示例数据:

name,25,female
name,24,male
name,27,female
name,21,male
在第二个数字列上排序后,

所需的最终结果:

name,21,male
name,24,male
name,25,female
name,27,female

7 个答案:

答案 0 :(得分:11)

由于CSV是一种非常复杂的格式,因此最好使用为我们工作的模块

以下是使用Text::CSV模块的示例:

#!/usr/bin/env perl

use strict;
use warnings;

use constant AGE => 1;

use Text::CSV;

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

my @rows;
while ( my $row_ref = $csv->getline( \*DATA ) ) {
    push @rows, $row_ref;
}

@rows = sort { $a->[AGE] <=> $b->[AGE] } @rows;

for my $row_ref (@rows) {
    $csv->combine(@$row_ref);
    print $csv->string(), "\n";
}

__DATA__
name,25,female
name,24,male
name,27,female
name,21,male

答案 1 :(得分:7)

本着总是有另一种方法去做的事情,请记住,简单的旧GNU排序可能就足够了。

$ sort -t, -k2 -n unsorted.txt
name,21,male
name,24,male
name,25,female
name,27,female

命令行参数为:

-t, # use comma as the record separator
-k2 # sort on the second key (record) in the line
-n  # sort using numerical comparison (like using <=> instead of cmp in perl)

如果您需要Perl解决方案,请将其包装在qx(); - )

答案 2 :(得分:6)

还有DBD::CSV

#!/usr/bin/perl

use strict; use warnings;
use DBI;

my $dbh = DBI->connect('dbi:CSV:', undef, undef, {
    RaiseError => 1,
    f_ext => '.csv',
    csv_tables => { test => { col_names => [qw' name age sex '] } },
});

my $sth = $dbh->prepare(q{
    SELECT name, age, sex FROM test ORDER BY age
});

$sth->execute;

while ( my @row = $sth->fetchrow_array ) {
    print join(',' => @row), "\n";
}

$sth->finish;
$dbh->disconnect;

输出:

name,21,male
name,24,male
name,25,female
name,27,female

答案 3 :(得分:3)

原始海报要求没有第三方模块(我认为CPAN没有任何意义)。虽然这种限制会严重限制您编写优质现代Perl代码的能力,但在这种情况下,可以使用(核心)Text :: ParseWords模块代替(非核心)Text :: CSV。因此,从Alan的例子中大量借鉴,我们得到:

#!/usr/bin/env perl

use strict;
use warnings;

use Text::ParseWords;

my @rows;

while (<DATA>) {
    push @rows, [ parse_line(',', 0, $_) ];
}

@rows = sort { $a->[1] <=> $b->[1] } @rows;

foreach (@rows) {
    print join ',', @$_;
}

__DATA__
name,25,female
name,24,male
name,27,female
name,21,male

答案 4 :(得分:0)

当您提供自己的比较代码时,您可以对任何内容进行排序。只需使用正则表达式提取所需的元素,或者在这种情况下可能是拆分,然后进行比较。如果你有很多元素,我会将数据解析为列表列表,然后比较代码可以访问它而无需解析。与其他行相比,这将无法一遍又一遍地解析同一行。

答案 5 :(得分:0)

使用Raku(néePerl6)

这是一个相当简单的解决方案,主要用于“手动” CSV。只要每行只有一(1)个年龄,该代码就起作用:读取行$a,梳理1至3 <digit>,并用逗号包围并分配给@b,派生排序索引$c,使用$c对行$a重新排序:

~$ raku -e 'my $a=lines();  my @b=$a.comb(/ \, <(\d**1..3)> \, /).pairs;  my $c=@b.sort(*.values)>>.keys.flat;  $a[$c.flat]>>.put;' sort_age.txt
name,21,male
name,24,male
name,25,female
name,27,female

我在OP的输入文件前添加了一些虚拟行,以查看上面的代码对1)的反应。空白的年龄字段,2)。年龄的空白字符串“”,3)。年龄为假的“ 9999”,以及4)。假的“ NA”年龄。上面的代码灾难性地失败。要解决此问题,您必须编写一个三元数,每当正则表达式与行不匹配时插入一个数字占位符值(例如零)。

下面是一个更长但更可靠的解决方案。注意-我使用999占位符值将空白/无效年龄的行移到底部:

~$ raku -e 'my @a=lines(); my @b = do for @a {if $_ ~~ m/ \, <(\d**1..3)> \, / -> { +$/ } else { 999 }; }; my $c=@b.pairs.sort(*.values)>>.keys.flat;  @a[$c.flat]>>.put;' sort_age.txt
name,21,male
name,24,male
name,25,female
name,27,female
name,,male
name,"",female
name,9999,male
name,NA,male

要反向排序,请在创建.reverse的方法链的末尾添加$c。同样,更改else占位符参数以将没有有效年龄的行移动到顶部或底部。另外,可以使用三元运算符@b来替代编写上面的my @b = do for @a {(m/ \, <(\d**1..3)> \, /) ?? +$/ !! 999 };

以下是后代的未排序输入文件:

$ cat sort_age.txt
name,,male
name,"",female
name,9999,male
name,NA,male
name,25,female
name,24,male
name,27,female
name,21,male

HTH。

https://raku.org/

答案 6 :(得分:-2)

我会做这样的事情:

#!/usr/bin/perl
use warnings;
use strict;

my @rows = map { chomp; [split /[,\s]+/, $_] } <DATA>; #read each row into an array
my @sorted = sort { $a->[1] <=> $b->[1] } @rows; # sort the rows (numerically) by second column

for (@sorted) {
  print join(', ', @$_) . "\n"; # print them out as CSV
}

__DATA__
name,25,female
name,24,male
name,27,female
name,21,male