PDA

View Full Version : [C#] Formattazione spazi di un testo


J0k3r91
17-11-2008, 16:56
Salve a tutti!

Sono un novellino nella programmazione C# e mi sarebbe piaciuto porgervi un mio problema che non riesco a risolvere :( :

Ho una testbox, che viene popolata da una stringa di testo che purtroppo presenta diversi spazi al suo interno: vi faccio un esempio del genere...

Questo è un programma C# . . . . . . fatto da me.

Come potete notare ci sono un sacco di spazi (li ho indicati coi puntini) tra: C# e fatto da me... :help:

Ecco, come potrei fare per rimuovere tutti quegli spazi e lasciarne così uno solo? Del tipo: Questo è un programma C# fatto da me.

Vi ringrazio anticipatamente per il vostro tempo dedicatomi :stordita:

||ElChE||88
17-11-2008, 17:03
while (stringa.Contains(" "))
stringa = stringa.Replace(" ", " ");

J0k3r91
17-11-2008, 17:09
Ti ringrazio per la risposta ||ElChE||88 ! :)

Il fatto è che usando quel codice, il programma mi entra il un loop infinito nel while sembrerebbe e, dato che la textbox la popolo nel formload, il form non viene appunto mai caricato :( .
Non saprei qualcuno ha qualche altra idea?

||ElChE||88
17-11-2008, 17:17
Ti ringrazio per la risposta ||ElChE||88 ! :)

Il fatto è che usando quel codice, il programma mi entra il un loop infinito nel while sembrerebbe e, dato che la textbox la popolo nel formload, il form non viene appunto mai caricato :( .
Non saprei qualcuno ha qualche altra idea?
Non ho ben capito quel che intendi, ma il codice andrebbe usato così:

string text = textBox1.Text;

while (text.Contains(" "))
text = text.Replace(" ", " ");

Puoi postare il tuo codice (la parte interessata)?

Vincenzo1968
17-11-2008, 17:22
Soluzione in stile C:


static string EliminaSpazi(string str)
{
StringBuilder sb = new StringBuilder(str.Length);

for (int i = 0; i < str.Length; i++)
{
if (str[i] != ' ' && str[i] != '\t')
{
sb.Append(str[i]);
}
else
{
sb.Append(' ');
while (str[i] == ' ' || str[i] == '\t')
i++;
i--;
}
}

return sb.ToString().Trim();
}

static void Main(string[] args)
{
Console.WriteLine();

string str = "questo e' un programma C# fatto da me";

Console.WriteLine(str);

str = EliminaSpazi(str);

Console.WriteLine(str);
}

J0k3r91
17-11-2008, 17:22
Non ho ben capito quel che intendi, ma il codice andrebbe usato così:

string text = textBox1.Text;

while (text.Contains(" "))
text = text.Replace(" ", " ");

Puoi postare il tuo codice (la parte interessata)?



string strpro = queryObj["Name"].ToString();
while (strpro.Contains(" "))
{
ProcessoreBox.Text = strpro.Replace(" ", " ");
}



Esattamente sto usando il WMI per ricevere le informazioni (in questo caso del processore), solamente che la stringa che ritorna è formattata malamente... :stordita:
Dimmi se sbaglio...

J0k3r91
17-11-2008, 17:26
Grazie mille davvero Vincenzo1968!

Ha funzionato perfettamente! :D

Certo che c'è una bella massa di codice per fare una cosa apparentemente banale... :doh:

Grazie ancora! :)

gugoXX
17-11-2008, 18:11
Questo e' in stile C#


string input = "Pazzo Pezzo Pizzo Pozzo P*zzo ";
string output = Regex.Replace(input, @"\s+", " ");


Poco codice e leggibile.
(Poi quanto in genere siano leggibili le Regular Expression e' un altro discorso)

Vincenzo1968
17-11-2008, 18:23
Questo e' in stile C#


string input = "Pazzo Pezzo Pizzo Pozzo P*zzo ";
string output = Regex.Replace(input, @"\s+", " ");


Poco codice e leggibile.
(Poi quanto in genere siano leggibili le Regular Expression e' un altro discorso)

http://www.hwupgrade.it/forum/customavatars/avatar246559_1.gif

Una versione migliore della funzione EliminaSpazi:


static string EliminaSpazi(string str)
{
StringBuilder sb = new StringBuilder(str.Length);

for (int i = 0; i < str.Length; i++)
{
if (str[i] != ' ' && str[i] != '\t')
{
sb.Append(str[i]);
}
else
{
sb.Append(' ');
while (i < str.Length && (str[i] == ' ' || str[i] == '\t'))
i++;
i--;
}
}

return sb.ToString().Trim();
}


Ho aggiunto il controllo i < str.Length nel ciclo while. Senza questo accorgimento, se ci sono spazi finali, la funzione crasha.

gugoXX
17-11-2008, 18:29
Mmmh...
un codice con un ciclo for il cui contatore viene cambiato all'interno del ciclo stesso, e per di piu' con anche un i-- viene chiamato

http://content.answers.com/main/content/wp/en-commons/thumb/8/82/180px-Spaghetti.jpg

Ma quella faccia che mi hai piazzato come commento cosa significa? :oink:

Vincenzo1968
17-11-2008, 18:42
Mmmh...
un codice con un ciclo for il cui contatore viene cambiato all'interno del ciclo stesso, e per di piu' con anche un i-- viene chiamato

http://content.answers.com/main/content/wp/en-commons/thumb/8/82/180px-Spaghetti.jpg

Ma quella faccia che mi hai piazzato come commento cosa significa? :oink:

A mo' di sgrunf.

Sarà spaghetti code ma, dal punto di vista delle performance, per come, dico, sono implementate le regex nei vari linguaggi, non c'è paragone:

http://swtch.com/~rsc/regexp/regexp1.html

Per quanto riguarda la leggibilità, non vedo cosa ci sia di poco leggibile in quelle poche righe di codice. L'algoritmo è semplice:

1) Leggiamo la stringa di input un carattere alla volta.
2) Se non è uno spazio, aggiungiamo il carattere letto alla stringa di output.
3) Se è uno spazio, aggiungiamo un carattere spazio alla stringa di output e incrementiamo il contatore(la variabile indice i) finchè il carattere letto è uno spazio; così facendo saltiamo tutti gli spazi superflui.
4) All'uscita dal ciclo while dobbiamo decrementare il contatore di una unità, altrimenti salteremmo il primo carattere della parola successiva.

È, bene o male, la stessa tecnica utilizzata dai compilatori per l'analisi lessicale.

gugoXX
17-11-2008, 19:28
Se proprio lo dovessi fare senza Regex, (e non lo farei), lo farei cosi'


string input = "Pazzo Pezzo Pizzo Pozzo P*zzo ";
StringWriter outputStream = new StringWriter();
bool previousspace = false;
foreach (char ch in input)
{
if (char.IsWhiteSpace(ch))
{
if (!previousspace)
{
outputStream.Write(ch);
}
previousspace = true;
}
else
{
outputStream.Write(ch);
previousspace = false;
}
}
string output = outputStream.ToString();


Il discorso della leggibilita'.
Lo vedo che non c'e' nulla di complesso in quel codice.
Ma se fra un anno io leggo la mia Regex la capisco subito. La Regex descrive un comportamento.
Se devo leggere il tuo codice o il mio ultimo, prima di dire "Fa questo!", devo simulare, devo seguire, devo capire, insomma, devo fare il lavoro del compilatore.

Il discorso della velocita'.
Spero sia chiaro che chi sceglie C# non lo sceglie per la velocita' di esecuzione.
Ma per la aumentare la velocita' di scrittura, diminuire gli errori e semplificare la fase di debug e la manutenzione
(rispetto a C e C++)

Vincenzo1968
17-11-2008, 19:53
...
Il discorso della leggibilita'.
Lo vedo che non c'e' nulla di complesso in quel codice.
Ma se fra un anno io leggo la mia Regex la capisco subito. La Regex descrive un comportamento.
Se devo leggere il tuo codice o il mio ultimo, prima di dire "Fa questo!", devo simulare, devo seguire, devo capire, insomma, devo fare il lavoro del compilatore.


Basta commentare adeguatamente il codice :


/*
1) Leggiamo la stringa di input un carattere alla volta.
2) Se non è uno spazio, aggiungiamo il carattere letto alla stringa di output.
3) Se è uno spazio, aggiungiamo un carattere spazio alla stringa di output e incrementiamo il contatore(la variabile indice i) finchè il carattere letto è uno spazio; così facendo saltiamo tutti gli spazi superflui.
4) All'uscita dal ciclo while dobbiamo decrementare il contatore di una unità, altrimenti salteremmo il primo carattere della parola successiva.
*/
... segue la definizione della funzione



Il discorso della velocita'.
Spero sia chiaro che chi sceglie C# non lo sceglie per la velocita' di esecuzione.
Ma per la aumentare la velocita' di scrittura, diminuire gli errori e semplificare la fase di debug e la manutenzione
(rispetto a C e C++)

Si, ma se con una funzione di poche righe ci guadagno in prestazioni, perché dovrei rinunciarvi? La funzione che ho postato(o la seconda che hai postato tu) è di uso generale. Se mi serve togliere gli spazi in eccesso da una serie di righe in uno o più file di testo, di media o grande dimensione, la questione 'prestazioni' non mi sembra un aspetto secondario.

^TiGeRShArK^
17-11-2008, 19:56
Basta commentare adeguatamente il codice :


/*
1) Leggiamo la stringa di input un carattere alla volta.
2) Se non è uno spazio, aggiungiamo il carattere letto alla stringa di output.
3) Se è uno spazio, aggiungiamo un carattere spazio alla stringa di output e incrementiamo il contatore(la variabile indice i) finchè il carattere letto è uno spazio; così facendo saltiamo tutti gli spazi superflui.
4) All'uscita dal ciclo while dobbiamo decrementare il contatore di una unità, altrimenti salteremmo il primo carattere della parola successiva.
*/
... segue la definizione della funzione




Si, ma se con una funzione di poche righe ci guadagno in prestazioni, perché dovrei rinunciarvi? La funzione che ho postato(o la seconda che hai postato tu) è di uso generale. Se mi serve togliere gli spazi in eccesso da una serie di righe in uno o più file di testo, di media o grande dimensione, la questione 'prestazioni' non mi sembra un aspetto secondario.
I commenti sono inutili perchè ti porta molto + tempo leggere tutte quelle righe piuttosto che capire immediatamente cosa fa la riga di codice con la regular expression.
Inoltre, riguardo alle prestazioni, quanti nanosecondi credi che guadagneresti con il tuo lunghissimo codice rispetto alla regex su un'unica stringa (come da requisiti)? :mbe:

EDIT: dimenticavo.
Inoltre i commenti non devono assolutamente spiegare quello che fa il codice ma piuttosto il motivo per cui viene fatta quell'operazione.
Ad esempio se io chiamo una funzione removeSpaces() è già chiarissimo il suo compito ed è inutile commentare scrivendo //Rimuove gli spazi.
Invece *potrebbe* essere utile spiegare il motivo per cui si vogliono eliminare gli spazi, e quindi in questo caso sarebbe //formatta correttamente la stringa restituita da WMI.

Vincenzo1968
17-11-2008, 20:04
I commenti sono inutili perchè ti porta molto + tempo leggere tutte quelle righe piuttosto che capire immediatamente cosa fa la riga di codice con la regular expression.
Inoltre, riguardo alle prestazioni, quanti nanosecondi credi che guadagneresti con il tuo lunghissimo codice rispetto alla regex su un'unica stringa (come da requisiti)? :mbe:

EDIT: dimenticavo.
Inoltre i commenti non devono assolutamente spiegare quello che fa il codice ma piuttosto il motivo per cui viene fatta quell'operazione.
Ad esempio se io chiamo una funzione removeSpaces() è già chiarissimo il suo compito ed è inutile commentare scrivendo //Rimuove gli spazi.
Invece *potrebbe* essere utile spiegare il motivo per cui si vogliono eliminare gli spazi, e quindi in questo caso sarebbe //formatta correttamente la stringa restituita da WMI.

Si, ma, tra pochi minuti, c'è l'ultima puntata di Montalbano. Anche se ho letto il libro e so, dunque, come va a finire, non me la voglio perdere. Ne riparliamo più tardi. :O

P.S sull'inutilità dei commenti nel codice, avrei molto da obiettare.

^TiGeRShArK^
17-11-2008, 20:25
Si, ma, tra pochi minuti, c'è l'ultima puntata di Montalbano. Anche se ho letto il libro e so, dunque, come va a finire, non me la voglio perdere. Ne riparliamo più tardi. :O

P.S sull'inutilità dei commenti nel codice, avrei molto da obiettare.

buona visione :p
per quanto riguarda i commenti in questo post:
http://www.codinghorror.com/blog/archives/000749.html
è spiegato piuttosto bene perchè i commenti devono spiegare solo il perchè di un certo codice e non la funzionalità del codice.
e citando Martin Fowler:

Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.

Che sintetizza perfettamente come la prima documentazione di ogni software dovrebbe essere nel codice stesso e non nei commenti ;)

Vincenzo1968
17-11-2008, 23:34
http://www.guidealgoritmi.it/images/ImgForums/EliminaSpazi.jpg


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

namespace EliminaSpazi
{
class Program
{
static string EliminaSpazi(string str)
{
StringBuilder sb = new StringBuilder(str.Length);

for (int i = 0; i < str.Length; i++)
{
if (str[i] != ' ' && str[i] != '\t')
{
sb.Append(str[i]);
}
else
{
sb.Append(' ');
while (i < str.Length && (str[i] == ' ' || str[i] == '\t'))
i++;
i--;
}
}

return sb.ToString().Trim();
}

static string EliminaSpaziGugo2(string str)
{
string input = "Pazzo Pezzo Pizzo Pozzo P*zzo ";
StringWriter outputStream = new StringWriter();
bool previousspace = false;
foreach (char ch in input)
{
if (char.IsWhiteSpace(ch))
{
if (!previousspace)
{
outputStream.Write(ch);
}
previousspace = true;
}
else
{
outputStream.Write(ch);
previousspace = false;
}
}

return outputStream.ToString();
}

static string EliminaSpaziGugo(string str)
{
return Regex.Replace(str, @"\s+", " ");
}

static void Main(string[] args)
{
string str = "Pazzo Pezzo Pizzo Pozzo P*zzo ";
string[] stringhe = new string[100000];

Console.WriteLine();

Stopwatch sw = new Stopwatch();

sw.Start();
for (int i = 0; i < 100000; i++)
stringhe[i] = EliminaSpazi(str);
sw.Stop();
Console.WriteLine("Tempo impiegato me medesimo -> {0} ms", sw.ElapsedMilliseconds);

sw.Reset();

sw.Start();
for (int i = 0; i < 100000; i++)
stringhe[i] = EliminaSpaziGugo(str);
sw.Stop();
Console.WriteLine("Tempo impiegato Gugo 1 -> {0} ms", sw.ElapsedMilliseconds);

sw.Reset();

sw.Start();
for (int i = 0; i < 100000; i++)
stringhe[i] = EliminaSpaziGugo2(str);
sw.Stop();
Console.WriteLine("Tempo impiegato Gugo 2 -> {0} ms", sw.ElapsedMilliseconds);

//Console.WriteLine(stringhe[0]);
//Console.WriteLine(stringhe[99999]);
}
}
}


:O

gugoXX
18-11-2008, 00:18
Non hai capito.
Scrivilo in C allora.
Oppure in Assembly, perche' rinunciarvi?

Se veramente volessi la velocita', oppure quando mi servisse la velocita', passerei ad un C++ ben scritto.
Ebbene, in 5 anni di C# non mi e' mai servita la velocita' al punto da scendere a compromessi con leggibilita', manutenabilita', autodescrizione.
E non sono mai passato al C++, se non proprio quando non ne potevo fare a meno, e non era per questioni di velocita'.
Su un progetto vivo, una riga di codice vive poco, molto meno rispetto al totale. La maggior parte delle volte e' inutile addirittura ottimizzare.
Meglio rifattorizzare.
E il target e' quello di scrivere meno codice possibile.
Meno codice equivale a meno errori, e spesso anche a piu' prestazioni.

Comunque se vuoi ti mostro un paio di trucchi.
Prova a trasformare il mio foreach in un ciclo for semplice e rifai i calcoli...
E prova a cambiare lo StringWriter in uno StringBuilder.
char.IsWhiteSpace fa qualcosa in piu' di un semplice controllo con spazio. Gli "spazi" sono tanti.

E le Regex comunque si possono compilare una volta per tutte. Si puo' mangiare qualcosa anche li'.

Riprendo. Se vuoi la velocita', se non programmi in gruppo, se non riscrivi il codice, se rileggi solo codice scritto da te, non usare il C#.
Ma fai attenzione. Fra qualche anno, neppure troppi, i linguaggi descrittivi corredati da compilatori con le palle, potranno anche rischiare di superare in velocita' i monolitici come il C o il C++ usati in modo classico.
Riprova fra 5/6 anni i nostri contest, penso che avrai delle suorprese.

Vincenzo1968
18-11-2008, 01:14
Non hai capito.
Scrivilo in C allora.
Oppure in Assembly, perche' rinunciarvi?

Se avessi, in input, cianquantamila file, allora si: C(assembly no; per lo stesso motico per il quale K&R hanno inventato il loro linguaggio ;))


Se veramente volessi la velocita', oppure quando mi servisse la velocita', passerei ad un C++ ben scritto.


Se avessi presentato la soluzione in C++ penso che J0k3r91 si sarebbe messo a scalciare come un mulo. E l'effetto della scalciata sarebbe stato da ricordarsene per sempre. :D


Ebbene, in 5 anni di C# non mi e' mai servita la velocita' al punto da scendere a compromessi con leggibilita', manutenabilita', autodescrizione.
E non sono mai passato al C++, se non proprio quando non ne potevo fare a meno, e non era per questioni di velocita'.
Su un progetto vivo, una riga di codice vive poco, molto meno rispetto al totale. La maggior parte delle volte e' inutile addirittura ottimizzare.
Meglio rifattorizzare.
E il target e' quello di scrivere meno codice possibile.
Meno codice equivale a meno errori, e spesso anche a piu' prestazioni.


Ma con meno codice possibile(regex) abbiamo una soluzione che è 5-6 volte più lenta. Pensa se dovessimo elaborare centomila file: non è meglio un programma che impiega un'ora anziché uno che ne impiega cinque?


Comunque se vuoi ti mostro un paio di trucchi.
Prova a trasformare il mio foreach in un ciclo for semplice e rifai i calcoli...
E prova a cambiare lo StringWriter in uno StringBuilder.
char.IsWhiteSpace fa qualcosa in piu' di un semplice controllo con spazio. Gli "spazi" sono tanti.

E le Regex comunque si possono compilare una volta per tutte. Si puo' mangiare qualcosa anche li'.

No no, non ho nulla da obiettare sulle prestazioni della tua seconda soluzione. La differenza con la mia è minima. È la regex che è 5 volte più lenta.


Riprendo. Se vuoi la velocita', se non programmi in gruppo, se non riscrivi il codice, se rileggi solo codice scritto da te, non usare il C#.
Ma fai attenzione. Fra qualche anno, neppure troppi, i linguaggi descrittivi corredati da compilatori con le palle, potranno anche rischiare di superare in velocita' i monolitici come il C o il C++ usati in modo classico.
Riprova fra 5/6 anni i nostri contest, penso che avrai delle suorprese.

Correrò il rischio. Per ora mi tengo stretto il C ;)

P.S per quanto riguarda la leggibilità, tenendo conto di quello che ha scritto Tiger, personalmente penso che, tra un anno, troverei più facile capire il codice che ho scritto io(o la tua seconda soluzione) piuttosto che quell'unica riga di codice con l'espressione regolare: @"\s+", " " (penso farei questa faccia: :confused: ) :D

^TiGeRShArK^
18-11-2008, 08:16
Ma con meno codice possibile(regex) abbiamo una soluzione che è 5-6 volte più lenta. Pensa se dovessimo elaborare centomila file: non è meglio un programma che impiega un'ora anziché uno che ne impiega cinque?
ecco..
ora dividi il secondo impiegato dal codice con la regex per 100000 e guarda la differenza con il tuo codice.
Una differenza di un paio di microsecondi è davvero così importante?
Devo ricordarti che in caso di una query WMI il collo di bottiglia non è assolutamente l'elaborazione del risultato restituito tramite regex?
E' perfettamente inutile scrivere un codice che su 100000 file da elaborare è 5 volte + veloce quando il requisito del problema ci dice chiaramente che questo metodo va applicato una volta (o, nel caso peggiore, una volta su ogni stringa restituita da WMI) e dunque le differenze di velocità sono nell'ordine dei microsecondi.

P.S per quanto riguarda la leggibilità, tenendo conto di quello che ha scritto Tiger, personalmente penso che, tra un anno, troverei più facile capire il codice che ho scritto io(o la tua seconda soluzione) piuttosto che quell'unica riga di codice con l'espressione regolare: @"\s+", " " (penso farei questa faccia: :confused: ) :D
Le espressioni regolari sono praticamente trasversali ai linguaggi usati e conoscendole capire il codice scritto in questo caso è assolutamente banale.
la @ serve per inserire in una stringa caratteri con backslash senza duplicarlo, lo \s indica i caratteri considerati spazio (\t\r\n\v\f) e il + indica che deve esserci almeno un elemento presente.
Quindi il significato del metodo utilizzato è: sostituisci tutti i caratteri in cui vi sia almeno uno spazio consecutivo con uno spazio singolo.
Per fare quanto richiesto dal problema in realtà sarebbe bastato utilizzare " +" che mi pare assolutamente banale da capire....
Comunque se hai un pò di fantasia prova a sostituire nel tuo bench @" \s+" con " +" per vedere come si sarebbero comportate prestazionalmente le regex nel caso reale.
E, volendo, ci sarebbe anche la compilazione da fare.
Ma, come dicevo prima, in questo caso è uno sbattimento assolutamente inutile e, anzi, peggiora le prestazioni dato il ridottissimo numero di esecuzioni di questo metodo.

gugoXX
18-11-2008, 08:48
Gia'. Le regular expression sono un linguaggio, e come tale va imparato.
Come dice Tiger, se fra un anno o piu' io leggessi quella regular expression la leggerei:
E' una regular expression di replacement.
Data una stringa in input, ogni volta che si incontra uno o piu' spazi consecutivi, li sostituiamo con il carattere spazio.

Che e' praticamente il testo del problema.

Vincenzo1968
18-11-2008, 16:57
Si ma tutto questo scarmazzo* per una funzioncina di poche righe. Se avessi postato una funzione con decine e decine di righe, allora si.

* Esempio di scarmazzo:
C'è, nel mese di maggio, una festa religiosa che prevede una processione. L'effige delle Madonna viene portata da un paese all'altro. Nonostante la mia scarsa vocazione religiosa(e più che scarsa, direi, meglio, nulla) corro sempre a vedermi la processione: è uno spettacolo da non perdere.
Coloro che accompagnano in processione la Madonna, vorrebbero portarla a spalla fin dentro il secondo paese, lasciandola dentro la chiesa. Ma gli abitanti del secondo paese, pretendono la consegna avvenga alle porte del paese. Ne nasce sempre burrasca, scarmazzo. Una girandola di bestemmie si alza intorno all'effige della Madonna. Mai, la Madonna, viene bestemmiata come in quell'occasione. Dalle parole si passa subito ai fatti e giù botte(della Madonna, è il caso di dire).
Ho i video. Prima o poi ne pubblico qualcuno su youtube :D

gugoXX
18-11-2008, 17:23
Te'.

Trova il modo di profilarmi questo Spaghettone molto sugoso, cosi' tasti con mano se varebbe ancora la pena o no di passare all'Assembly al posto del C
Guarda che bello spaghettone.

La parte commentata e' alternativa alla parte immediatamente precedente, che ho messo in onore della nostra amica.

(PS: Funziona con stringhe input non piu' lunghe di 100, ma tanto lo vedi subito leggendo il codice... :D )


char* Despacer2(char* input)
{
char* output=new char[100];
__asm
{
mov edi,input
mov esi,output
mov ecx,100

prev: mov al,[edi]
inc edi
cmp al,0
je end
ecchi: mov [esi],al
inc esi

cmp al,' '
jne prev

sloppa: repe scasb
mov al,[edi-1]
cmp al,0
jne ecchi
//sloppa: mov al,[edi]
//inc edi
//cmp al,' '
//je sloppa
//cmp al,0
//jne ecchi

end: mov [esi],0
}
return output;
}

Vincenzo1968
18-11-2008, 18:32
Te'.

Trova il modo di profilarmi questo Spaghettone molto sugoso, cosi' tasti con mano se varebbe ancora la pena o no di passare all'Assembly al posto del C
Guarda che bello spaghettone.

La parte commentata e' alternativa alla parte immediatamente precedente, che ho messo in onore della nostra amica.



Minchia! Vero è:

http://www.guidealgoritmi.it/images/ImgForums/EliminaSpazi2.jpg


#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <time.h>

char* EliminaSpaziRepne(char* input, int size)
{
char *output = (char*)malloc(sizeof(char)*size);

__asm
{
mov edi,input
mov esi,output
mov ecx,100
prev: mov al,[edi]
inc edi
cmp al,0
je end
ecchi: mov [esi],al
inc esi
cmp al,' '
jne prev
sloppa: repe scasb
mov al,[edi-1]
cmp al,0
jne ecchi
end: mov [esi],0
}
return output;
}


char* EliminaSpaziGugo(char* input, int size)
{
char *output = (char*)malloc(sizeof(char)*size);

__asm
{
mov edi,input
mov esi,output
mov ecx,100

prev: mov al,[edi]
inc edi
cmp al,0
je end
ecchi: mov [esi],al
inc esi
cmp al,' '
jne prev
sloppa: mov al,[edi]
inc edi
cmp al,' '
je sloppa
cmp al,0
jne ecchi

end: mov [esi],0
}
return output;
}

int main()
{
int k;
char **Array;
clock_t c_start, c_end;
int dim = 100000;

char *str = " stringa con spazi ";
int len = strlen(str);


Array = (char**)malloc(dim*sizeof(char*));
if ( Array != NULL )
{
for ( k = 0; k < dim; k++ )
{
Array[k] = (char*)malloc(len * sizeof(char));
if ( Array[k] == NULL )
{
printf("Memoria insufficiente.\n");
return -1;
}
}
}
else
{
printf("Memoria insufficiente.\n");
return -1;
}


c_start = clock();
for ( k = 0; k < dim; k++ )
Array[k] = EliminaSpaziRepne(str, len);
c_end = clock();
printf("\nTempo impiegato Repne-> %5.5f secondi\n", (double)(c_end - c_start) / CLOCKS_PER_SEC);

printf("%s\n", Array[dim-1]);

c_start = clock();
for ( k = 0; k < dim; k++ )
Array[k] = EliminaSpaziGugo(str, len);
c_end = clock();
printf("\nTempo impiegato Gugo-> %5.5f secondi\n", (double)(c_end - c_start) / CLOCKS_PER_SEC);

printf("%s\n", Array[dim-1]);

for ( k = 0; k < dim; k++ )
free(Array[k]);
free(Array);

return 0;
}


A questo punto mi chiedo se non sia stata follia pura, in tutti questi anni, ignorare l'assembly :confused:

:D

gugoXX
18-11-2008, 18:56
Appunto, come vedi anche per la comunita' il target principale non era la velocita', ma il tempo di scrittura, di rilettura, di manutenzione, la percentuale di errori, etc.
Cose che in assembly sono praticamente impossibili.

Occhio che e' una REPE SCASB, non una REPNE SCASCB.
Fa esattemente l'opposto della REPNE... non vorrei si arrabbiasse.

Poi la differenza tra i due tempi non me la spiego, avrei detto che sarebbe stata piu' veloce la versione senza REPE SCASB.
E sarebbe da confrontare con una versione fatta in C puro, per tastare la differenza.
Forse ho trovato, devi mettere
MOV ECX,size
nella versione REPE (nella'ltra invece la riga si puo' proprio levare)

Vincenzo1968
18-11-2008, 19:40
Appunto, come vedi anche per la comunita' il target principale non era la velocita', ma il tempo di scrittura, di rilettura, di manutenzione, la percentuale di errori, etc.
Cose che in assembly sono praticamente impossibili.

Occhio che e' una REPE SCASB, non una REPNE SCASCB.
Fa esattemente l'opposto della REPNE... non vorrei si arrabbiasse.

Poi la differenza tra i due tempi non me la spiego, avrei detto che sarebbe stata piu' veloce la versione senza REPE SCASB.
E sarebbe da confrontare con una versione fatta in C puro, per tastare la differenza.


Non ho capito il discorso di REPE SCASB e REPNE SCASCB. Pur non capendone un tubo di assembly, direi che la versione di repne è più veloce perchè contiene un paio di istruzioni in meno. Giusto? :confused:


Forse ho trovato, devi mettere
MOV ECX,size
nella versione REPE (nella'ltra invece la riga si puo' proprio levare)

Forse è meglio che mi posti l'assembly per intero. Come dicevo prima, non capendone un tubo, non saprei dove andare a mettere le mani.

J0k3r91
18-11-2008, 19:56
Se avessi presentato la soluzione in C++ penso che J0k3r91 si sarebbe messo a scalciare come un mulo. E l'effetto della scalciata sarebbe stato da ricordarsene per sempre. :D


:D :D ahahahahahah :D :D Vincenzo non posso darti torto! Il C# è comunque l'unico linguaggio che "cerco" di masticare :D.

Ad ogni modo penso che coi processori che ci ritroviamo di sti giorni... millisecondo più, millisecondo meno a me non fa proprio differenza :p

D'altronde è una buona ragione per analizzare i vari modi al fine di imparare, per la prossima occasione, il migliore! ;)

Vincenzo1968
18-11-2008, 20:05
:D :D ahahahahahah :D :D Vincenzo non posso darti torto! Il C# è comunque l'unico linguaggio che "cerco" di masticare :D.

Ad ogni modo penso che coi processori che ci ritroviamo di sti giorni... millisecondo più, millisecondo meno a me non fa proprio differenza :p

D'altronde è una buona ragione per analizzare i vari modi al fine di imparare, per la prossima occasione, il migliore! ;)

Ciao J0k3r91, :D

si lo so; nel tuo caso non c'è bisogno di ottimizzare il codice. Ma metti il caso che, domani, tu debba elaborare centomila file; per ogni riga di ognuno di questi file, devi togliere gli spazi superflui. Qui non si tratta più di una manciata di cicli di cpu. ;)

J0k3r91
18-11-2008, 20:12
Eh si è Vincenzo vero!

Io mi riferivo soltanto al caso del mio programma da "comune mortale" :D

Poi logicamente quando si hanno masse abnormi di dati da elaborare. in diverse ore di lavoro, quegli ms iniziano ad ingrossarsi :D

Ad ogni modo, pensiamo anche solamente ai giochi o meglio ancora a una partita in rete con un gioco, si cerca sempre di dare il minor carico possibile sia alla scheda video che all'adsl, altrimenti... scattoni tipo Crysis e lag a non finire! :rolleyes:

Vincenzo1968
18-11-2008, 20:15
...
Pur non capendone un tubo di assembly, direi che la versione di repne è più veloce perchè contiene un paio di istruzioni in meno. Giusto? :confused:


sbagliato!

direi che ho detto una gran minchiata:

http://www.guidealgoritmi.it/images/ImgForums/EliminaSpazi3.jpg

Ho aumentato le iterazioni da centomila a un milione.


#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <time.h>

char* EliminaSpaziRepne(char *input, int size)
{
char *output = (char*)malloc(sizeof(char)*size);

__asm
{
mov edi,input
mov esi,output
mov ecx,100
prev: mov al,[edi]
inc edi
cmp al,0
je end
ecchi: mov [esi],al
inc esi
cmp al,' '
jne prev
sloppa: repe scasb
mov al,[edi-1]
cmp al,0
jne ecchi
end: mov [esi],0
}
return output;
}


char* EliminaSpaziGugo(char *input, int size)
{
char *output = (char*)malloc(sizeof(char)*size);

__asm
{
mov edi,input
mov esi,output
mov ecx,100

prev: mov al,[edi]
inc edi
cmp al,0
je end
ecchi: mov [esi],al
inc esi
cmp al,' '
jne prev
sloppa: mov al,[edi]
inc edi
cmp al,' '
je sloppa
cmp al,0
jne ecchi

end: mov [esi],0
}
return output;
}

char * EliminaSpazi(char *input, int size)
{
int i, j;
char *output = (char*)malloc(sizeof(char)*size);

j = 0;
for (i = 0; i < size; i++)
{
if (input[i] != ' ' && input[i] != '\t')
{
output[j++] = input[i];
}
else
{
if ( i > 0 )
output[j++] = ' ';
while (i < size && (input[i] == ' ' || input[i] == '\t'))
i++;
i--;
}
}

return output;
}

int main()
{
int k;
char **Array;
clock_t c_start, c_end;
int dim = 1000000;

char *str = " stringa con spazi ";
int len = strlen(str);


Array = (char**)malloc(dim*sizeof(char*));
if ( Array != NULL )
{
for ( k = 0; k < dim; k++ )
{
Array[k] = (char*)malloc(len * sizeof(char));
if ( Array[k] == NULL )
{
printf("Memoria insufficiente.\n");
return -1;
}
}
}
else
{
printf("Memoria insufficiente.\n");
return -1;
}

c_start = clock();
for ( k = 0; k < dim; k++ )
Array[k] = EliminaSpazi(str, len);
c_end = clock();
printf("\nTempo impiegato me medesimo -> %5.5f secondi\n", (double)(c_end - c_start) / CLOCKS_PER_SEC);

printf("%s\n", Array[dim-1]);

c_start = clock();
for ( k = 0; k < dim; k++ )
Array[k] = EliminaSpaziRepne(str, len);
c_end = clock();
printf("\nTempo impiegato Repne-> %5.5f secondi\n", (double)(c_end - c_start) / CLOCKS_PER_SEC);

printf("%s\n", Array[dim-1]);

c_start = clock();
for ( k = 0; k < dim; k++ )
Array[k] = EliminaSpaziGugo(str, len);
c_end = clock();
printf("\nTempo impiegato Gugo-> %5.5f secondi\n", (double)(c_end - c_start) / CLOCKS_PER_SEC);

printf("%s\n", Array[dim-1]);

for ( k = 0; k < dim; k++ )
free(Array[k]);
free(Array);

return 0;
}


:D

gugoXX
19-11-2008, 08:29
Si',hai intuito bene.
Per ciascuna istruzione assembly, dato uno specifico microprocessore, e' possibile calcolare quanti cicli ci clock vengono impiegati. Alcune sono veloci (1 colpo di clock), altre sono lente (sul 8086 c'erano istruzioni che impiegavano 200 colpi di clock)
E' anche possibile che alcune operazioni vengano eseguite contemporaneamente, grazie all'accoppiabilita' su diverse pipeline. Le istruzioni devono essere indipendenti (agire tra registri diversi p.es.).
Sono pensate che i compilatori ovviamente fanno, ma non all'estremo.
Insomma, ce ne sarebbe ancora di tempo da guadagnare, tra C e Assembly, ma non e' la direzione preferita.
Ma laddove servisse veramente, laddove il profiler ti dicesse che la maggior parte del tempo e' trascorsa in uno specifico punto, allora potrai sempre aprire il contesto __asm{ e divertirti.
P.es. non so quanti compilatori siano in grado di sfruttare le MMX (e tutta la compagnia SSE) in modo diretto. Penso che quando vengono usate siano spesso piazzate a mano da un umano.
Ma intervengono appunto in quello che normalmente e' il cuore del calcolo di un programma, proprio sul percorso critico.

Quando dicevo REPE al posti di REPNE, intendevo nel nome della funzione EliminaSpaziRepe

Vincenzo1968
19-11-2008, 15:51
Ohé Gugo,

spiegami un paio di cose:

1) se cambio la label da 'sloppa: repe scasb' a 'sloppa: repne scasb' il programma va in crash. Inoltre, il compilatore mi segnala il warning "'sloppa' : unreferenced label". Ma, se tolgo del tutto la label, il programma, nuovamente, va in crash. :confused:

2) Ho ricalcolato i tempi con la tua frase di esempio : " Pazzo Pezzo Pizzo Pozzo P*zzo ". Come vedi ho aggiunto uno spazio iniziale. Le funzioni in assembly non lo eliminano:

http://www.guidealgoritmi.it/images/ImgForums/EliminaSpazi4.jpg

Come si può modificare il codice assembly per fare in modo che elimini anche gli spazi iniziali?

Grazie ;)

gugoXX
19-11-2008, 16:08
Ohé Gugo,

spiegami un paio di cose:

1) se cambio la label da 'sloppa: repe scasb' a 'sloppa: repne scasb' il programma va in crash. Inoltre, il compilatore mi segnala il warning "'sloppa' : unreferenced label". Ma, se tolgo del tutto la label, il programma, nuovamente, va in crash. :confused:

2) Ho ricalcolato i tempi con la tua frase di esempio : " Pazzo Pezzo Pizzo Pozzo P*zzo ". Come vedi ho aggiunto uno spazio iniziale. Le funzioni in assembly non lo eliminano:

http://www.guidealgoritmi.it/images/ImgForums/EliminaSpazi4.jpg

Come si può modificare il codice assembly per fare in modo che elimini anche gli spazi iniziali?

Grazie ;)

Ciao. Se metti repne invece di repe il risultato e' diverso perche' sono 2 opcode diversi.
REPE resta nel ciclo fino a che una condizione e' verificata.
REPNE resta nel ciclo fino a che una condizione non si e' verificata
(Per noi la condizione era essere uguali a ' ')

Lo spazio iniziale non viene tolto come da specifiche, questa funzione semplicmente sostutisce li spazi multipli consecutivi con uno spazio solo.
Per modificarla, prova prima a scriverla in C, e poi pensiamo all'Assembly.
Ho idea che anche la tua C non tolga un singolo spazio finale.

Vincenzo1968
19-11-2008, 16:23
Ciao. Se metti repne invece di repe il risultato e' diverso perche' sono 2 opcode diversi.
REPE resta nel ciclo fino a che una condizione e' verificata.
REPNE resta nel ciclo fino a che una condizione non si e' verificata
(Per noi la condizione era essere uguali a ' ')

Lo spazio iniziale non viene tolto come da specifiche, questa funzione semplicmente sostutisce li spazi multipli consecutivi con uno spazio solo.
Per modificarla, prova prima a scriverla in C, e poi pensiamo all'Assembly.
Ho idea che anche la tua C non tolga un singolo spazio finale.

No, la mia funzione lo toglie. Guarda l'output nella figura che ho postato.
Dico, a parte le specifiche iniziali, mi interessa sapere come fare. Se vuoi ti posso postare l'assembly generato dal compilatore.

dunque REPE è un opcode? E SCASB che funzione ha? :confused: potresti chiarirmi meglio questa cosa?

gugoXX
19-11-2008, 16:35
No, la mia funzione lo toglie. Guarda l'output nella figura che ho postato.
Dico, a parte le specifiche iniziali, mi interessa sapere come fare. Se vuoi ti posso postare l'assembly generato dal compilatore.

dunque REPE è un opcode? :confused: potresti chiarirmi meglio questa cosa?

Finale, spazio finale. (Perche' l'iniziale lo vorresti togliere e il finale no?)

SCASB e' una instruzione particolare, piu' complessa delle istruzioni classiche normali che si limitano a compiere operazioni singole semplici.
SCASB e' un programma vero e proprio, ed infatti la sequenza di microistruzioni relativa e' abbastanza lunga. Riassumendo, e magari anche sbagliando, e' una vita che non la uso, dato che si accoppia male ed almeno nel pentium era piu' lenta delle istruzioni assembly necessarie per risolvere la stessa cosa:
(Versione 32bit)
Viene caricato in un registro temporaneo di 8bit il byte della cella di memoria che si trova all'indirizzo scritto nel registro EDI (32 bit)
Viene incrementato EDI di uno, per permettere una nuova istruzione SCASB di operare sul byte di memoria successivo*
Viene confrontato il contenuto del registro temporaneo con il contenuto del registro utente AL (8bit anch'esso).
Viene aggiornati i flag Z del registro di stato, che e' il flag che ci dice se i 2 byte coincidono oppure no.

Tale istruzione puo' essere usata accoppiata con l'opcode REP, per costruire quelle che sono le macroistruzioni di stringa gia' native nelle piattaforme x86
E quindi avremo
REPNE SCASB: ripeti la SCASB fino a che il registro temporaneo e AL sono diversi (in pratica quando vuoi cercare un valore specifico in una stringa)
REPE SCASB: ripeti la SCASB fino a che il registro temporaneo e AL sono uguali (proprio come nel nostro caso)

* il registro EDI viene incrementato per spostarsi alla cella successiva, oppure decrementato per spostarsi alla cella precedente, a seconda di un altro flag del registro di stato, chiamato appunto flag di direzione.

Si dice che REPNE e REPE siano opcode perche' effettivamente guardando il codice macchina generato e' semplicemente un BYTE (o 2 non ricordo) in piu' messi prima dell'istruzione SCASB.
Ci sono altre istruzioni accoppiabili con REP. Resta che sono tipicamente piu' lente della versione estesa di un programma ASSEMBLY che cerchi di fare la stessa cosa, proprio a causa degli accoppiamenti tra le diverse pipeline che possono essere fatti.
E' una questione di come e' scritto il microcodice, e soprattutto di alcune cose collaterali che queste istruzioni fanno e che magari nel caso di uso specifico non servono.

Tornando alla richiesta di modifica.
E' ovvio che si riesce a fare in assembly, ma come puoi intuire si tratta di fare un paio di confronti ulteriori, prima di spostare il carattere nella stringa destinazione, volti a capire se siamo ancori all'inizio o se siamo arrivati alla fine.
Ritengo che sia meglio un postprocesso stile TRIM, che si occupi proprio di togliere questi spazi.

Vincenzo1968
19-11-2008, 16:56
Grazie mille per le info :)

Ho modificato la funzione in modo da togliere anche gli spazi finali:

http://www.guidealgoritmi.it/images/ImgForums/EliminaSpazi5.jpg


char* EliminaSpazi(char *input, int size)
{
int i, j;
char *output = (char*)malloc(sizeof(char)*size);

j = 0;
for (i = 0; i < size; i++)
{
if (input[i] != ' ' && input[i] != '\t')
{
output[j++] = input[i];
}
else
{
if ( i > 0 )
output[j++] = ' ';
while (i < size && (input[i] == ' ' || input[i] == '\t'))
i++;
i--;
}
}
output[j] = '\0';

j--;
while ( j >= 0 && ( output[j] == ' ' || output[j] == '\t' ) )
j--;
j++;
output[j] = '\0';

return output;
}


Ti scasso i cabasisi per un'ultima volta: la versione assembly? (ché non ne capisco un tubo di postprocesso stile TRIM)

Vincenzo1968
19-11-2008, 17:10
...
Ti scasso i cabasisi per un'ultima volta: la versione assembly? (ché non ne capisco un tubo di postprocesso stile TRIM)

Chiedo venia,

intendi dire che, per eliminare gli spazi interni uso la funzione assembly e quelli iniziali e finali li tolgo successivamente. Giusto?

gugoXX
19-11-2008, 17:44
I cabasisi.

Ma io, che solitamente lavoro in C#.
Ma guarda cosa mi tocca fare ;)

Si, come postprocesso intendevo qualcosa di piu' alto livello, ma nulla toglie che si possa ovviamente farlo anche in assembly


__asm
{
mov edi,input
mov esi,output
mov cl,' '
prepr: mov al,[edi] // Cerco spazi iniziali e li levo
lea edi,[edi+1]
cmp al,cl
je prepr
jmp prev2

prev: mov al,[edi]
lea edi,[edi+1]
prev2: test al,al
je end
ecchi: mov [esi],al
lea esi,[esi+1]
cmp al,cl
jne prev

sloppa: mov al,[edi]
lea edi,[edi+1]
cmp al,cl
je sloppa
test al,al
jne ecchi

end: mov byte ptr [esi],0
cmp esi,output
je ver
cmp [esi-1],cl
jne ver
mov byte ptr [esi-1],0
ver:
}

Vincenzo1968
19-11-2008, 17:48
I cabasisi.

Ma io, che solitamente lavoro in C#.
Ma guarda cosa mi tocca fare ;)



Forse è per questa tua disponibilità che non riesci mai a farmi incazzare. Neanche quando mi muovi feroci critiche. Mi stai simpatico, sempre, in ogni caso. Mah!

Ciao :D (e grazie di tutto ;))

P.S. Ho definito, in un altro thread, il .NET framework come 'una gran camurrìa' :D