2011-03-11 22 views
5

que tienen una gran cantidad de datos como estos¿Cómo "mapa" entre 3 valores?

type1, type2, type3 
aax, ert, ddd 
asx, eer, kkk 
xkk, fff, lll 
xxj, vtt, lle 
... 

y realmente me gustaría ser capaz de "mapa" entre ellos, por lo que puede pasar de

type1 -> type2 
type1 -> type3 
type2 -> type1 
type3 -> type1 

Ejemplo:

type1_to_type2(aax) should return ert 
type1_to_type3(asx) should return kkk 
type2_to_type3(fff) should return lll 
type3_to_type1(lle) should return xxj 

¿Qué estructura de datos se debe usar para los datos?

¿Y cómo serían esas funciones?

Actualización: Todos los datos son únicos.

Respuesta

3

Una versión que realmente implementa las funciones 'type1_to_type2' etc ...

#!/usr/bin/perl 

use strict; 
use warnings; 

my $data; 
while (<DATA>) { 
    chomp; 
    push @$data, [ split ]; 
} 

sub typeX_to_typeY { 
    my ($x, $y, $str) = @_; 

    foreach (@$data) { 
    if ($_->[$x - 1] eq $str) { 
     return $_->[$y - 1]; 
    } 
    } 

    return; 
} 

sub type1_to_type2 { typeX_to_typeY(1, 2, @_) } 
sub type1_to_type3 { typeX_to_typeY(1, 3, @_) } 
sub type2_to_type1 { typeX_to_typeY(2, 1, @_) } 
sub type2_to_type3 { typeX_to_typeY(2, 3, @_) } 
sub type3_to_type1 { typeX_to_typeY(3, 1, @_) } 
sub type3_to_type2 { typeX_to_typeY(3, 2, @_) } 

# tests 
use Test::More tests => 4; 

is(type1_to_type2('aax'), 'ert'); 
is(type1_to_type3('asx'), 'kkk'); 
is(type2_to_type3('fff'), 'lll'); 
is(type3_to_type1('lle'), 'xxj'); 

__DATA__ 
aax ert ddd 
asx eer kkk 
xkk fff lll 
xxj vtt lle 
3

Si todas las cuerdas son únicos, los puede utilizar como claves en un hash:

my %data = (
    aax => ["aax", "ert", "ddd"], 
    ert => ["aax", "ert", "ddd"], 
    ddd => ["aax", "ert", "ddd"], 
    asx => ["asx", "eer", "kkk"], 
    ... 
); 

sub get_value { 
    my ($s, $type) = @_; 
    return $data{$s}[$type-1]; 
} 

print get_value("aax", 2); # "ert" 
2

Utilice una matriz, que contiene:

(
    ['aax', 'ert', 'ddd'], 
    ['asx', 'eer', 'kkk'], 
    ..., 
) 

y una serie de tres hashrefs, la primera hashref que contiene:

{ 
    aax => $array[0], 
    asx => $array[1], 
    ..., 
} 

la segunda hashref que contiene:

{ 
    ert => $array[0], 
    eer => $array[1], 
    ..., 
} 

etc. (con los tres hash apuntando a las entradas en la misma matriz original). Luego, para encontrar el valor de "columna tres" correspondiente a "columna 1 = 'asx'", la búsqueda es solo $table[0]{asx}[2], que dará "kkk".

+0

Esta solución también funciona bien, incluso si los datos solo son posicionales (por columna). Por supuesto, el hecho de que no se requieran llamadas de función aquí puede recomendarlo en términos de velocidad, aunque no estoy seguro de eso. –

2

Si todas las cadenas son globalmente únicas, la solución 'eugene' funcionará bien.

Si las cadenas no son globalmente únicas, al menos deberán ser posicionales únicas para que su pregunta tenga sentido (a menos que, por supuesto, se permitan múltiples respuestas); es decir, los valores deben ser únicos por columna.

Si este es el caso, entonces usted puede utilizar la solución de Eugene y agrega el número de columna de la clave hash, como en:

my %data = (
    aax1 => ["aax", "ert", "ddd"], 
    ert2 => ["aax", "ert", "ddd"], 
    ddd3 => ["aax", "ert", "ddd"], 
    asx1 => ["asx", "eer", "kkk"], 
    ... 
); 

sub get_value { 
    my ($string, $from_type, $to_type) = @_; 
    return $data{$string . $from_type }[ $to_type - 1 ]; 
} 

print get_value("aax", 1, 2); # "ert" 
+2

Tenga cuidado con las teclas hash de concatenación. Casi siempre es mejor agregar otra capa a su estructura de datos en lugar de hacer esto. La velocidad es solo una preocupación. El otro gran problema es con la corrección. Expanda un poco nuestro conjunto de datos y considere: ¿'foo11' 'foo1' en la columna uno o 'foo' en la columna 11? Al mantener separadas las claves por separado, se evita la molestia de analizar y construir claves, y también se elimina la carga de elegir una codificación clave segura y sin ambigüedades. – daotoad

3

Un método consiste en utilizar una base de datos para este tipo de cosa. Aquí está un ejemplo:

use strict; 
use warnings; 

use DBI; 
my $dbh = DBI->connect("dbi:SQLite:dbname=demo_db","",""); 

# Initialize an SQLite DB with some content. 
my @init_db = (
    'CREATE TABLE demo (ty1 VARCHAR(5), ty2 VARCHAR(5), ty3 VARCHAR(5));', 
    'INSERT INTO demo (ty1, ty2, ty3) values ("aax", "ert", "ddd");', 
    'INSERT INTO demo (ty1, ty2, ty3) values ("asx", "eer", "kkk");', 
    'INSERT INTO demo (ty1, ty2, ty3) values ("xkk", "fff", "lll");', 
    'INSERT INTO demo (ty1, ty2, ty3) values ("xxj", "vtt", "lle");', 
); 

for my $s (@init_db){ 
    $dbh->do($s) or die $!; 
} 

# Query the data by any field we like. 
my $sth = $dbh->prepare('SELECT * FROM demo'); 
$sth->execute(); 
my $result = $sth->fetchall_hashref('ty1'); 

El resultado es una referencia a un hash de hashes, introducido por el valor de ty1 y luego por los nombres de campo en nuestra mesa. Por ejemplo:

$result->{xkk} = { 
    'ty2' => 'fff', 
    'ty3' => 'lll', 
    'ty1' => 'xkk' 
}; 

Si está interesado en un solo valor particular de un campo determinado, puede utilizar una consulta más específica. Con este enfoque, es muy fácil escribir métodos de utilidad, por ejemplo, tomar un nombre de campo y un valor de interés, y devolver los resultados en el formato que sea más útil.

2

Comencemos imaginando el mundo que nos gustaría existir.

#! /usr/bin/env perl 

use strict; 
use warnings; 

use Convert q(
    type1, type2, type3 
    aax, ert, ddd 
    asx, eer, kkk 
    xkk, fff, lll 
    xxj, vtt, lle 
); 

Con esa porción de materia delante, debemos ser capaces de llamar a unas cuantas funciones útiles:

use Test::More; 

diag type1_to_type2("aax"); 
diag type1_to_type3("asx"); 
diag type2_to_type3("fff"); 
diag type3_to_type1("lle"); 

Los resultados deben corresponder a lo que hay en la mesa.

my @tests = (
    [ qw/ type1_to_type2 aax ert/], 
    [ qw/ type1_to_type3 asx kkk/], 
    [ qw/ type2_to_type3 fff lll/], 
    [ qw/ type3_to_type1 lle xxj/], 
    [ qw/ type2_to_type1 ert aax/], 
); 

my %sub_ok; 
for (@tests) { 
    my($name,$from,$expect) = @$_; 

    my $sub; 
    { no strict 'refs'; 
    unless ($sub_ok{$name}++) { 
     ok defined &$name, "$name defined" 
     or next; 
    } 
    $sub = \&$name; 
    } 

    is $sub->($from), $expect, "$name($from)"; 
} 

done_testing; 

Para que esto suceda, el módulo Convert necesita tomar una especificación y generar los subs apropiados.

El código en Convert.pm comienza con una repetición familiar.

According to the perlfunc documentation, use Module LIST es equivalente a

BEGIN { require Module; Module->import(LIST); } 

manera de convertir import necesidades de tomar la mesa como uno de sus argumentos. (El primero, que ignoramos, es la cadena "Convert" porque import se llama como un método de clase.)

sub import { 
    my(undef,$spec) = @_; 

    my %map; 
    my @names; 
    _populate(\%map, \@names, $spec); 

    my $pkg = caller; 
    foreach my $n1 (@names) { 
    foreach my $n2 (@names) { 
     next if $n1 eq $n2; 

     my $sub = sub { 
     my($preimage) = @_; 
     return unless exists $map{$n1}{$n2}{$preimage}; 
     $map{$n1}{$n2}{$preimage}; 
     }; 

     my $name = $pkg . "::" . $n1 . "_to_" . $n2; 
     { no strict 'refs'; *$name = $sub; } 
    } 
    } 
} 

Con _populate, se explica a continuación, construimos un hash cuyas claves son

  1. de -name
  2. a nombre
  3. imagen previa

Por ejemplo, la primera fila de datos en la memoria descriptiva (AAX, ert, ddd) corresponde a seis (= P) entradas:

  1. $map{type1}{type2}{aax} = "ert"
  2. $map{type1}{type3}{aax} = "ddd"
  3. $map{type2}{type1}{ert} = "aax"
  4. $map{type2}{type3}{ert} = "ddd"
  5. $map{type3}{type1}{ddd} = "aax"
  6. $map{type3}{type2}{ddd} = "ert"

Tener el hash, que a continuación, instalar submarinos (por ejemplo , type1_to_type2) en el espacio de nombres de la persona que llama donde cada uno busca su argumento en el espacio apropiado y devuelve el image asignado, si existe.

En _populate, tomamos los nombres de columna de la primera fila no vacía. Para las filas de datos restantes, cada par de valores entra en el mapa.

sub _populate { 
    my($map,$names,$spec) = @_; 

    my $line; 
    for (split /\s*\n\s*/, $spec) { 
    ++$line; 
    my @fields = split /\s*,\s*/; 
    next unless @fields; 

    if (@$names) { 
     my %f; 
     @f{@$names} = @fields; 
     unless (@fields == @$names) { 
     warn "$0: line $line: number of fields and columns do not match!\n"; 
     next; 
     } 

     foreach my $n1 (@$names) { 
     foreach my $n2 (@$names) { 
      next if $n1 eq $n2; 
      my($f1,$f2) = @f{$n1,$n2}; 

      my $slot = \$map->{$n1}{$n2}{$f1}; 
      warn "$0: line $line: discarding $$slot ($n1 -> $n2)\n" 
      if defined $$slot; 

      $$slot = $f2; 
     } 
     } 
    } 
    else { 
     @$names = @fields; 
    } 
    } 
} 

No olvide hacer que el módulo devuelva un valor verdadero al final.

1; 

Finalmente, la salida!

# ert 
# kkk 
# lll 
# xxj 
ok 1 - type1_to_type2 defined 
ok 2 - type1_to_type2(aax) 
ok 3 - type1_to_type3 defined 
ok 4 - type1_to_type3(asx) 
ok 5 - type2_to_type3 defined 
ok 6 - type2_to_type3(fff) 
ok 7 - type3_to_type1 defined 
ok 8 - type3_to_type1(lle) 
ok 9 - type2_to_type1 defined 
ok 10 - type2_to_type1(ert) 
1..10
Cuestiones relacionadas