Usando Perl, come posso ordinare un array usando il valore di un numero all'interno di ogni elemento dell'array?
Domanda
Diciamo che ho un array, @theArr, che contiene circa 1.000 elementi come il seguente:
01 '12 16 sj.1012804p1012831.93.gz'
02 '12 16 sj.1012832p1012859.94.gz'
03 '12 16 sj.1012860p1012887.95.gz'
04 '12 16 sj.1012888p1012915.96.gz'
05 '12 16 sj.1012916p1012943.97.gz'
06 '12 16 sj.875352p875407.01.gz'
07 '12 16 sj.875408p875435.02.gz'
08 '12 16 sj.875436p875535.03.gz'
09 '12 16 sj.875536p875575.04.gz'
10 '12 16 sj.875576p875603.05.gz'
11 '12 16 sj.875604p875631.06.gz'
12 '12 16 sj.875632p875659.07.gz'
13 '12 16 sj.875660p875687.08.gz'
14 '12 16 sj.875688p875715.09.gz'
15 '12 16 sj.875716p875743.10.gz'
...
Se la mia prima serie di numeri (tra 'sj.' e 'p') fosse sempre di 6 cifre, non avrei problemi. Tuttavia, quando i numeri si sovrappongono a 7 cifre, l'ordinamento predefinito smette di funzionare poiché i numeri di 7 cifre più grandi vengono prima del numero di 6 cifre più piccolo.
C'è un modo per dire a Perl di ordinare in base a quel numero all'interno della stringa in ciascun elemento dell'array?
Soluzione
Sembra che tu abbia bisogno di una Schwartzian Transform :
#!/usr/bin/perl
use strict;
use warnings;
my @a = <DATA>;
print
map { Sembra che tu abbia bisogno di una Schwartzian Transform :
<*>->[1] } #get the original value back
sort { $a->[0] <=> $b->[0] } #sort arrayrefs numerically on the sort value
map { /sj\.(.*?)p/; [$1, Sembra che tu abbia bisogno di una Schwartzian Transform :
<*>] } #build arrayref of the sort value and orig
@a;
__DATA__
12 16 sj.1012804p1012831.93.gz
12 16 sj.1012832p1012859.94.gz
12 16 sj.1012860p1012887.95.gz
12 16 sj.1012888p1012915.96.gz
12 16 sj.1012916p1012943.97.gz
12 16 sj.875352p875407.01.gz
12 16 sj.875408p875435.02.gz
12 16 sj.875436p875535.03.gz
12 16 sj.875536p875575.04.gz
12 16 sj.875576p875603.05.gz
12 16 sj.875604p875631.06.gz
12 16 sj.875632p875659.07.gz
12 16 sj.875660p875687.08.gz
12 16 sj.875688p875715.09.gz
12 16 sj.875716p875743.10.gz
Altri suggerimenti
Puoi usare una regex per estrarre il numero da ogni riga all'interno del blocco che passi alla funzione di ordinamento:
@newArray = sort { my ($anum,$bnum); $a =~ /sj\.([0-9]+)p/; $anum = $1; $b =~ /sj\.(\d+)p/; $bnum = $1; $anum <=> $bnum } @theArr;
Tuttavia, Chas. La soluzione di Owens è migliore, poiché fa corrispondere la regex solo una volta per ogni elemento.
Ecco un esempio che li ordina in ordine crescente, supponendo che non ti interessi troppo all'efficienza:
use strict;
my @theArr = split(/\n/, <<END_SAMPLE);
12 16 sj.1012804p1012831.93.gz
12 16 sj.1012832p1012859.94.gz
12 16 sj.1012860p1012887.95.gz
12 16 sj.1012888p1012915.96.gz
12 16 sj.1012916p1012943.97.gz
12 16 sj.875352p875407.01.gz
12 16 sj.875408p875435.02.gz
12 16 sj.875436p875535.03.gz
12 16 sj.875536p875575.04.gz
12 16 sj.875576p875603.05.gz
END_SAMPLE
my @sortedArr = sort compareBySJ @theArr;
print "Before:\n".join("\n", @theArr)."\n";
print "After:\n".join("\n", @sortedArr)."\n";
sub compareBySJ {
# Capture the values to compare, against the expected format
# NOTE: This could be inefficient for large, unsorted arrays
# since you'll be matching the same strings repeatedly
my ($aVal) = $a =~ /^\d+\s+\d+\s+sj\.(\d+)p/
or die "Couldn't match against value $a";
my ($bVal) = $b =~ /^\d+\s+\d+\s+sj\.(\d+)p/
or die "Couldn't match against value $a";
# Return the numerical comparison of the values (ascending order)
return $aVal <=> $bVal;
}
Uscite:
Before:
12 16 sj.1012804p1012831.93.gz
12 16 sj.1012832p1012859.94.gz
12 16 sj.1012860p1012887.95.gz
12 16 sj.1012888p1012915.96.gz
12 16 sj.1012916p1012943.97.gz
12 16 sj.875352p875407.01.gz
12 16 sj.875408p875435.02.gz
12 16 sj.875436p875535.03.gz
12 16 sj.875536p875575.04.gz
12 16 sj.875576p875603.05.gz
After:
12 16 sj.875352p875407.01.gz
12 16 sj.875408p875435.02.gz
12 16 sj.875436p875535.03.gz
12 16 sj.875536p875575.04.gz
12 16 sj.875576p875603.05.gz
12 16 sj.1012804p1012831.93.gz
12 16 sj.1012832p1012859.94.gz
12 16 sj.1012860p1012887.95.gz
12 16 sj.1012888p1012915.96.gz
12 16 sj.1012916p1012943.97.gz
Sì. La funzione sort utilizza una funzione di confronto opzionale che verrà utilizzata per confrontare due elementi. Può assumere la forma di un blocco di codice o del nome di una funzione da chiamare.
C'è un esempio nel documento collegato che è simile a quello che vuoi fare:
# inefficiently sort by descending numeric compare using
# the first integer after the first = sign, or the
# whole record case-insensitively otherwise
@new = sort {
($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0]
||
uc($a) cmp uc($b)
} @old;