2015-04-13 11 views
6

Sto cercando di ordinare elenchi di nomi con Perl con un ordine di lettera specifico per eseguire alcune funzioni speciali.
L'ordinamento funzionerebbe allo stesso modo di sort { $a cmp $b } ma con una diversa sequenza di lettere.
Per esempio, ordinare con l'ordine dei caratteri arbitrario "abdrtwsuiopqe987654" ...Ordinare le stringhe con un ordine di lettera specifico con Perl

ho cercato di fare con sort { $a myFunction $b } ma io sono newbie con Perl e non vedo come organizzare correttamente myFunction per ottenere ciò che Voglio.

  • Esiste una funzione specifica (un pacchetto) che fornisce questa funzionalità?
  • Avete un esempio di una funzione di ordinamento personalizzata che si occupa di stringhe?
  • Sai come (o in quale file sorgente) la funzione cmp è stata implementata con Perl per vedere come funziona?

risposta

9

Quello che segue è probabilmente il più veloce [1]:

sub my_compare($$) { 
    $_[0] =~ tr{abdrtwsuiopqe987654}{abcdefghijklmnopqrs}r 
     cmp 
    $_[1] =~ tr{abdrtwsuiopqe987654}{abcdefghijklmnopqrs}r 
} 

my @sorted = sort my_compare @unsorted; 

Oppure, se volete qualcosa di più dinamico, potrebbe essere la seguente il più veloce [2]:

my @syms = split //, 'abdrtwsuiopqe987654'; 
my @map; $map[ord($syms[$_])] = $_ for 0..$#syms; 

sub my_compare($$) { 
    (pack 'C*', map $map[ord($_)], unpack 'C*', $_[0]) 
     cmp 
    (pack 'C*', map $map[ord($_)], unpack 'C*', $_[1]) 
} 

my @sorted = sort my_compare @unsorted; 

Potremmo confrontare il carattere per carattere, ma quello sarà lontano più lento.

use List::Util qw(min); 

my @syms = split //, 'abdrtwsuiopqe987654'; 
my @map; $map[ord($syms[$_])] = $_ for 0..$#syms; 

sub my_compare($$) { 
    my $l0 = length($_[0]); 
    my $l1 = length($_[1]); 
    for (0..min($l0, $l1)) { 
     my $ch0 = $map[ord(substr($_[0], $_, 1))]; 
     my $ch1 = $map[ord(substr($_[1], $_, 1))]; 
     return -1 if $ch0 < $ch1; 
     return +1 if $ch0 > $ch1; 
    } 

    return -1 if $l0 < $l1; 
    return +1 if $l0 > $l1; 
    return 0; 
} 

my @sorted = sort my_compare @unsorted; 

  1. Tecnicamente, può essere reso più veloce utilizzando GRT.

    my @sorted = 
        map /\0(.*)/s, 
        sort 
        map { tr{abdrtwsuiopqe987654}{abcdefghijklmnopqrs}r . "\0" . $_ } 
        @unsorted; 
    
  2. Tecnicamente, può essere reso più veloce utilizzando GRT.

    my @sorted = 
        map /\0(.*)/s, 
        sort 
        map { (pack 'C*', map $map[ord($_)], unpack 'C*', $_) . "\0" . $_ } 
        @unsorted; 
    

cmp è attuato dall'operatore scmp.

$ perl -MO=Concise,-exec -e'$x cmp $y' 
1 <0> enter 
2 <;> nextstate(main 1 -e:1) v:{ 
3 <#> gvsv[*x] s 
4 <#> gvsv[*y] s 
5 <2> scmp[t3] vK/2 
6 <@> leave[1 ref] vKP/REFC 

L'operatore scmp è implementata dalla funzione pp_scmp in pp.c, che è in realtà solo un involucro per sv_cmp_flags in sv.c quando use locale; non è in vigore. sv_cmp_flags utilizza la funzione di libreria C memcmp o una versione sensibile UTF-8 (in base al tipo di scalare).

+0

dovrebbe probabilmente di cache quelle traslitterazioni, o almeno prendere nota del calcolo ripetuto che si verificherebbe se ci fossero duplicati. –

+0

@Hunter McMillen, non penso che i guadagni saranno così grandi, ma ho aggiunto le soluzioni GRT. – ikegami

+0

dipende dai dati. –

0
use Sort::Key qw(keysort); 
my @sorted = keysort { tr/abdrtwsuiopqe987654/abcdefghijklmnopqrs/r } @data; 

O in Perls più vecchie che non supportano la bandiera r in tr/.../.../r

my @sorted = keysort { my $key = $_; 
         $key =~ tr/abdrtwsuiopqe987654/abcdefghijklmnopqrs/; 
         $key } @data; 

È inoltre possibile creare una subroutine sorta specializzato per questo tipo di dati come segue:

use Sort::Key::Maker 'my_special_sort', 
        sub { tr/abdrtwsuiopqe987654/abcdefghijklmnopqrs/r }, 
        qw(string); 

my @sorted = my_special_sort @data; 
my @sorted2 = my_special_sort @data2;