代码之家  ›  专栏  ›  技术社区  ›  strager

交换键和数组值对

  •  1
  • strager  · 技术社区  · 17 年前

    我有一个如下布局的文本文件:

    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
    

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

    我该怎么做?我可以访问标准的shell实用程序(awk、sed、grep…)和GCC。如果需要的话,我可以为其他语言找一个编译器/解释器。

    6 回复  |  直到 17 年前
        1
  •  3
  •   ChristopheD    17 年前

    如果你有python(如果你在linux上,你可能已经有了),我会使用一个简短的python脚本来完成这项工作。请注意,我们使用集合过滤掉“double”项。

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

    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]))
    
        2
  •  1
  •   zimbu668    17 年前

    如果您可以的话,我会尝试perl。一次循环输入一行。在制表符上拆分行,然后在逗号上拆分右侧部分。将值推送到一个关联数组中,以字母作为键,该值为另一个关联数组。第二个关联数组将扮演集合的一部分,以消除重复。

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

        3
  •  1
  •   J.C. Inacio    17 年前

    下面是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";
    }   
    

    不是真的优化或好看,但它的工作。。。

        4
  •  0
  •   Brad Gilbert    17 年前
    # 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;
    }
    
        5
  •  0
  •   Chris Lutz    17 年前

    下面是一个使用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()

        6
  •  0
  •   camh    17 年前

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

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

    然后我们要应用您的排序顺序并消除重复项。我们使用bash特性指定一个tabchar作为分隔符(-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工具进行了测试。

    推荐文章