1

我有一个这样布局的文本文件:

1   a, b, c
2   c, b, c
2.5 a, c

我想反转键(数字)和值(CSV)(它们由制表符分隔)来产生这个:

a   1, 2.5
b   1, 2
c   1, 2, 2.5

(请注意 2 如何不与 c 重复。)

我不需要这个确切的输出。输入中的数字是有序的,而值不是。必须对输出的键和值进行排序。

我怎样才能做到这一点?我可以访问标准的 shell 实用程序(awk、sed、grep...)和 GCC。如果需要,我可能可以获取其他语言的编译器/解释器。

4

6 回答 6

3

如果你有python(如果你在linux上你可能已经有)我会使用一个简短的python脚本来做到这一点。请注意,我们使用集合来过滤掉“双重”项目。

编辑为更接近请求者的要求:

import csv
from decimal import * 
getcontext().prec = 7

csv_reader = csv.reader(open('test.csv'), delimiter='\t')

maindict = {}
for row in csv_reader:
    value = row[0]
    for key in row[1:]:
        try:
            maindict[key].add(Decimal(value))
        except KeyError:
            maindict[key] = set()
        maindict[key].add(Decimal(value))

csv_writer = csv.writer(open('out.csv', 'w'), delimiter='\t')

sorted_keys = [x[1] for x in sorted([(x.lower(), x) for x in maindict.keys()])]
for key in sorted_keys:
    csv_writer.writerow([key] + sorted(maindict[key]))
于 2009-03-22T23:32:50.157 回答
1

如果您可以使用 perl,我会尝试。一次循环输入一行。拆分选项卡上的行,然后拆分逗号上的右侧部分。将值推入一个关联数组,其中字母作为键,值是另一个关联数组。第二个关联数组将扮演集合的一部分,以消除重复。

读取输入文件后,根据关联数组的键进行排序,循环并输出结果。

于 2009-03-22T22:41:43.650 回答
1

这是php中的一个小实用程序:

// load and parse the input file
$data = file("path/to/file/");
foreach ($data as $line) {
    list($num, $values) = explode("\t", $line);
    $newData["$num"] = explode(", ", trim($values));
}
unset($data);

// reverse the index/value association
foreach ($newData as $index => $values) {
    asort($values);
    foreach($values as $value) {
        if (!isset($data[$value]))
            $data[$value] = array();
        if (!in_array($index, $data[$value]))
            array_push($data[$value], $index);
    }
}

// printout the result
foreach ($data as $index => $values) {
    echo "$index\t" . implode(", ", $values) . "\n";
}   

没有真正优化或好看,但它的工作原理......

于 2009-03-22T23:17:04.097 回答
0

这是一个 awk(1) 和 sort(1) 的答案:

您的数据基本上是一个多对多数据集,因此第一步是使用每行一个键和一个值对数据进行规范化。我们还将交换键和值以指示新的主字段,但这并不是绝对必要的,因为较低的部分不依赖于顺序。我们使用制表符或 [spaces],[spaces] 作为字段分隔符,因此我们在键和值之间以及值之间拆分制表符。这将在值中嵌入空格,但从前后修剪它们:

awk -F '\t| *, *' '{ for (i=2; i<=NF; ++i) { print $i"\t"$1 } }'

然后我们要应用您的排序顺序并消除重复项。我们使用 bash 功能将制表符指定为分隔符 (-t $'\t')。如果您使用的是 Bourne/POSIX shell,则需要使用 '[tab]',其中 [tab] 是文字选项卡:

sort -t $'\t' -u -k 1f,1 -k 2n

然后,把它放回你想要的形式:

awk -F '\t' '{ 
    if (key != $1) { 
        if (key) printf "\n";
        key=$1;
        printf "%s\t%s", $1, $2
    } else {
        printf ", %s", $2
    }
  }
  END {printf "\n"}'

将它们完全通过管道传输,您应该获得所需的输出。我使用 GNU 工具进行了测试。

于 2009-03-23T04:49:39.180 回答
0
# use Modern::Perl;
use strict;
use warnings;
use feature qw'say';


our %data;

while(<>){
  chomp;
  my($number,$csv) = split /\t/;
  my @csv = split m"\s*,\s*", $csv;
  push @{$data{$_}}, $number for @csv;
}

for my $number (sort keys %data){
  my @unique = sort keys %{{ map { ($_,undef) } @{$data{$number}} }};
  say $number, "\t", join ', ', @unique;
}
于 2009-03-23T05:14:27.513 回答
0

这是一个使用 CPAN 的 Text::CSV 模块而不是手动解析 CSV 字段的示例:

use strict;
use warnings;
use Text::CSV;

my %hash;
my $csv = Text::CSV->new({ allow_whitespace => 1 });

open my $file, "<", "file/to/read.txt";

while(<$file>) {
  my ($first, $rest) = split /\t/, $_, 2;
  my @values;

  if($csv->parse($rest)) {
    @values = $csv->fields()
  } else {
    warn "Error: invalid CSV: $rest";
    next;
  }

  foreach(@values) {
    push @{ $hash{$_} }, $first;
  }
}

# this can be shortened, but I don't remember whether sort()
# defaults to <=> or cmp, so I was explicit
foreach(sort { $a cmp $b } keys %hash) {
  print "$_\t", join(",", sort { $a <=> $b } @{ $hash{$_} }), "\n";
}

请注意,它将打印到标准输出。我建议只重定向标准输出,如果你扩展这个程序,确保使用它warn()来打印任何错误,而不是仅仅print()ing 它们。此外,它不会检查重复条目,但我不想让我的代码看起来像 Brad Gilbert 的,即使对于 Perlite 来说也有点古怪。

于 2009-03-23T05:18:11.497 回答