È un problema essere un programmatore senza conoscenza della complessità computazionale?


30

Mi è stato assegnato un esercizio nella mia università. L'ho portato a casa e ho provato a programmare un algoritmo per risolverlo, era qualcosa legato ai grafici, alla ricerca di componenti collegati, immagino.

Poi ho fatto la cosa più banale che mi è venuta in mente e poi ho mostrato al mio docente. Dopo una breve osservazione, ha percepito che la complessità di runtime della mia soluzione era invariabile e ha mostrato qualcosa di più efficiente. E c'è una tradizione di programmatori che non hanno idea di cosa sia la complessità computazionale (ero uno di quelli), quindi è un problema se un programmatore non ha idea di cosa sia la complessità computazionale?


3
Avviso del moderatore : non utilizzare commenti per discussioni estese o per inviare risposte pithy. È possibile utilizzare la chat room per discutere di questa domanda; i commenti precedenti sono stati spostati lì.
Gilles 'SO- smetti di essere malvagio' il

4
Il tuo titolo dice programmatore, ma la tua domanda dice studente. Generalmente 'programmatore' implica 'programmatore professionista' - quindi ti stai chiedendo se è un problema essere un programmatore professionista senza conoscenza della complessità computazionale? O se va bene per uno studente di programmazione non avere quella conoscenza? Le due sono domande diverse, anche se si scopre che hanno la stessa risposta.
corsiKa,

Risposte:


42

Sì, direi che conoscere qualcosa sulla complessità computazionale è un must per qualsiasi programmatore serio. Finché non hai a che fare con enormi set di dati, starai bene non conoscendo la complessità, ma se vuoi scrivere un programma che affronti seri problemi, ne hai bisogno.

Nel tuo caso specifico, il tuo esempio di ricerca di componenti collegati potrebbe aver funzionato per grafici fino a nodi. Tuttavia, se provassi un grafico con 100.000 nodi, l'algoritmo del tuo docente probabilmente lo avrebbe gestito in 1 secondo, mentre il tuo algoritmo avrebbe richiesto (a seconda di quanto fosse grave la complessità) 1 ora, 1 giorno o forse 1 eternità.100100.000

Un errore un po 'comune che gli studenti fanno nel nostro corso sugli algoritmi è quello di scorrere su un array come questo:

while array not empty
    examine first element of array
    remove first element from array

Questo potrebbe non essere il codice più bello ma in un programma complicato potrebbe apparire qualcosa di simile senza che il programmatore ne sia consapevole. Ora, qual è il problema con questo programma?

Supponiamo di eseguirlo su un set di dati di elementi. Rispetto al seguente programma, il precedente programma eseguirà 50.000 più lentamente.100.00050.000

while array not empty
    examine last element of array
    remove last element from array

Spero che tu sia d'accordo che avere le conoscenze per far funzionare il tuo programma volte più velocemente è probabilmente una cosa importante per un programmatore. Comprendere la differenza tra i due programmi richiede alcune conoscenze di base sulla teoria della complessità e alcune conoscenze sui particolari del linguaggio in cui si sta programmando.50.000

Nel mio linguaggio pseudocodice, "la rimozione di un elemento da un array" sposta tutti gli elementi a destra dell'elemento che viene rimosso di una posizione da sinistra. Questo rende la rimozione dell'ultimo elemento un'operazione poiché per fare ciò dobbiamo solo interagire con 1 elemento. La rimozione del primo elemento è O ( n ) poiché per rimuovere il primo elemento dobbiamo spostare anche tutti gli altri n - 1 elementi di una posizione verso sinistra.O(1)O(n)n-1

Un esercizio di base nella complessità è dimostrare che il primo programma farà operazioni mentre il secondo programma utilizza solonoperazioni. Se inseriscin=100.000vedrai che un programma è drasticamente più efficiente dell'altro.12n2nn=100.000

Questo è solo un esempio di giocattolo, ma richiede già una comprensione di base della complessità per dire la differenza tra i due programmi, e se stai effettivamente cercando di eseguire il debug / ottimizzare un programma più complicato che ha questo errore ci vuole una comprensione ancora maggiore per trovare fuori dove si trova il bug. Perché un errore come rimuovere un elemento da un array in questo modo può essere nascosto molto bene dalle astrazioni nel codice.

Avere una buona comprensione della complessità aiuta anche a confrontare due approcci per risolvere un problema. Supponiamo che tu abbia escogitato due diversi approcci per risolvere il problema dei componenti collegati da solo: per decidere tra loro sarebbe molto utile se tu potessi (rapidamente) stimarne la complessità e scegliere quello migliore.


10
"So long as you are not dealing with huge data sets you will be fine not knowing complexity"Questo è spesso vero, ma non sempre. Ad esempio, un O(n!)algoritmo non sarà praticabile nemmeno per set di dati relativamente piccoli. Se usi un O(n!)algoritmo in cui avresti potuto usare il O(n^2)tuo programma impiegherà 36.288 volte in più per l'esecuzione su una dimensione di dati di 10 . Su una dimensione dei dati di 20, stai esaminando 2,4 quintilioni di operazioni.
reirab

1
Penso che l'esempio di @ reirab debba essere incluso nella risposta. È più drammatico e dimostra il tuo punto in modo più deciso. E personalmente sono stato morso da tali algoritmi, prima di imparare la complessità computazionale.
Siyuan Ren,

2
Penso che ci sia un problema maggiore in gioco. Se semplicemente non conosci la tua auto, seleziona le attività in cui ciò non è necessario. Quindi puoi dire che quasi tutte le domande di cui ho bisogno per sapere che X finisce, potrebbe essere utile. Quindi, indipendentemente dal fatto che sia fondamentale, è comunque utile conoscerlo o alla fine potrebbe venire a morderti.
joojaa,

"Comprendere la differenza tra i due programmi richiede alcune conoscenze di base sulla teoria della complessità" - penso che per questo esempio particolare non lo sia. Potresti profilarlo, osservare che tutto il tempo viene impiegato in "rimuovi elemento", sapere (senza comprendere la teoria della complessità) che rimuovere l'ultimo elemento è più veloce della rimozione del primo, apportare la modifica e quindi accelerare il programma. Il vantaggio della comprensione della teoria della complessità è che consente di quantificare liberamente tali problemi senza profilarli, in modo da poter "prematuramente" ottimizzare.
Steve Jessop,

.. e in generale sospetto che tutti o quasi tutti gli esempi pratici possano essere risolti, uno per uno, senza riferimento alla teoria della complessità. In questo caso, sapere che copiare molti dati è più lento del non farlo, non è "teoria della complessità". Ma ovviamente è ancora utile nella programmazione (e in qualsiasi professione) avere un buon modello mentale di principi che emergono comunemente, perché è possibile analizzare, discutere e risolvere sistematicamente tali problemi per principio anziché uno alla volta con mezzi ad hoc.
Steve Jessop,

26

Questa è una confutazione della risposta di Tom van der Zanden , che afferma che questo è un must.

La cosa è, la maggior parte delle volte, 50.000 volte più lenta non è rilevante (a meno che tu non lavori in Google ovviamente).

Se l'operazione eseguita richiede un microsecondo o se la tua N non supera mai una determinata soglia (una parte elevata della codifica eseguita al giorno d'oggi) non avrà MAI importanza. In quei casi, pensare alla complessità computazionale ti farà solo perdere tempo (e molto probabilmente denaro).

La complessità computazionale è uno strumento per capire perché qualcosa potrebbe essere lento o scalare male e come migliorarlo, ma la maggior parte del tempo è completamente eccessivo.

Sono programmatore professionista da oltre cinque anni e non ho mai trovato la necessità di pensare alla complessità computazionale quando eseguo il loop all'interno di un loop O (M * N) perché l'operazione è sempre molto veloce o M e N lo sono piccolo.

Ci sono cose molto più importanti, generalmente utilizzate e più difficili da comprendere per chiunque svolga lavori di programmazione (threading e profiling sono buoni esempi nell'area delle prestazioni).

Certo, ci sono alcune cose che non sarai mai in grado di fare senza comprendere la complessità computazionale (ad esempio: trovare anagrammi su un dizionario), ma il più delle volte non ne hai bisogno.


3
Per espandere il tuo punto, ci sono casi in cui troppa enfasi sulla complessità computazionale può portarti fuori strada. Ad esempio, potrebbero esserci situazioni in cui l'algoritmo "migliore" è in realtà più lento per input piccoli. Il profiler è l'ultima fonte di verità.
Kevin Krumwiede,

2
@Kevin Krumwiede, concordo pienamente con te sul fatto che l'ottimizzazione di un ordinamento per un set di dati banale sia eccessiva. Ma mostra anche che avere almeno una comprensione della complessità è ancora importante. La comprensione è ciò che ti porterà a prendere la decisione che un ordinamento a bolle è appropriato rispetto ad altri algoritmi più complessi.
Kent A.

4
Quando sai che il set di dati è piccolo in tutti i casi puoi cavartela con questo genere di cose. Devi stare molto attento all'eccessiva complessità delle cose chiamate all'interno dei loop, però - non molto tempo fa ho ridotto un tempo di esecuzione al secondo in questo modo. Ho anche riscontrato un problema O (n ^ 8) una volta (convalida dei dati.) Molte cure lo hanno portato a 12 ore.
Loren Pechtel,

7
Non ho mai trovato la necessità di pensare alla complessità computazionale durante il loop all'interno di un loop O (M * N) perché l'operazione è sempre molto veloce o M e N sono così piccoli. - Ironia della sorte, l'argomento che esponi dimostra che hai pensato alla complessità computazionale. Hai deciso che non è un problema rilevante per quello che stai facendo e possibilmente giustamente, ma sei ancora consapevole dell'esistenza di questo problema e se mai dovesse costituire un problema, potresti reagire prima che si verifichino gravi conseguenze sul livello utente.
Wrzlprmft,

4
L'ottimizzazione prematura è la radice di tutti i mali, ma la pessimizzazione prematura è la radice di almeno una buona parte degli utenti infastiditi. Potrebbe non essere necessario essere in grado di risolvere una relazione di ricorrenza, ma se non si è in grado di dire la differenza tra O (1), O (N) e O (N ^ 2), soprattutto quando si Stai annidando i loop, qualcuno dovrà ripulire il casino in seguito. Fonte: i pasticci che ho dovuto ripulire in seguito. Un fattore 50.000 è così grande che è meglio sapere se puoi ancora permetterlo in seguito , quando i tuoi input sono cresciuti.
Jeroen Mostert,

14

Ho sviluppato software per circa trenta anni, lavorando sia come appaltatore che come dipendente, e ci sono riuscito abbastanza. La mia prima lingua era BASIC, ma mi sono subito insegnato il linguaggio automatico per ottenere una discreta velocità fuori dalla mia scatola poco potente. Nel corso degli anni ho trascorso molto tempo in profiler e ho imparato molto sulla produzione di codice ottimizzato veloce, efficiente in termini di memoria.

Indipendentemente da dire, sono autodidatta. Non ho mai incontrato la notazione O fino a quando non ho iniziato a intervistare alcuni anni fa. Nel mio lavoro professionale non è mai emerso che durante le interviste. Quindi ho dovuto imparare le basi solo per gestire quella domanda nelle interviste.

Mi sento il musicista jazz che non sa leggere gli spartiti. Posso ancora giocare bene. Conosco gli hashtable (diamine, ho inventato gli hashtable prima di sapere che erano già stati inventati) e altre importanti strutture di dati, e potrei anche conoscere alcuni trucchi che non insegnano a scuola. Ma penso che la verità sia che se vuoi avere successo in questa professione, dovrai andare indie o imparare le risposte alle domande che faranno durante le interviste.

Per inciso, di recente ho intervistato per un ruolo di sviluppatore web front-end. Mi hanno posto una domanda in cui la risposta richiedeva sia una conoscenza della complessità computazionale che dei logaritmi. Sono riuscito a ricordare abbastanza matematica di venti anni fa per rispondere più o meno correttamente, ma era un po 'stonante. Non ho mai dovuto usare i logaritmi in nessuno sviluppo di front-end.

Buona fortuna a te!


2
Quindi, la tua risposta è "sì"?
Raffaello

6
TL; DR: "sì". Tuttavia, nella mia esperienza non parlerai della complessità computazionale nella maggior parte dei lavori dopo che sei stato assunto. Sì, conosci le tue strutture di dati e le loro prestazioni, ma sapendo solo che un algoritmo è O (n) o qualunque cosa non faccia un buon programmatore. È molto meglio concentrarsi sulla scrittura di buon codice rapidamente e quindi sull'ottimizzazione degli hot spot in un secondo momento. La leggibilità e la manutenibilità sono generalmente più importanti per la maggior parte del codice rispetto alle prestazioni.
Scott Schafer,

3
Penso che possa accadere che la complessità si presenti in un contesto aziendale, ma la prima vera preoccupazione per le aziende è la spedizione : se funziona, è abbastanza buono, fino a quando non c'è un budget disponibile per migliorare l'app o un cliente torna a lamentarsi di problemi spettacoli. In situazioni b2b per progetti ad hoc, è probabilmente abbastanza raro. In b2c, o in mercati altamente competitivi (prodotti standardizzati), verrebbe probabilmente più spesso, con l'effetto diretto di innalzare l'asticella di entrata per i nuovi assunti.
didierc,

4
@didierc "Abbastanza buono" è anche ciò che rompe le cose tutto il tempo.
Raffaello

1
@didierc 1) Beh, la gente con gli ambiti di solidi in CS do (si spera) hanno una buona intuizione di ciò che è corretto e ciò che non lo è, mentre risolutori di problemi ad hoc possono commettere errori "semplici". Garantire che l'esecuzione dopo la compilazione di multipli sia esattamente ciò che era specifico è altamente non banale e ha un problema irrisolto. 2) No .
Raffaello

9

La domanda è abbastanza soggettiva, quindi penso che la risposta sia che dipende .

Non importa molto se lavori con piccole quantità di dati. In questi casi, di solito è bene usare qualunque cosa la libreria standard della tua lingua offra.

Tuttavia, quando hai a che fare con grandi quantità di dati o per qualche altra ragione insisti sul fatto che il tuo programma è veloce, devi capire la complessità computazionale. In caso contrario, come fai a sapere come risolvere un problema o quanto velocemente è possibile risolverlo? Ma capire solo la teoria non è abbastanza per essere un programmatore davvero bravo. Per produrre un codice estremamente veloce, credo, devi anche capire come funziona la tua macchina (cache, layout di memoria, set di istruzioni) e cosa fa il tuo compilatore (i compilatori fanno del loro meglio, ma non sono perfetti).

In breve, penso che comprendere la complessità ti renda chiaramente un programmatore migliore.


1
Penso che tu abbia generalmente la giusta idea, ma "soggettivo" non descrive adeguatamente questo problema; "circostanziale" sarebbe una parola migliore. Inoltre, si possono comunque scrivere programmi molto lenti che non funzionano su molti dati. Di recente ho risposto a una domanda su math.se sulla rappresentazione / archiviazione polinomiale. Ciò di solito comporta una quantità piuttosto piccola di dati, ad esempio polinomi di circa 1000 termini sono tipici; tuttavia ci sono enormi differenze nelle prestazioni del mondo reale (centinaia o migliaia di secondi rispetto a pochi secondi per una moltiplicazione) a seconda dell'implementazione.
Fizz,

4

È certamente un problema se qualcuno che sta sviluppando algoritmi significativi non capisce la complessità dell'algoritmo. Gli utenti di un algoritmo fanno generalmente affidamento su una buona qualità di implementazione con buone caratteristiche prestazionali. Sebbene la complessità non sia l'unico fattore che contribuisce alle caratteristiche prestazionali di un algoritmo, è comunque significativo. Qualcuno che non comprende la complessità dell'algoritmo ha meno probabilità di sviluppare algoritmi con utili caratteristiche prestazionali.

È meno un problema per gli utenti di un algoritmo, supponendo che gli algoritmi disponibili siano di buona qualità. Questo è vero per gli sviluppatori che usano linguaggi che hanno una libreria standard significativa, ben specificata, devono solo sapere come scegliere un algoritmo che soddisfi le loro esigenze. Il problema si presenta quando sono disponibili più algoritmi di un certo tipo (diciamo, ordinamento) all'interno di una libreria, perché la complessità è spesso uno dei criteri per scegliere tra. Uno sviluppatore che non comprende la complessità non può comprendere le basi per scegliere un algoritmo efficace per il proprio compito da svolgere.

Quindi ci sono sviluppatori che si concentrano su (per mancanza di una migliore descrizione) preoccupazioni non algoritmiche. Ad esempio, possono concentrarsi sullo sviluppo di interfacce utente intuitive. Spesso tali sviluppatori non dovranno preoccuparsi della complessità dell'algoritmo, sebbene, ancora una volta, possano fare affidamento su librerie o altri codici sviluppati con alta qualità.


3

Dipende, ma non dalla quantità di dati con cui stai lavorando, ma dal tipo di lavoro che fai, dai programmi che sviluppi.

Chiamiamo programmatore che non conosce il programmatore noobish di complessità concettuale.

Il programmatore noobish può fare:

  • sviluppare database di big data - non deve sapere come funziona all'interno, tutto ciò che deve sapere sono le regole sullo sviluppo di database. Sa cose come: cosa dovrebbe essere indicizzato, ... dove è meglio fare ridondanza nei dati, dove non è ...
  • creare giochi - deve solo studiare il funzionamento di alcuni motori di gioco e seguirne i paradigmi, i giochi e la grafica computerizzata rappresentano un grosso problema per i dati. Considerare 1920 * 1080 * 32 bit = cca 7,9 MB per singola immagine / cornice ... @ 60 FPS è almeno 475 MB / s. Considera che solo una copia non necessaria dell'immagine a schermo intero sprecerebbe circa 500 MB di memoria al secondo. Ma non ha bisogno di preoccuparsene, perché usa solo il motore!

Il programmatore noobish non dovrebbe fare:

  • sviluppare programmi complessi usati molto frequentemente indipendentemente dalla dimensione dei dati con cui lavora, ... ad esempio, i piccoli dati non causeranno un impatto notevole di una soluzione impropria durante lo sviluppo, perché saranno più lenti dei tempi di compilazione, ecc. Quindi, 0,5 secondo per un semplice programma non è molto dal punto di vista del programmatore noobish, beh, considera server server, che esegue questo programma venti volte al secondo. Richiederebbe 10 core per essere in grado di sostenere quel carico!
  • sviluppare programmi per dispositivi integrati. I dispositivi integrati funzionano con dati di piccole dimensioni, ma devono essere quanto più efficienti possibile, poiché le operazioni ridondanti producono un consumo di energia non necessario

Quindi, il programmatore noobish va bene, quando vuoi solo usare le tecnologie. Quindi, quando si tratta di sviluppo di nuove soluzioni, tecnologie personalizzate, ecc. Quindi è meglio assumere programmatori non noobish.

Tuttavia, se la società non sviluppa nuove tecnologie, utilizza solo quelle già realizzate. Sarebbe spreco di talento assumere programmatori abili e di talento. Lo stesso vale, se non vuoi lavorare su nuove tecnologie e stai bene mettendo le idee dei clienti in progetti e programmi usando framework già realizzati, allora è una perdita di tempo, imparare qualcosa che non ti servirà mai, tranne se è il tuo hobby e ti piacciono le sfide logiche.


1
Questa risposta potrebbe essere migliorata se usasse un'etichetta più neutra, o nessuna etichetta, proprio come l'altra risposta che utilizzava il termine "programmatore incompetente".
Moby Disk,

1
Non sono sicuro di cosa intendi per "complessità concettuale". La mia esperienza è che le persone che non conoscono abbastanza alberi o hashtable non possono prendere decisioni intelligenti su come indicizzare (parti di) un grande database.
Fizz,

3

Sono un po 'titubante nel scrivere una risposta qui, ma dato che mi sono trovato a fare pipì su molti altri [alcuni dei miei commenti sono stati spostati in chat], ecco come la vedo

Ci sono livelli / gradi di conoscenza di molte cose nell'informatica (e con questo termine intendo approssimativamente l'unione dell'informatica con la tecnologia dell'informazione). La complessità computazionale è sicuramente un campo vasto (sai cos'è OptP? O cosa dice il teorema di Abiteboul-Vianu?) E ammette anche molta profondità: la maggior parte delle persone con un grado CS non può produrre le prove degli esperti che vanno alla ricerca pubblicazioni in complessità computazionale.

Il livello di conoscenza e abilità / competenza richieste in tali materie dipende molto da ciò su cui si lavora. Completamente all'oscuro O (n2) a volte si dice che l'ordinamento sia una delle principali cause di programmi lenti [citazione necessaria] , ma un documento SIGCSE del 2003 riportava "L'ordinamento per inserzione è usato per ordinare piccoli (sotto) array nelle librerie standard Java e C ++". D'altro canto, l'ottimizzazione prematura proviene da qualcuno che non capisce cosa sia asintotico significhi (la complessità computazionale è una misura del genere) è talvolta un problema nella pratica della programmazione. Tuttavia, il punto di sapere almeno quando la complessità computazionale è importante è il motivo per cui devi avere qualche indizio a riguardo, almeno a livello universitario.

Onestamente oserei confrontare la situazione di sapere quando applicare i concetti di complessità computazionale (e sapere quando è possibile ignorarli in modo sicuro) con la pratica piuttosto comune (al di fuori del mondo Java) di implementare un codice sensibile alle prestazioni in C e insensibile alle prestazioni roba in Python ecc. (A parte questo, questo è stato chiamato in un discorso di Julia il "compromesso standard" .) Sapere quando non devi pensare alle prestazioni ti fa risparmiare tempo di programmazione, che è anche una merce abbastanza preziosa.

E un altro punto è che conoscere la complessità computazionale non ti renderà automaticamente bravo a ottimizzare i programmi; devi capire più cose relative all'architettura come la localizzazione della cache, il pipelining [a volte] e al giorno d'oggi anche la programmazione parallela / multi-core; quest'ultimo ha sia la sua teoria della complessità sia considerazioni pratiche; un assaggio di quest'ultimo da un documento SOSP del 2013 "Ogni schema di chiusura ha i suoi quindici minuti di fama. Nessuno dei nove schemi di chiusura che consideriamo supera costantemente qualsiasi altro, su tutte le architetture o carichi di lavoro di destinazione. A rigor di termini, per cercare l'ottimalità, un l'algoritmo di blocco dovrebbe quindi essere selezionato in base alla piattaforma hardware e al carico di lavoro previsto. "


1
A lungo termine, lo sviluppo o la ricerca di un algoritmo migliore è generalmente più vantaggioso rispetto alla modifica del linguaggio di programmazione per i bit sensibili alle prestazioni. Sono d'accordo con te sul fatto che esiste una forte associazione tra la mancanza di comprensione della complessità e l'ottimizzazione precoce, perché di solito si rivolgono ai bit meno sensibili alle prestazioni per l'ottimizzazione.
Rob,

1
In pratica, gli algoritmi (involontari) di Schlemiel the Painter sono molto più frequenti dell'ordinamento O (n ^ 2).
Peter Mortensen,

-1

Se non conosci big-O dovresti impararlo. Non è difficile ed è davvero utile. Inizia con la ricerca e l'ordinamento.

Ho notato che molte risposte e commenti raccomandano la profilazione , e quasi sempre significano utilizzare uno strumento di profilazione .

Il problema è che gli strumenti di profilazione sono su tutta la mappa in termini di efficacia per trovare ciò di cui hai bisogno per accelerare. Qui ho elencato e spiegato le idee sbagliate di cui soffrono i profiler.

Il risultato è che i programmi, se sono più grandi di un esercizio accademico, possono contenere giganti addormentati , che nemmeno il miglior profiler automatico può esporre. Questo post mostra alcuni esempi di come i problemi di prestazioni possono nascondere ai profiler.

Ma non possono nascondersi da questa tecnica.


Affermate che "Big-Oh" è utile, ma poi sostenete un approccio diverso. Inoltre, non vedo come l'apprendimento di "Big-Oh" (matematica) possa "iniziare con la ricerca e l'ordinamento" (problemi di algoritmo).
Raffaello

@Raphael: Non sostengo un approccio diverso: è ortogonale. Big-O è una conoscenza di base per comprendere gli algoritmi, mentre trovare problemi di prestazioni in software non giocattolo è qualcosa che fai dopo che il codice è stato scritto ed eseguito, non prima. (A volte gli accademici non lo sanno, quindi continuano a insegnare gprof, facendo più male che bene.) In tal modo, potresti scoprire che il problema è l'uso di un algoritmo O (n * n), quindi dovresti essere in grado di riconoscerlo. (E big-O è solo una proprietà matematicamente definita degli algoritmi, non un argomento diverso.)
Mike Dunlavey,

"E big-O è solo una proprietà matematicamente definita degli algoritmi, non una materia diversa." - è sbagliato, e pericolosamente così. "Big-Oh" definisce le classi di funzioni ; di per sé, non ha nulla a che fare con gli algoritmi.
Raffaello

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.