2009-03-22 7 views
1

J'ai un fichier texte layed comme ceci:Swap paire de clés et la valeur de tableau

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

Je voudrais inverser les touches (le nombre) et les valeurs (CSV) (ils sont séparés par un caractère de tabulation) pour produire ceci:

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

(Remarquez comment 2 n'est pas dupliqués pour c)

Je ne ai pas besoin cette sortie exacte.. Les nombres dans l'entrée sont classés, tandis que les valeurs ne le sont pas. Les clés de sortie doivent être commandées, ainsi que les valeurs.

Comment est-ce que je peux faire ceci? J'ai accès aux utilitaires de shell standard (awk, sed, grep ...) et GCC. Je peux probablement prendre un compilateur/interprète pour d'autres langues si nécessaire.

Répondre

3

Si vous avez python (si vous êtes sur Linux, vous avez probablement déjà) J'utiliserais un court script python pour faire cela. Notez que nous utilisons des ensembles pour filtrer les éléments "doubles".

Edité pour être plus proche des exigences de la demande:

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])) 
+0

Très bien! Un bug, cependant: la 'valeur' ​​n'est pas classée comme un nombre. Je le veux commandé en nombre. Par exemple, je reçois ceci: 13 18 2 3 35 39 6. – strager

+0

Modifier la dernière ligne à: csv_writer.writerow ([touche] + triée ([float (x) pour x dans (maindict [clé])]) Cela devrait faire l'affaire;) – ChristopheD

+0

Je reçois cette erreur avec la mise à jour: ValueError: littéral invalide pour float():. – strager

1

Je voudrais essayer perl si c'est à votre disposition. Boucle à travers l'entrée une ligne à la fois. Diviser la ligne sur l'onglet puis la partie de la main droite sur les virgules. Shove les valeurs dans un tableau associatif avec des lettres comme les clés et la valeur étant un autre tableau associatif. Le second tableau associatif jouera la partie d'un ensemble de manière à éliminer les doublons. Une fois le fichier d'entrée lu, trier en fonction des clés du tableau associatif, faire une boucle et cracher les résultats.

+0

Je n'ai jamais codé en Perl. Pouvez-vous donner un exemple pour travailler? – strager

1

est ici un petit utilitaire en 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"; 
} 

pas vraiment optimisé ou beau, mais il fonctionne ...

+0

Fonctionne, sauf les clés qui en résultent ne sont pas triées. Le tri devrait être trivial, cependant. – strager

0

Voici un awk (1) et le tri (1) Réponse:

Vos données est essentiellement un grand nombre à plusieurs ensemble de données de sorte que le La première étape consiste à normaliser les données avec une clé et une valeur par ligne. Nous allons également échanger les clés et les valeurs pour indiquer le nouveau champ primaire, mais ce n'est pas strictement nécessaire car les parties inférieures ne dépendent pas de l'ordre. Nous utilisons un onglet ou [espaces], [espaces] comme séparateur de champ afin que nous divisons sur la tabulation entre la clé et les valeurs, et entre les valeurs. Cela laissera des espaces intégrés dans les valeurs, mais les couper avant et après:

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

Ensuite, nous voulons appliquer votre ordre de tri et d'éliminer les doublons. Nous utilisons une fonction bash pour spécifier un caractère tab comme séparateur (-t $ '\ t'). Si vous utilisez Bourne shell/POSIX, vous devez utiliser '[tab]', où [tab] est une tabulation littérale:

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

Ensuite, remettez sous la forme que vous voulez:

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"}' 

Enfilez-les complètement et vous devriez obtenir la sortie désirée. J'ai testé avec les outils GNU.

+0

Je vais essayer ça demain. Le bit "convertir les clés en minuscules" est-il nécessaire? Je veux comparer/trier un cas insensible mais toujours vouloir maintenir l'enveloppe dans le résultat final. – strager

+0

Si les clés sont distinctes, mais doivent toujours être triées sans tenir compte de la casse, modifiez la ligne de tri en "trier -u -k 1f, 1 -k 2n" et supprimez l'appel tolower dans la première invocation awk. – camh

+0

J'ai fait cette modification à ma réponse. – camh

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; 
} 
0

Voici un exemple en utilisant le module Text :: CSV de CPAN plutôt que l'analyse manuelle des champs 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"; 
} 

Notez qu'il imprime sur la sortie standard. Je recommande simplement de rediriger la sortie standard, et si vous développez ce programme du tout, assurez-vous d'utiliser warn() pour imprimer les erreurs, plutôt que de les mémoriser. En outre, il ne vérifiera pas les entrées en double, mais je ne veux pas que mon code ressemble à celui de Brad Gilbert, qui semble un peu farfelu même à un Perlite.

Questions connexes