En esta página

Ejercicio 08: ft_print_combn

Muestra todas las combinaciones de n dígitos en orden creciente, sin repeticiones. El valor de n cumple 0 < n < 10.

DatoValor
Carpetaex08/
Archivoft_print_combn.c
Prototipovoid ft_print_combn(int n);
Funciones autorizadassolo write

Hecho con amor para Laura, por Bogdan.

El archivo

#include <unistd.h>

/*
** Copia local de ft_putchar: el enunciado solo permite entregar este archivo.
** Escribe un unico caracter en la salida estandar.
*/
static void put_char(char c)
{
    write(1, &c, 1);
}

/* Escribe los n digitos que hay guardados en el array. */
static void print_combination(char *digits, int n)
{
    int index;

    index = 0;
    while (index < n)
    {
        put_char(digits[index]);
        index++;
    }
}

/*
** Rellena la posicion indicada con todos los digitos posibles y baja un nivel.
** El tope de cada posicion deja sitio a los digitos que faltan a la derecha:
** asi nunca se pasa de '9' y cada combinacion sale ordenada y sin repetir.
*/
static void build_combinations(char *digits, int position, int n, int *first)
{
    char    digit;
    char    last;

    last = '0' + 10 - (n - position);
    if (position == 0)
        digit = '0';
    else
        digit = digits[position - 1] + 1;
    while (digit <= last)
    {
        digits[position] = digit;
        if (position == n - 1)
        {
            if (!*first)
            {
                put_char(',');
                put_char(' ');
            }
            *first = 0;
            print_combination(digits, n);
        }
        else
            build_combinations(digits, position + 1, n, first);
        digit++;
    }
}

/*
** Muestra todas las combinaciones de n digitos distintos en orden creciente.
** El enunciado garantiza 0 < n < 10; fuera de ese rango no se dibuja nada.
*/
void    ft_print_combn(int n)
{
    char    digits[10];
    int     first;

    if (n <= 0 || n >= 10)
        return ;
    first = 1;
    build_combinations(digits, 0, n, &first);
    put_char('\n');
}

Línea por línea

#include <unistd.h>

Declara write, la única función de librería permitida.

static void put_char(char c)

Copia privada de ft_putchar, igual que en los demás ejercicios.

static void print_combination(char *digits, int n)

Imprime los n dígitos guardados en el array.

char *digits es un puntero: la dirección donde empieza el array. Se usa para no copiar los diez caracteres en cada llamada y para que la función vea los cambios que hace build_combinations.

int index; y while (index < n)

Recorre las n casillas que están en uso. Las demás del array pueden tener basura: no se tocan.

static void build_combinations(char *digits, int position, int n, int *first)

Es la función recursiva que genera las combinaciones. Parámetros:

ParámetroQué es
digitsel array donde se va montando la combinación
positionqué casilla se está llenando ahora
ncuántos dígitos tiene la combinación
firstla dirección de la bandera del separador

char digit; char last;

digit es el valor que prueba la casilla actual; last, el máximo que puede tomar sin quedarse sin sitio a la derecha.

last = '0' + 10 - (n - position);

El cálculo clave. Reserva hueco para los dígitos que faltan:

npositionlastPor qué
20'8'detrás va un dígito, no puede ser '9'
21'9'es la última casilla
90'1'las otras ocho casillas necesitan ocho dígitos mayores
98'9'última casilla

Sin ese tope, la casilla 0 podría valer '9' y ya no habría dígitos mayores para las siguientes.

if (position == 0) / digit = '0';

La primera casilla empieza en '0'.

else / digit = digits[position - 1] + 1;

Las demás empiezan uno por encima del dígito anterior. Esa es la regla que garantiza el orden creciente y la ausencia de repeticiones: si la casilla previa tiene '3', esta empieza en '4'.

while (digit <= last)

Prueba todos los valores posibles de la casilla, del inicial al tope.

digits[position] = digit;

Guarda el valor elegido en el array. Las llamadas siguientes lo verán.

if (position == n - 1)

El caso base: si es la última casilla, la combinación está completa y toca imprimirla.

if (!*first)

first es un puntero, así que *first es el valor guardado en la variable original. !*first se lee «si no es la primera combinación». El * delante es desreferencia: ir a la dirección y leer lo que hay.

put_char(','); y put_char(' ');

El separador entre combinaciones, delante de todas menos la primera.

*first = 0;

Marca que ya se imprimió al menos una. Al escribir en *first, el cambio lo ve el nivel de arriba: por eso se pasa un puntero y no un int.

Imprime la combinación completa.

else / build_combinations(digits, position + 1, n, first);

Si todavía no es la última casilla, baja un nivel para llenar la siguiente. Se pasa first tal cual, porque ya es un puntero.

digit++;

Prueba el siguiente valor de esta casilla.

void ft_print_combn(int n)

La función pública.

char digits[10];

El array que guarda la combinación. Tiene 10 posiciones y no n porque el tamaño de un array se fija al compilar y n llega por parámetro. Diez es suficiente: el máximo de dígitos distintos es 10.

if (n <= 0 || n >= 10) / return ;

La guard clause. El enunciado garantiza 0 < n < 10, pero así la función no toca el array con valores imposibles. return ; sale de la función y no dibuja nada.

first = 1; y build_combinations(digits, 0, n, &first);

Arranca la recursión en la casilla 0 y le pasa la dirección de first con &. Sin el &, la función recibiría una copia y el separador saldría mal a partir de la segunda combinación.

put_char('\n');

Un salto al final, fuera de la recursión.

Traza de n = 2

Combinacióndigits[0]digits[1]Salida
1'0''1'01
2'0''2'02
…………
9'0''9'09
10'1''2'12
…………
45'8''9'89

Con n = 2, la casilla 0 llega hasta '8' y la 1 hasta '9'.

Cuántas combinaciones salen

nCombinacionesEjemplo
1100, 1, 2, …, 9
24501, 02, …, 89
3120igual que el ejercicio 05
910012345678 … 123456789

El número es 10! / (n! × (10 - n)!), las formas de elegir n dígitos entre 10.

Casos límite

CasoQué pasa
n = 0no se dibuja nada
n = 10no se dibuja nada
n = 1los diez dígitos separados por ", "
n = 9diez combinaciones de nueve cifras

Errores típicos

Mensaje o síntomaCausaSolución
sale el separador al principiousaste *first al revésimprime si !*first
todas las combinaciones llevan ", " delantepasaste first por valorpasa &first
combinaciones repetidasla casilla no empieza en anterior + 1revisa digits[position - 1] + 1
se pasa de '9'falta el tope lastcalcula last
n = 9 imprime basuraarray de tamaño nusa char digits[10]

Comprobarlo

cat > /tmp/main.c <<'EOF'
void    ft_print_combn(int n);

int main(void)
{
    ft_print_combn(2);
    return (0);
}
EOF
cc -Wall -Wextra -Werror ex08/ft_print_combn.c /tmp/main.c -o /tmp/ex08
/tmp/ex08

Salida:

01, 02, 03, 04, 05, 06, 07, 08, 09, 12, 13, ..., 89

Los puntos suspensivos abrevian; la salida real tiene las 45 combinaciones. Prueba también n = 1 y n = 9, y comprueba que n = 0 y n = 10 no imprimen nada ni se cuelgan.

Descargar