Ordinare due array in un terzo

di il
8 risposte

Ordinare due array in un terzo

Leggendo un precedente thread ho visto che si parlava di fondere due array di interi in un terzo che risultasse ordinato. Siccome quando ho tempo libero mi diverte tentare di risolvere questo tipo di "enigmi", ho preso spunto da alcuni interventi e ho provato a delineare due soluzioni, in merito alle quali mi piacerebbe sentire qualche commento costruttivo.

In entrambi i casi ho eliminato due condizioni: 1) gli array originali sono ordinati e 2) negli array compaiono solo valori positivi.

Dunque, fondo due array di interi non ordinati che possono essere sia positivi, sia negativi. Vai col codice!!!

La prima versione non è molto onesta, infatti anziché fondere gli array non ordinati, prima ordina gli array e poi li fonde, aggirando l'ostacolo più che superarlo.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define Q_EL   10 /* Q_EL = quantita' di elementi */

void popola_array( int *a, int *b, size_t q );
int fondi_array_non_preordinati( int *a, int *b, int *e, size_t q );
void fondi_array_preordinati( int *a, int *b, int *e, size_t q );
void visualizza_array( int *a, int *b, int *e, size_t q );

int main() {
    int e[2*Q_EL] = {0}; /* e = esito  */
    int a[Q_EL], b[Q_EL];

    popola_array( a, b, Q_EL );

    if( fondi_array_non_preordinati(a,b,e,Q_EL) == 0 )
        visualizza_array( a, b, e, Q_EL );
    else printf( "Si e' verificato un errore.\n\n" );

    printf( " Premi \"invio\" per terminare il programma...    " );
    getchar();

    printf( "%s", "\n\n" );
    return 0;
}

/* inserisce valori casuali negli array */
void popola_array( int *a, int *b, size_t q ) {
    int i;

    srand( time(NULL) );

    for( i=0; i<q; ++i ) {
        a[i] = rand()%19-9; /* valori da -9 a +9 */
        b[i] = rand()%19-9; /* valori da -9 a +9 */
    }
}

/* usata da qsort() in fondi_array_non_preordinati() */
int confronta_int( const void *p1, const void *p2 ) {
    int a=*((int*)p1), b=*((int*)p2);
    return a==b ? 0 : (a<b?-1:1);
}

int fondi_array_non_preordinati( int *a, int *b, int *e, size_t q ) {
    int *ac = NULL; /* ac = copia di a */
    int *bc = NULL; /* bc = copia di b */
    int errore = 1; /* presuppone un errore */

    ac = malloc( q*sizeof(*ac) );

    if( ac ) {
        memcpy( ac, a, q*sizeof(*ac) );

        bc = malloc( q*sizeof(*bc) );

        if( bc ) {
            memcpy( bc, b, q*sizeof(*bc) );

            qsort( ac, q, sizeof(*ac), confronta_int );
            qsort( bc, q, sizeof(*bc), confronta_int );
            fondi_array_preordinati( ac, bc, e, q );
            errore = 0; /* tutto bene! */

            free( bc ); bc = NULL;
        }

        free( ac ); ac = NULL;
    }

    return errore;
}

void fondi_array_preordinati( int *a, int *b, int *e, size_t q ) {
    int i=0, j=0, c=0; /* contatori  */

    while( i<q && j<q )
        e[c++] = b[j]<a[i] ? b[j++] : a[i++];

    while( i<q )
        e[c++] = a[i++];

    while( j<q )
        e[c++] = b[j++];
}

void visualizza_array( int *a, int *b, int *e, size_t q ) {
    size_t i;

    printf( "\n\n%s", " Array a[]:  " );
    for( i=0; i<q; ++i ) printf( " %2d", a[i] );

    printf( "\n\n%s", " Array b[]:  " );
    for( i=0; i<q; ++i ) printf( " %2d", b[i] );

    printf( "\n\n%s", " Array e[]:  " );
    for( i=0; i<2*q; ++i ) printf( " %2d", e[i] );

    printf( "%s", "\n\n" );
}
La seconda versione prende spunto dal suggerimento di ettorec, che propone un'idea alla quale non avrei mai pensato.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define Q_EL   10 /* Q_EL = quantita' di elementi */

void popola_array( int *a, int *b, size_t q );
int fondi_array_non_preordinati( int *a, int *b, int *e, size_t q );
void visualizza_array( int *a, int *b, int *e, size_t q );

int main() {
    int e[2*Q_EL] = {0}; /* e = esito  */
    int a[Q_EL], b[Q_EL];

    popola_array( a, b, Q_EL );

    if( fondi_array_non_preordinati(a,b,e,Q_EL) == 0 )
        visualizza_array( a, b, e, Q_EL );
    else printf( "Si e' verificato un errore.\n\n" );

    printf( " Premi \"invio\" per terminare il programma...    " );
    getchar();

    printf( "%s", "\n\n" );
    return 0;
}

/* inserisce valori casuali negli array */
void popola_array( int *a, int *b, size_t q ) {
    int i;

    srand( time(NULL) );

    for( i=0; i<q; ++i ) {
        a[i] = rand()%19-9; /* valori da -9 a +9 */
        b[i] = rand()%19-9; /* valori da -9 a +9 */
    }
}

/* funzione ausiliaria usata da trova_limiti() */
int trova_minimo( int *a, size_t q ) {
    int minimo;
    size_t i;

    for( minimo=*a, i=1; i<q; ++i )
        minimo = a[i]<minimo ? a[i]: minimo;

    return minimo;
}

/* funzione ausiliaria usata da trova_limiti() */
int trova_massimo( int *a, size_t q ) {
    int massimo;
    size_t i;

    for( massimo=*a, i=1; i<q; ++i )
        massimo = a[i]>massimo ? a[i]: massimo;

    return massimo;
}

/* funzione ausiliaria usata da fondi_array_non_preordinati() */
void trova_limiti( int *a, int *b, int q, int *min, int *max, size_t *gamma ) {
    int t1, t2;

    t1 = trova_minimo( a, q );
    t2 = trova_minimo( b, q );
    *min = t1<t2 ? t1 : t2;

    t1 = trova_massimo( a, q );
    t2 = trova_massimo( b, q );
    *max = t1>t2 ? t1 : t2;

    *gamma = 1 + *max - *min;
}

int fondi_array_non_preordinati( int *a, int *b, int *e, size_t q ) {
    int errore = 1; /* presuppone un errore */
    int min, max;
    int *aux = NULL;
    size_t i,j, gamma;

    trova_limiti( a, b, q, &min, &max, &gamma );

    aux = malloc( gamma*sizeof(*aux) );

    if( aux ) {
        memset( aux, 0, gamma*sizeof(*aux) );

        for( i=0; i<q; ++i ) {
            aux[a[i]-min]++;
            aux[b[i]-min]++;
        }

        for( i=0, j=0; i<gamma; ++i )
            for( ; aux[i]; --aux[i] )
                e[j++] = (int)i+min;

        free( aux ); aux = NULL;

        errore = 0; /* tutto bene! */
    }

    return errore;
}

void visualizza_array( int *a, int *b, int *e, size_t q ) {
    size_t i;

    printf( "\n\n%s", " Array a[]:  " );
    for( i=0; i<q; ++i ) printf( " %2d", a[i] );

    printf( "\n\n%s", " Array b[]:  " );
    for( i=0; i<q; ++i ) printf( " %2d", b[i] );

    printf( "\n\n%s", " Array e[]:  " );
    for( i=0; i<2*q; ++i ) printf( " %2d", e[i] );

    printf( "%s", "\n\n" );
}
Altro che Settimana enigmistica!

8 Risposte

  • Re: Ordinare due array in un terzo

    E la domanda sarebbe?
    livello codice dilettantistico, diciamo ragioniere programmatore
  • Re: Ordinare due array in un terzo

    Infatti sono un dilettante (vedi la "firma" in calce ad ogni mio messaggio), per di più un po' avanti con gli anni.
    Cosa cambieresti? (poche cose, che se mi gonfi il cervello non riesco a seguirti e tanto vale).
  • Re: Ordinare due array in un terzo

    Tutto, sia dal punto di vista teorico che pratico (di implementazione).
    Sotto il primo profilo hai scritto un algoritmo (caso 2) di complessità o(f(n)), il che è una delle cose peggiori che si possano fare, cioè far dipendere la complessità dei dati di input, neppure dalla loro numerosità.
    Quanti passi impiega, complessivamente, quel programma per ordinare i due vettori
    {1000000000,1} e {2000000000,2} ? E quanta memoria?
    Si tratta di complessivamente 4 elementi.
    Bocciato senza appello

    ---
    Lato implementazione
    - il codice è del tutto illeggibile
    - le funzioni non sempre ritornano qualcosa, ma possono fallire silenziosamente
    - non c'è il minimo controllo sull'input
    - uso devastante di costrutti inefficienti, da un lato, illeggibili, dall'altro
    - variabili con nome privo di un qualsiasi significato
    - variabili passate nelle funzioni che non si capisce se sono di input o output
    - uso di presunte ottimizzazioni (tipo ++i) che investono analisi molto, ma molto più approfondite delle tipiche banalità da forum
    - printf con parametro %s ma statico (!), inutile e inefficiente
    ... insomma... livello ragioniere programmatore
  • Re: Ordinare due array in un terzo

    Essendo un appassionato e non avendo un'immagine professionale da difendere, sono molto propenso ad accettare le critiche se mi permettono di ricavarne qualcosa in termini di comprensione. Dunque, siccome l'elenco è un po' troppo esteso per essere considerato nel suo insieme (ti avevo chiesto di non inzupparmi il cervello, malefico!) da dove cominciamo? Mmm... in ordine sparso.

    L'algoritmo #2 non è farina del mio sacco, ma una "suggestione" che m'è giunta da un commento di ettorec. Il gioco consisteva nel riprendere una sua idea per scambiarci quattro chiacchiere (finalità ludica, di intrattenimento, tipica dei dilettanti). Quindi penso di poter lasciar perdere quel punto.

    L'algoritmo #1 è effettivamente un "trucco", perché ordina gli array prima di fonderli. Come va quel caso? C'era di meglio? Suggerisci, che ci provo e magari mi resta qualcosa nella capoccia per il futuro!

    Lato "implementazione" (parlo esclusivamente del primo dei due listati)...

    Codice illeggibile - questa non l'ho capita. Cosa lo rende illeggibile?

    Fallimento silenzioso - intendi forse dire che (ad esempio) avrei dovuto scrivere popola_array() a questo modo?
    /* inserisce valori casuali negli array */
    int popola_array( int *a, int *b, size_t q ) {
        if( a && b ) {
            /* nota: non conosco un modo per verificare che gli array
               a e b contengano effettivamente q elementi... esiste? */
            int i;
    
            srand( time(NULL) );
    
            for( i=0; i<q; ++i ) {
                a[i] = rand()%19-9; /* valori da -9 a +9 */
                b[i] = rand()%19-9; /* valori da -9 a +9 */
            }
            
            return 1; /* in genere mi piace usare return 1, perche' cosi'
                         posso fare verifiche rapide del tipo if( funzione() )
                         e if( !funzione() ) */
        }
        else {
            return 0; /* in genere mi piace usare return 0, perche' cosi'
                         posso fare verifiche rapide del tipo if( funzione() )
                         e if( !funzione() ) */
        }
    }
    Non l'ho fatto perché, nel contesto di un programma così smilzo, è noto e univoco chi e come chiama la funzione, per cui non viene mai chiamata con parametri che non siano già stati verificati in precedenza.

    Hai notato altre funzioni che ritornano void e che possono fallire per motivi diversi? Me le fai notare?

    Controllo sull'input - ti riferisci all'input tramite i parametri delle funzioni o all'input dei dati negli array da elaborare?

    Costrutti inefficienti/illeggibili - ad esempio? tipo int a=*((int*)p1), b=*((int*)p2); ?

    Nomi variabili non significativi - per avere più codice possibile sott'occhio tendo a usare delle specie di acronimi aggiungendo un commento che ne spiega il significato, tipo int *ac = NULL; /* ac = copia di a */ . Solitamente sono più attento a queste cose, specialmente quando mi cimento in programmi più ampi. In questo caso ho lasciato un po' correre, intenzionalmente. Lasciamo predere questo punto, dove non penso di poter apprendere gran che.

    Parametri con input/output non specificato - vero. Esiste un modo standard o ci si aggiusta? E' sensato farlo (come faccio quando affronto cose più complicate) aggiungendo un commento esplicativo in testa a ogni funzione? Tipo...
    /*==============================================================================
    Riceve in a e in b i puntatori a due array di int, ciascuno costituito da q
    elementi ordinati per valore crescente. In output, l'array di int puntato da e
    contiene tutti i valori di a e di b, sempre ordinati per valore crescente.
    Ovviamente, l'array puntato da e deve avere dimensioni almeno pari a 2*q.
    ==============================================================================*/
    
    int fondi_array_preordinati( int *a, int *b, int *e, size_t q ) {
        if( a && b && e ) {
            size_t i=0, j=0, c=0; /* contatori  */
    
            while( i<q && j<q )
                e[c++] = b[j]<a[i] ? b[j++] : a[i++];
    
            while( i<q )
                e[c++] = a[i++];
    
            while( j<q )
                e[c++] = b[j++];
            
            return 1;
        }
        else {
            return 0;
        }
    }
    i++ vs ++i - tempo fa lessi un libro nel quale si spiegava come l'uso di ++i permetta di "risparmiare" una chiamata al costruttore di copie nell'incrementare un oggetto in C++. Siccome ogni tanto mi capita anche di pasticciare un po' col C++, da quel momento ho cominciato a fare attenzione a quel dettaglio e, dai una volta dai due volte, m'è rimasta un'abitudine che tendo ad assecondare senza troppi pensieri (salvo quando è controindicata, ovviamente). Il libro è "Thinking in C++". Alcune parti ammetto d'averle un po' "subite" perché sono su un livello che vola un po' alto sulla mia testa, però qualche cosina mi si è appiccicata.

    %s con costante stringa: anche questa è una "suggestione" che m'è rimasta da letture su Stack Overflow; in alcuni interventi si faceva riferiemento a un possibile pecca in termini di sicurezza del passare direttamente puntatori a printf, tipo...
    char pippo[128];
    
    strcpy( pippo, "Quando passa ride tutta la citta!'" );
    
    printf( pippo ); /* brutto, cacca, diavolo! */
    printf( "%s", pippo ); /* bello, miele, angioletti che svolazzano :) */
    Ora, non ho assolutamente capito il meccanismo in gioco, però mi son detto "cosa mi costa?" e da allora ho cominciato a usare sempre "%s" (anche quando la stringa è una costante e non presenta rischi) fidandomi del parere di persone che sembrava sapessero di cosa stavano parlando. Ritornare sui miei passi non mi costa niente, ma mi piacerebbe sapere se e perché quel che paventavano su Stack Overflow è irrilevante.

    Livello ragioniere programmatore - per me è già una conquista, visto che nessuno mi ha insegnato! Se mi dai qualche dritta provo a passare al livello geometra programmatore...
  • Re: Ordinare due array in un terzo

    Sto per andare a cena, ed hai scritto non non dilungarmi.
    Cosa fa questo?
     printf( "\n\n%s", " Array a[]:  " );
    Normalmente invece di quella "roba strana" (strcpy) si usa sprintf, detto per inciso

    i++ e ++i non ha nulla a che fare col C++, riguarda a (presunte) maggiori prestazioni di ++i per la non-allocazione temporanea di una variabile "ghost" rispetto a i++
    Qui partirebbe uno spiegone-pippone titanico sui vari tipi di compilatori C e C++, e come materialmente trasformano i costrutti.

    I commenti puoi toglierli, tanto nessuno li legge.
    Non esiste un metodo "standard" per identificare i parametri; ognuno ha il suo.
    Il mio è quello di iniziare con le variabili di input, giustapponendo un i_, e quelle di output con o_
    quindi avrai
    
    int funzione( unsigned int i_numerorighe, unsigned int i_numerocolonne, int o_risultato)
    
    qui, vagamente, puoi capire che un i_numerocolonne sarà una variabile passata alla funzione come input, mentre
    in o_risultato la funzione scriverà qualcosa

    ------
    Riguardo agli algoritmi quello (2) ha l'errore peggiore per i motivi già esposti.

    Non disdegnare i geometri programmatori: io stesso lo sono.
  • Re: Ordinare due array in un terzo

    Solo questo
    Nomi variabili non significativi - per avere più codice possibile sott'occhio tendo a usare delle specie di acronimi aggiungendo un commento che ne spiega il significato, tipo int *ac = NULL; /* ac = copia di a */ . Solitamente sono più attento a queste cose, specialmente quando mi cimento in programmi più ampi. In questo caso ho lasciato un po' correre, intenzionalmente. Lasciamo predere questo punto, dove non penso di poter apprendere gran che.
    E' normale chiamare una variabile elencodeipuntatorichepoidevorilasciare.
    L'auto-documentazione del codice è importante quanto la documentazione (che in realtà non esiste mai, o quasi, se non facente parte del progetto).
    Unica eccezione sono le variabili contatori i,j,k tipicamente di derivazione FORTRAN, per i cicli
  • Re: Ordinare due array in un terzo

    Usando nomi tipo "elencodeipuntatorichepoidevorilasciare" in un'espressione, in un attimo mi ritrovo a dover scorrere in continuazione orizzontalmente il testo sullo schermo, il che mi rende difficile seguire il flusso di quel che sto facendo. Può sembrare roba da poco, ma per me non la è -- io ho BISOGNO di avere sotto agli occhi quello su cui sto ragionando.

    Per questo (sempre a titolo di esempio ed esagerandoalla grande, anche per scherzarci su), dovendo fare un programmino che conti la quantità degli elementi presenti in una stringa nella quale gli elementi stessi siano separati da un certo carattere delimitatore, non farei così...
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    size_t conta_gli_elementi_presenti_nella_stringa_lista( const char *i_stringa_lista, const char i_carattere_separatore_degli_elementi_della_lista );
    
    int main() {
        const char stringa_lista[] = "1,2,3,4,5,6";
        const char carattere_separatore_degli_elementi_della_lista = ',';
        size_t quantita_degli_elementi_presenti_nella_stringa_lista = conta_gli_elementi_presenti_nella_stringa_lista( stringa_lista, carattere_separatore_degli_elementi_della_lista );
    
        if( quantita_degli_elementi_presenti_nella_stringa_lista != (size_t)-1 ) {
            printf( "%u\n\n", quantita_degli_elementi_presenti_nella_stringa_lista );
        }
        else {
            printf( "Si e' verificato un errore.\n\n" );
        }
    
        printf( "Premi \"invio\" per uscire... " );
        getchar();
        return 0;
    }
    
    size_t conta_gli_elementi_presenti_nella_stringa_lista( const char *i_stringa_lista, const char i_carattere_separatore_degli_elementi_della_lista ) {
        if( i_stringa_lista ) {
            size_t lunghezza_della_stringa_lista = strlen( i_stringa_lista );
    
            if( lunghezza_della_stringa_lista != (size_t)-1 ) {
                size_t i, quantita_degli_elementi_presenti_nella_stringa_lista;
    
                for( quantita_degli_elementi_presenti_nella_stringa_lista=1, i=0; i<lunghezza_della_stringa_lista; ++i ) {
                    if( i_carattere_separatore_degli_elementi_della_lista == i_stringa_lista[i] ) {
                        quantita_degli_elementi_presenti_nella_stringa_lista = quantita_degli_elementi_presenti_nella_stringa_lista + 1;
                    }
                }
    
                return quantita_degli_elementi_presenti_nella_stringa_lista;
            }
    
            return 0; /* tutto bene */
        }
    
        return (size_t)-1; /* se arrivi fin qui, errore */
    }
    
    ...bensì...
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    size_t conta_elementi( const char *i_l, const char i_sep );
    
    int main() {
        const char lista[] = "1,2,3,4,5,6"; /* una lista delimitata da virgole */
        const char sep = ','; /* il separatore degli elementi della lista */
        size_t qEl; /* qEl = quantita' degli elementi presenti nella lista*/
        
        qEl = conta_elementi( lista, sep );
    
        if( qEl != (size_t)-1 )
            printf( "%u\n\n", qEl ); /* tutto bene */
        else printf( "Si e' verificato un errore.\n\n" );
    
        printf( "Premi \"invio\" per uscire... " );
        getchar();
        return 0;
    }
    
    /*==============================================================================
    Conta la quantita' degli elementi presenti nella stringa C i_l, individuandoli
    in base alle ricorrenze del carattere separatore sep.
    Se l'operazione ha successo, restituisce la quantita' degli elementi rilevata.
    Se l'operazione fallisce, restituisce (size_t)-1.
    L'operazione fallisce se i_l e' un puntatore nullo o se la stringa puntata ha
    dimensioni pari a (size_t)-1.
    ==============================================================================*/
    
    size_t conta_elementi( const char *i_l, const char sep ) {
        if( i_l ) {
            size_t ll = strlen( i_l ); /* ll = lunghezza della stringa lista */
    
            if( ll != (size_t)-1 ) {
                size_t i, qEl; /* qEl = quantita' degli elementi nella lista */
    
                for( qEl=1, i=0; i<ll; ++i )
                    if( sep == i_l[i] ) ++qEl;
    
                return qEl;
            }
        }
    
        return (size_t)-1; /* se arrivi fin qui, errore */
    }
    
    In questo modo riesco a tenere meglio traccia di quel che sto facendo.
    Invece, noterai che ho accolto ben volentieri il tuo suggerimento sulla specificazione della natura di input/output dei parametri che potrebbero dar luogo a equivoci (diversamente, la lista l'avrei chiamata "s").
    Normalmente invece di quella "roba strana" (strcpy) si usa sprintf
    Hai ragione, anch'io uso spesso sprintf() se devo "comporre" una stringa o inserirci delle conversioni. Qui però stavo solo facendo un esempio sbrigativo con una stringa piana, per cui ho "tagliato corto". Mi incuriosisce però il fatto che tu definisca "strana" la funzione strcpy()... è una comune funzione standard che si trova in string.h, nei testi sul C la si trova in ogni dove.
    i++ e ++i non ha nulla a che fare col C++, riguarda a (presunte) maggiori prestazioni di ++i per la non-allocazione temporanea di una variabile "ghost" rispetto a i++
    Sì, è un modo diverso di dire quel che ho detto io, perché in C++ la variabile "ghost" è una copia temporeanea dell'oggetto che vai ad incrementare, con tanto di invocazione della funzione costruttrice di copia e, successivamente della funzione distruttrice. Se quelle funzioni sono "ponderose", in condizioni limite (tipo cicli con elevate iterazioni) il carico può farsi sentire anche con la "muscolatura" dei PC moderni.

    In C è grossomodo la stessa cosa, solo che non si parla di "oggetti" e le funzioni creatrici non sono sotto il controllo di chi stila il programma.
  • Re: Ordinare due array in un terzo

    Ciao
    ho visto che hai provato ad applicare l'algoritmo che ti avevo consigliato .
    c'è un tipo di sintassi che utilizzi che non capisco sinceramente , abbiamo tipi di programmazione differenti ;
    comunque scrivo qui com'è che lo farei io dato che mi sono espresso
    
    #include <iostream>
    #include <ctime> 
    #include <cstdlib>
    using namespace std ; 
    
    int main () 
    {	
    srand (time (0)) ; 
    int x = rand ()% 30 ; 
    unsigned A [15] ; 
    for (int i = 0 ; i < 15 ; i++ ) 
    {
    	x = rand ()% 30 ; 
    	A [i] = x ; 
    }
    unsigned B [15] ;  
    for (int i = 0 ; i < 15 ; i++ ) 
    {
    	x = rand ()% 30 ; 
    	B [i] = x ; 
    }
    unsigned C [30] = {0} ;
    for (int i = 0 ; i < 15 ; i++ ) 
    {
    	C [A[i]] ++ ;
    }
    for (int i = 0 ; i < 15 ; i++ ) 
    {
    	C [B[i]] ++ ; 
    }
    int ordinato [30] ; 
    int j = 0 ; 
    int i = 0 ; 
    while (i < 30 )
    {
    	while (C [i] > 0 ) 
    	{
    		ordinato [j] = i ; 
    		j++ ; 
    		C [i] -- ; 
    	}
    i++ ; 
    }
    for (int i = 0 ; i < 30 ; i++ )
    {
    	cout << ordinato [i] << " " ;
    	if (i % 15 == 0 && i != 0 ) 
    	{
    		cout << endl ;
    	}
    }
    cout << endl ;
    }
Devi accedere o registrarti per scrivere nel forum
8 risposte