Domanda

Ho un file di testo spiegate in questo modo:

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

desidero invertire i tasti (il numero) e valori (CSV) (che sono separati da un carattere di tabulazione) per produrre questo:

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

(Si noti come 2 non è duplicato per c.)

Non ho bisogno di questa uscita precisa. I numeri in ingresso sono ordinati, mentre i valori non sono. le chiavi del uscita devono essere ordinati, così come i valori.

Come posso fare questo? Ho accesso a utility shell standard (awk, sed, grep ...) e GCC. Posso probabilmente prendere un compilatore / interprete per le altre lingue se necessario.

È stato utile?

Soluzione

Se si dispone di python (se siete su linux probabilmente avete già) mi piacerebbe utilizzare uno script python breve per fare questo. Si noti che usiamo set a filtrare voci "doppi".

A cura per essere più vicini alle esigenze del richiedente:

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

Altri suggerimenti

Vorrei provare perl se questo è a vostra disposizione. Ciclo attraverso l'ingresso di una riga alla volta. Dividere la linea della scheda allora la parte della mano destra sulle virgole. Spingere i valori in un array associativo con lettere come le chiavi e il valore essendo altro array associativo. Il secondo array associativo ci sarà la parte di una serie in modo da eliminare i duplicati.

Dopo aver letto il file di input, sorta in base alle chiavi del array associativo, Loop Through e sputare il risultato.

ecco una piccola utility in 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";
}   

Non realmente ottimizzato o bello, ma funziona ...

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

Ecco un esempio utilizzando il modulo Text :: CSV di CPAN piuttosto che l'analisi manuale dei campi 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";
}

Si noti che verrà stampata sullo standard output. Mi raccomando solo il reindirizzamento standard output, e se si espande questo programma a tutti, assicurarsi di utilizzare warn() per stampare eventuali errori, e non solo loro print() ing. Inoltre, non sarà verificare la presenza di voci duplicate, ma io non voglio fare il mio sguardo codice come Brad Gilbert, che sembra un po 'più sporgente rispetto anche ad un Perlite.

Ecco un awk (1) e sort (1) risposta:

I tuoi dati sono fondamentalmente un molti-a-molti set di dati in modo che il primo passo è quello di normalizzare i dati con una chiave e un valore per riga. Faremo anche scambiare le chiavi e valori per indicare il nuovo campo primario, ma questo non è strettamente necessario, come le parti più in basso non dipendono ordine. Usiamo una scheda o [spazi], [spazi] come separatore di campo così abbiamo diviso nella scheda tra chiave e valori, e tra i valori. Questo lascerà spazi incorporati nei valori, ma li assetto da prima e dopo:

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

Poi vogliamo applicare il criterio di ordinamento ed eliminare i duplicati. Usiamo una funzione bash per specificare un char scheda come separatore (-t $ '\ t'). Se si utilizza shell / POSIX Bourne, è necessario utilizzare '[scheda]', dove [scheda] è una scheda letterale:

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

Poi, rimetterlo in forma che si desidera:

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

li tubo del tutto e si dovrebbe ottenere l'output desiderato. Ho provato con gli strumenti di GNU.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top