Ключ подкачки и пара значений массива - PullRequest
1 голос
/ 23 марта 2009

У меня есть текстовый файл, выложенный так:

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.)

Мне не нужен этот точный вывод. Числа на входе упорядочены, а значения - нет. Должны быть упорядочены ключи выхода и значения.

Как я могу это сделать? У меня есть доступ к стандартным утилитам оболочки (awk, sed, grep ...) и GCC. Я, возможно, могу при необходимости получить компилятор / интерпретатор для других языков.

Ответы [ 6 ]

3 голосов
/ 23 марта 2009

Если у вас есть 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]))
1 голос
/ 23 марта 2009

вот небольшая утилита в 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";
}   

не очень оптимизировано или хорошо выглядит, но работает ...

1 голос
/ 23 марта 2009

Я бы попробовал Perl, если он вам доступен. Зацикливайте входные данные по строке за раз. Разделите строку на вкладке, а затем правую часть на запятых. Положите значения в ассоциативный массив с буквами в качестве ключей, а значение является другим ассоциативным массивом. Второй ассоциативный массив будет играть роль набора, чтобы исключить дубликаты.

После прочтения входного файла выполните сортировку по ключам ассоциативного массива, выполните цикл и выплюните результаты.

0 голосов
/ 23 марта 2009

Вот пример использования модуля 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() их. Кроме того, он не будет проверять дубликаты записей, но я не хочу, чтобы мой код выглядел как код Брэда Гилберта, что выглядит немного странно даже для Perlite.

0 голосов
/ 23 марта 2009
# 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;
}
0 голосов
/ 23 марта 2009

Вот ответ awk (1) и sort (1):

Ваши данные - это набор данных «многие ко многим», поэтому первым шагом является нормализация данных с одним ключом и значением в строке. Мы также поменяем местами ключи и значения, чтобы указать новое первичное поле, но это не является строго необходимым, поскольку нижние части не зависят от порядка. Мы используем табуляцию или [пробелы], [пробелы] в качестве разделителя полей, поэтому мы разделяем на вкладке между ключом и значениями и между значениями. Это оставит пробелы встроенными в значения, но обрежет их до и после:

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

Тогда мы хотим применить ваш порядок сортировки и устранить дубликаты. Мы используем функцию bash, чтобы указать символ табуляции в качестве разделителя (-t $ '\ t'). Если вы используете оболочку Bourne / POSIX, вам нужно использовать '[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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...