Newsgroup: it.comp.os.linux.iniziare
Titolo: Ordinare numeri float elencati in un file di testo

 
Ho un file di testo "nums" che contiene N numeri decimali
uno per riga.
Devo semplicemente ordinarli dal più grande al più piccolo.
 
La soluzione che mi è venuta in mente per prima è usare
banalmente "sort":
 
sort -rg nums
 
Ok, in teoria dovrebbe funzionare e invece c'è un
problemino, guardate cosa esce fuori:
 
________________
 
$ sort -rg nums
3.45
0.88
0.4
0.3
.9
.8
.5
.3
.2
___
 
 
Ok, il problema è che il file contiene numeri
inferiori all'unità e rappresentati senza lo
zero iniziale.
 
A quanto pare (anche dalla lettura del man emerge
questo fatto) sort non è in grado di riconoscere
come numeri le righe che iniziano con il punto.
 
cioè per sort ".5" è diverso da 0.5.
 
Quindi l'idea sarebbe semplicemente di stampare
il file formattandolo correttamente e dare in
pasto a sort il numero 0.xxx.
Provando con awk:
 
__________________________________________________
 
$ awk '{printf "%3.3f\n", $1 | "sort -rg" }' nums
3.450
0.900
0.880
0.800
0.500
0.400
0.300
0.300
0.200
______
 
 
Ci sono secondo voi soluzioni migliori?
Specie parlando di prestazioni e tempi d'esecuzione?
Perchè il file nums è solo un esempio, ma nella realtà
ho da riordinare files ben più lunghi.
 
Correggetemi se sbaglio, nel comando sopra:
1- awk stampa il file in formato numerico
2- poi pssa a sort la ristampa dell'output
riordinato.
 
Questa serie di operazioni non può essere
ulteriormente semplificata per rendere tutto
più rapido ed efficiente?


Il sort  integrato di perl per esempio facendo il tutto con un unico comando
Non so se awk ne ha uno
Lavorando in memoria potrebbe essere più veloce ma comunque occorre 
monitorare
Banalmente prova con un:
 
 
sort -rg  nums | perl -ne  'printf("%3f\n",$_)'

 sort -rg  nums | perl -ne  'printf("%3f\n",$_)'
 
Non dovrebbe funzionare perchè il primo sort che
lanci, come ho detto non tratta alcuni numeri come
tali.
Ecco cosa capita:
__________________________________________________
 
$ sort -rg  nums | perl -ne  'printf("%3f\n",$_)'
3.450000
0.880000
0.400000
0.300000
0.900000
0.800000
0.500000
0.300000
0.200000
_________
 
Come vedi i numeri sopra sono ordinati bene fino al
primo "0.3", poi ripartono da "0.9"...
Non sono ordinati in ordine decrescente.
Per usare il sort della bash bisogna prima riformattare
i numeri aggiungendovi lo "0" prima del punto decimale.
Ovvero basta invertire i due comandi separati dalla pipe.
 
Riguardo al sort integrato nel "perl" facendo tutto in
un unico comando...
Mi faresti un esempio di comando in perl cui dare in pasto
il file "nums".
Del perl ho provato ad iniziare la lettura di alcuni manuali
e guide varie, ma sinceramente non mi è sembrato così semplice
e in ogni caso non lo so assolutamente maneggiare.

.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
 
Written by Mike Haertel and Paul Eggert.
% 

 ...
 
Il separatore decimale è la virgola.
 
LC_NUMERIC=C
 
--- news://
freenews.netfront.net/
 - complaints: 
ne...@netfront.net
 ---


Un piccolo esempio:
 
#!perl
use strict;
use warnings;
my @a;
while(<STDIN>) {   # riempie l' array @a leggendo riga per riga
	chop;
	next unless /^\s*$/; # scarta le righe a spazi
	next unless /^\d*[\.]{0,1}\d*$/; # scarta i non numeri
	push @a,$_;
}
 
my @b=sort { $a <=> $b} @a;   #ordina @a con risultato in @b
 
while(@b) {   # stampa @b
	printf(STDOUT "%0.3f\n",shift @b);
}
exit 0;
 
Questo programma ha lo svantaggio che dovrebbe diventare lento con 
grossi dati (non ho idea per quale limite ma posso approssimare oltre i 
100MB) in quanto i dati vengono caricati tutti in memoria diversamente 
dal comando sort che usa un piccolo buffer per l' ordinamento ed il 
resto lo scarica su file
 
 
Perl è un vero coltellino svizzero, impararlo non fa mai male
Per questo ti ho messo qualche commento e comunque non è un linguaggio 
difficile
Basta solo prenderci un po la mano
 
 
 

 LC_NUMERIC=C
 
 
Già, c'è di mezzo quella variabile...
Nel mio caso ho LC_NUMERIC vuota.
Ecco cosa accade:
 
___________________
 
$ echo $LC_NUMERIC
 
$ cat nums|sort -gr
3.45
0.88
0.4
0.3
.9
.8
.5
.3
.2
$ tr '\.' '\,' <nums | sort -gr
3,45
,9
0,88
,8
,5
0,4
0,3
,3
,2
_____
 
Effettivamente sostituendo la virgola al punto
il sort riesce senza problemi.
Se invece imposto la variaile LC_NUMERIC=C ecco
cosa otengo:
 
________________
 
$ LC_NUMERIC=C
$ sort -gr nums
3.45
.9
0.88
.8
.5
0.4
0.3
.3
.2
$ cat nums|tr '\.' '\,'|sort -gr
3,45
0,88
0,4
0,3
,9
,8
,5
,3
,2
___
 
Ovvero la situazione si ribalta.
Pertanto la soluzione (già postata) consiste nel
definire nello script la variabile:
LC_NUMERIC=C
 
In questo modo il separatore decimale è impostato
a "." (punto).
 
Vi posto un esempio scemo, in cu si vede che
la shell corrente è impostata su "it_IT.UTF-8",
ma impostando la variabile nello script, questa
induce l'interpretazione del punto come separatore
decimale. E non modifica nulla nel default della
shell da cui si lancia lo script.
 
_______________________
 
$ cat ./sort-locale.sh
#!/bin/sh
 
LC_NUMERIC=C
sort -rg $1
$ ./sort-locale.sh nums
3.45
.9
0.88
.8
.5
0.4
0.3
.3
.2
$ locale
LANG=it_IT.UTF-8
LC_CTYPE="it_IT.UTF-8"
LC_NUMERIC=it_IT.UTF-8
[...]
_______________________
 
 
 
Vorrei osservare anche due cose sulle performance.
Mi sono creato un file di 1000 righe costituito da
numeri decimali random.
E ho provato a farne il sort in tre modi diversi,
poi ne ho controllato con "time" i tempi d'esecuzione
ripetendo per tre volte il "time" sui tre comandi.
Vorrei un vostro parere sui risultati:
_______________________________________________________________________
 
$ time sort -rg rand-nums >/dev/null;
time awk '{printf "%3.3f\n", $1 | "sort -rg" }' rand-nums >/dev/null;
time nums.perl rand-nums >/dev/null
 
real    0m0.014s
user    0m0.012s
sys     0m0.002s
 
real    0m0.023s
user    0m0.023s
sys     0m0.005s
 
real    0m0.009s
user    0m0.005s
sys     0m0.003s
 
$ time sort -rg rand-nums >/dev/null;
time awk '{printf "%3.3f\n", $1 | "sort -rg" }' rand-nums >/dev/null;
time nums.perl rand-nums >/dev/null
 
real    0m0.014s
user    0m0.011s
sys     0m0.002s
 
real    0m0.023s
user    0m0.022s
sys     0m0.005s
 
real    0m0.009s
user    0m0.005s
sys     0m0.003s
 
$ time sort -rg rand-nums >/dev/null;
time awk '{printf "%3.3f\n", $1 | "sort -rg" }' rand-nums >/dev/null;
time nums.perl rand-nums >/dev/null
 
real    0m0.013s
user    0m0.012s
sys     0m0.001s
 
real    0m0.023s
user    0m0.026s
sys     0m0.002s
 
real    0m0.009s
user    0m0.007s
sys     0m0.002s
_________________
 
Cosa si può conlcudere?
1- Che awk è più lento delle altre due soluzioni perchè
deve richiamare il comando esterno "sort".
Su questo non ci piove... probabilmente c'è anche un modo
più efficiente di fare il sorting anche con awk magari
usando le funzioni asort() dopo aver caricato il file
in un array. Sinceramente non so bene come si faccia, mi
manca qualche base.
 
2- PEr quanto riguarda le altre due soluzioni, avrei
creduto che sort fosse più rapido dello script perl
che ho creato, ve lo posto:
_______________
 
$ cat nums.perl 
#!/usr/bin/perl
 
open (FILE, "< $ARGV[0]");
@sorted = sort {$b <=> $a} <FILE>;
printf "%10.3f\n" x @sorted, @sorted;
______________________________________
 
 
Devo dire la verità, il perl mi sembra piuttosto diverso
dai linguaggi che ho incontrato fin ora... meno intuitivo.
Ma penso sia questione di abitudine.
 
Ad ogni modo, come si spiega il maggiore tempo di esecuzione
di "sort" rispetto al mio scriptino abbastanza scemo immagino?
 
Aggiungo senza ulteriori report che sostituendo a
 
sort -rg
 
il comando
 
sort -rn
 
Ovvero "compare according to string numerical value"...
il comando sort risulta finalmente il più rapido dei tre.
Ma è sarebbe corretto volendo ordinare una lista di numeri
usare l'opzione "-n" di sort invece di "-g", cioè alla fine
l'ordinamento dei valori numerici sarebbe effettivamente
corretto o ne avrei qualche sorpresa?
 
 
Grazie a tutti per le risposte che avete postato fin qui e
scusate la lunghezza del post.

 Basta solo prenderci un po la mano
 
Guarda un po' cosa ne pensi del mio tentativo
molto grezzo, comunque mi sembra funzionare bene
e in modo molto efficiente in termini di tempi
d'esecuzione.
L'ho già postato nell'altro messaggio, ma te lo
ripoto anche qui:
 
________________
 
$ cat nums.perl 
#!/usr/bin/perl
 
open (FILE, "< $ARGV[0]");
@sorted = sort {$b <=> $a} <FILE>;
printf "%10.3f\n" x @sorted, @sorted;
______________________________________
 
Cosa ne dici?


E' un modo abbreviato (senza controlli) per fare la stessa cosa
Però la print è alquanto inefficiente
Viene moltiplicata la stringa di formattazione per il numero di elementi 
di sort
Meglio una classica while od un for sull' array ordinato
 
 

 Meglio una classica while od un for sull' array ordinato
 
 
Mettiamo a fuoco le due alternative di print, sempre che
io abbia capito quello che intendevi (adatto al mio esempio):
______________
 
- soluzione 1:
 
printf "%10.3f\n" x @sorted, @sorted;
_____________________________________
 
______________
 
- soluzione 2:
 
while(@sorted) {
  printf(STDOUT "%10.3f\n",shift @sorted);
}
____________________________________
 
 
Dicevi che la seconda dovrebbe essere più
efficiente della prima.
Però in entrambi i casi la stringa di formattazione
viene eseguita tante volte quanti sono gli elementi
dell'array... o no?
Cosa cambia tra le due soluzioni?
Potreste brevemente spiegarmelo?
 
Ho provato a fare qualche test con "time" per
confrontare le due alternative (sinceramente
non so se sia così attendibile... eventualmente
se avete altri suggerimenti per testare l'efficienza
di uno script dite pure).
 
_________________________________________________________________
 
$ for i in 1 2 3 4 5; do echo prova $i;time nums.perl rand-nums >
/dev/null; time nums_2.perl rand-nums >/dev/null;done 2>&1|grep
"prova\|real"
prova 1
real    0m0.008s
real    0m0.010s
prova 2
real    0m0.008s
real    0m0.011s
prova 3
real    0m0.008s
real    0m0.010s
prova 4
real    0m0.009s
real    0m0.011s
prova 5
real    0m0.008s
real    0m0.010s
________________
 
Prendiamola come "tendenza", tendenzialmente mi risulterebbe
che la soluzione 1, con moltiplicazione del formato sia più
rapida della soluzione 2, con while per il print.
Siccome ho parecchi dubbi sull'attendibilità dei miei test
e siccome soprattutto mi facevi notare l'inefficienza della
mia soluzione relativamente al print moltiplicativo, mi
farebbe davvero comodo una spiegazione in più se ti và.
 
In ogni caso grazie della disponibilità!  :)


se il compilatore è un po furbo la costruisce una volta sola essendo 
costante
Nel primo caso deve comunque fare una concatenazione e più il numero di 
elementi è grande piu concatenazioni deve fare

Fin quando gli elementi sono pochi potrebbe anche essere piu veloce
Ma dopo un certo limite non credo
Naturalmente sto andando a naso
 
 
 

 Naturalmente sto andando a naso
 
Potrei provare con un file di numeri
random più grande.
 
$ wc -l rand-nums 
1000 rand-nums
 
Nell'attuale ve ne sono solo 1000
per un totale di 11K...
...potrei crearne uno di 100'000
righe e vedere cosa succede...

 righe e vedere cosa succede...
 
 
Ecco l'esito sul file maggiorato, siamo sui 28MB
per un  totale di 2'600'000 righe (numeri):
_________________
 
$ wc -l rand-nums 
2600000 rand-nums
 
$ for i in 1 2 3 4 5; do echo prova $i;
time nums.perl rand-nums > /dev/null;
time nums_2.perl rand-nums >/dev/null;
done 2>&1|grep "prova\|real"
prova 1
real    0m9.258s
real    0m14.311s
prova 2
real    0m9.285s
real    0m14.341s
prova 3
real    0m9.274s
real    0m14.344s
prova 4
real    0m9.237s
real    0m14.208s
prova 5
real    0m9.280s
real    0m14.380s
_________________
 
Insomma per il momento sembra che la soluzione
1 sia ben più veloce della 2.
Se qualcuno fosse in grado di spiegarlo con
parole semplici, sarei curioso di capire il
perchè di questo fatto.


Credo che sia per la syscall write
Concatenare in memoria porta via meno tempo che ogni volta chiamare la 
syscall
Credo che sia l' unica spiegazione
Comunque non è tutto gratis, significa raddoppiare la memoria usata 
rispetto a quella occupata da @sort
 


 
concatenazione di stringhe:
 
in perl per concatenare una stringa si usa l' operatore punto (.)
 
Es:
 
print "a"."b"  emette ab
 
 
Poi esiste l' operatore 'x' sulle stringhe
print "a"x3  è equivalente a  print "a"."a"."a"


 print "a"x3  è equivalente a  print "a"."a"."a"
 
Ah sì sì ora mi viene in mente.
Quindi quando io dò il comando
 
printf "%10.3f\n" x @sorted, @sorted;
 
come si traduce il concatenamento?
 
Innanzitutto il primo @sorted dovrebbe
essere tradotto nel numero di elementi
del vettore... grazie al discorso del
"contesto" (già qui il Perl è un po'
diverso dai soliti linguaggi...).
 
Quindi mettiamo di avere 3 valori
nel vettore (per fare un esempio semplice):
 
printf "%10.3f\n" x 3, @sorted
 
diventerà
 
printf "%10.3f\n" . "%10.3f\n" . "10.3f\n", @sorted
 
dico bene?
Se pensi che sia utile un esempio ancora più approfondito
potresti riportare cosa viene espanso se il vettore @sorted
contenesse per esempio:
 
{ 0.3 ; 0.2 ; 0.1 }
 
 
 
 
Si vede ad occhio, come facevi notare che l'utilzzo di
memoria, di RAM è maggiore usando la concatenzione.
Perchè in memoria deve risisedere un comando di stampa
notevolmente più lungo.


Già...
Eventualmente ci sono metodi per il calcolo della memoria usata
da uno script del genere?
 


 
Infatti