added level-1

This commit is contained in:
Rui Ribeiro 2025-09-23 08:41:21 +01:00
parent 034fb0dd63
commit 259d4ef6f3
31 changed files with 2853 additions and 0 deletions

View File

@ -0,0 +1,87 @@
#include "broken_gnl.h"
char *ft_strchr(char *s, int c)
{
int i = 0;
while (s[i] != c)
i++;
if (s[i] == c)
return (s + i);
else
return (NULL);
}
void *ft_memcpy(void *dest, const void *src, size_t n)
{
while (--n > 0)
((char *)dest)[n - 1] = ((char *)src)[n - 1];
return (dest);
}
size_t ft_strlen(char *s)
{
size_t ret = 0;
while (*s)
{
s++;
ret++;
}
return (ret);
}
int str_append_mem(char **s1, char *s2, size_t size2)
{
size_t size1 = ft_strlen(*s1);
char *tmp = malloc(size2 + size1 + 1);
if (!tmp)
return (0);
ft_memcpy(tmp, *s1, size1);
ft_memcpy(tmp + size1, s2, size2);
tmp [size1 + size2] = 0;
free(*s1);
*s1 = tmp;
return (1);
}
int str_append_str(char **s1, char *s2)
{
return (str_append_mem(s1, s2, ft_strlen(s2)));
}
void *ft_memmove(void *dest, const void *src, size_t n)
{
if (dest > src)
return (ft_memcpy(dest, src, n));
else if (dest == src)
return (dest);
size_t i = ft_strlen((char *)src) - 1;
while (i >= 0)
{
((char *)dest)[i] = ((char *)src)[i];
i--;
}
return (dest);
}
char *get_next_line(int fd)
{
static char b[BUFFER_SIZE + 1] = "";
char *ret = NULL;
char *tmp = ft_strchr(b, '\n');
while (!tmp)
{
if (!str_append_str(&ret, b))
return (NULL);
int read_ret = read(fd, b, BUFFER_SIZE);
if (read_ret == -1)
return (NULL);
b[read_ret] = 0;
}
if (!str_append_mem(&ret, b, tmp - b + 1))
{
free(ret);
return (NULL);
}
return (ret);
}

View File

@ -0,0 +1,10 @@
#ifndef GNL
# define GNL
# ifndef BUFFER_SIZE
# define BUFFER_SIZE 10
# endif
char *get_next_line(int fd);
#endif

158
level-1/filter/filter.c Normal file
View File

@ -0,0 +1,158 @@
/*
* EJERCICIO: FILTER
*
* DESCRIPCIÓN:
* Leer de stdin y escribir a stdout, pero reemplazando todas las ocurrencias
* de una cadena dada por asteriscos (*) de la misma longitud.
*
* CONCEPTOS CLAVE:
* 1. LECTURA DINÁMICA: read() con buffer variable
* 2. BÚSQUEDA DE PATRONES: strstr() o memmem()
* 3. GESTIÓN DE MEMORIA: realloc() para buffer dinámico
* 4. MANEJO DE ERRORES: perror() para errores de sistema
*
* ALGORITMO:
* 1. Leer todo el contenido de stdin en un buffer dinámico
* 2. Buscar todas las ocurrencias del patrón
* 3. Reemplazar cada ocurrencia con asteriscos
* 4. Escribir el resultado a stdout
*/
#define _GNU_SOURCE // Para memmem()
#ifndef BUFFER_SIZE
# define BUFFER_SIZE 42
#endif
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// Función para encontrar y reemplazar todas las ocurrencias
void ft_filter(char *buffer, const char *target)
{
int i = 0;
int target_len = strlen(target);
int j, k;
/*
* ALGORITMO DE BÚSQUEDA Y REEMPLAZO:
* - Recorrer el buffer carácter por carácter
* - En cada posición, verificar si coincide con el patrón
* - Si coincide, escribir asteriscos y saltar la longitud del patrón
* - Si no coincide, escribir el carácter original
*/
while (buffer[i])
{
j = 0;
// Verificar si hay coincidencia desde la posición actual
while (target[j] && (buffer[i + j] == target[j]))
j++;
if (j == target_len) // Coincidencia completa encontrada
{
// Escribir asteriscos en lugar del patrón
k = 0;
while (k < target_len)
{
write(1, "*", 1);
k++;
}
i += target_len; // Saltar el patrón completo
}
else
{
// No hay coincidencia, escribir carácter original
write(1, &buffer[i], 1);
i++;
}
}
}
int main(int argc, char **argv)
{
/*
* VALIDACIÓN DE ARGUMENTOS:
* - Debe haber exactamente 1 argumento
* - El argumento no puede estar vacío
*/
if (argc != 2 || argv[1][0] == '\0')
return 1;
/*
* LECTURA DINÁMICA DE STDIN:
* - Usar buffer temporal para leer chunks
* - Usar realloc() para expandir el buffer principal
* - Mantener seguimiento del total leído
*/
char temp[BUFFER_SIZE];
char *result = NULL;
char *buffer;
int total_read = 0;
ssize_t bytes;
// Leer de stdin hasta EOF
while ((bytes = read(0, temp, BUFFER_SIZE)) > 0)
{
// Expandir el buffer principal para acomodar los nuevos datos
buffer = realloc(result, total_read + bytes + 1);
if (!buffer)
{
free(result);
perror("realloc");
return 1;
}
result = buffer;
// Copiar los nuevos datos al buffer principal
memmove(result + total_read, temp, bytes);
total_read += bytes;
result[total_read] = '\0'; // Asegurar terminación
}
// Verificar errores de lectura
if (bytes < 0)
{
perror("read");
free(result);
return 1;
}
// Si no se leyó nada, salir sin error
if (!result)
return 0;
// Procesar el buffer y aplicar el filtro
ft_filter(result, argv[1]);
// Liberar memoria
free(result);
return 0;
}
/*
* PUNTOS CLAVE PARA EL EXAMEN:
*
* 1. GESTIÓN DE MEMORIA:
* - Siempre verificar el retorno de malloc/realloc
* - Liberar memoria en caso de error
* - Usar memmove() en lugar de memcpy() para solapamientos
*
* 2. MANEJO DE ERRORES:
* - Usar perror() para errores de sistema
* - Retornar códigos de error apropiados
* - Validar argumentos antes de usarlos
*
* 3. LECTURA DINÁMICA:
* - El buffer puede llenarse con cualquier cantidad de datos
* - Usar realloc() para expandir según sea necesario
* - Mantener un terminador nulo válido
*
* 4. ALGORITMO EFICIENTE:
* - Búsqueda simple carácter por carácter
* - Evitar usar funciones no permitidas
* - Escribir directamente a stdout sin almacenar el resultado
*/

View File

@ -0,0 +1,52 @@
Assignment name: filter
Expected files: filter.c
Allowed functions: read, write, strlen, memmem, memmove, malloc, calloc,
realloc, free, printf, fprintf, stdout, stderr, perror
--------------------------------------------------------------------------------
Write a program that will take one and only one argument.
Your program will then read from stdin and write all the content read in stdout
except that every occurrence of s must be replaced by '*' (as many as the length
of s). Your program will be tested with random buffer sizes, using a custom read
function. Therefore the buffer being set in your program will be filled with a
different number of chars each new call.
For example:
./filter bonjour
will behave in the same way as:
sed 's/bonjour/*******/g'
./filter abc
will behave in the same way as:
sed's/abc/***/g'
More generally your program must be the equivalent of the shell script filter.sh
present in this directory (you can compare your program with it).
In case of error during a read or a malloc, you must write "Error: " followed by
the error message in stderr and return 1.
If the program is called without arguments or with an empty argument or with multiple
arguments, it must return 1.
For example this should work:
$> echo 'abcdefaaaabcdeabcabcdabc' | ./filter abc | cat -e
***defaaa***de******d***$
$> echo 'ababcabababc' | ./filter ababc | cat -e
*****ab*****$
$>
NOTES:
memmem includes:
#define _GNU_SOURCE
#include <string.h>
perror includes:
#include <errno.h>
read includes:
#include <fcntl.h>

323
level-1/scanf/ft_scanf Normal file
View File

@ -0,0 +1,323 @@
/*
* EJERCICIO: FT_SCANF
*
* DESCRIPCIÓN:
* Implementar una versión simplificada de scanf que maneje solo %s, %d y %c.
*
* CONCEPTOS CLAVE:
* 1. ARGUMENTOS VARIABLES: va_list, va_start, va_arg, va_end
* 2. PARSING DE FORMATO: Analizar string de formato carácter por carácter
* 3. LECTURA DE ARCHIVO: fgetc(), ungetc() para control de flujo
* 4. CONVERSIONES: Convertir strings a números, manejar espacios en blanco
*
* FORMATO SOPORTADO:
* - %s: string (hasta el primer espacio en blanco)
* - %d: entero decimal (con signo opcional)
* - %c: un solo carácter
*/
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
// Función para saltar espacios en blanco en el stream
int match_space(FILE *f)
{
/*
* MANEJO DE ESPACIOS EN BLANCO:
* - Leer caracteres mientras sean espacios
* - Devolver el primer carácter no-espacio al stream
* - Retornar -1 en caso de error
*/
int ch = fgetc(f);
if (ch == EOF && ferror(f))
return -1;
while (ch != EOF)
{
if (!isspace(ch))
{
ungetc(ch, f); // Devolver carácter no-espacio
break;
}
ch = fgetc(f);
}
if (ferror(f))
return -1;
return 1;
}
// Función para verificar un carácter específico
int match_char(FILE *f, char c)
{
/*
* COINCIDENCIA DE CARACTERES LITERALES:
* - Leer un carácter del stream
* - Verificar si coincide con el esperado
* - Devolver al stream si no coincide
*/
int ch = fgetc(f);
if (ch == c)
return 1;
if (ch != EOF)
ungetc(ch, f);
return -1;
}
// Función para leer un carácter (%c)
int scan_char(FILE *f, va_list ap)
{
/*
* CONVERSIÓN %c:
* - Leer exactamente un carácter
* - No saltar espacios en blanco
* - Almacenar en el puntero proporcionado
*/
int ch = fgetc(f);
char *cp = va_arg(ap, char *);
if (ch == EOF)
return -1;
*cp = (char)ch;
return 1;
}
// Función para leer un entero (%d)
int scan_int(FILE *f, va_list ap)
{
/*
* CONVERSIÓN %d:
* - Saltar espacios en blanco iniciales
* - Leer signo opcional (+/-)
* - Leer dígitos y construir el número
* - Devolver último carácter no-dígito al stream
*/
int sign = 1;
int value = 0;
int ch = fgetc(f);
int *ip = va_arg(ap, int *);
int count = 0;
if (ch == EOF)
return -1;
// Saltar espacios en blanco
while (isspace(ch))
ch = fgetc(f);
// Manejar signo
if (ch == '-')
{
sign = -1;
ch = fgetc(f);
}
else if (ch == '+')
{
ch = fgetc(f);
}
// Verificar que el primer carácter sea un dígito
if (!isdigit(ch))
{
ungetc(ch, f);
return -1;
}
// Leer dígitos y construir el número
while (isdigit(ch))
{
value = value * 10 + (ch - '0');
count++;
ch = fgetc(f);
}
// Devolver último carácter no-dígito
if (ch != EOF)
ungetc(ch, f);
if (count == 0)
return -1;
*ip = value * sign;
return 1;
}
// Función para leer un string (%s)
int scan_string(FILE *f, va_list ap)
{
/*
* CONVERSIÓN %s:
* - Saltar espacios en blanco iniciales
* - Leer caracteres hasta encontrar espacio en blanco
* - Terminar el string con '\0'
* - Devolver último carácter de espacio al stream
*/
int ch = fgetc(f);
char *sp = va_arg(ap, char *);
int i = 0;
// Saltar espacios en blanco iniciales
while (ch != EOF && isspace(ch))
ch = fgetc(f);
if (ch == EOF)
return -1;
// Leer caracteres hasta espacio en blanco
do
{
sp[i] = ch;
i++;
ch = fgetc(f);
} while (ch != EOF && !isspace(ch));
sp[i] = '\0'; // Terminar string
// Devolver carácter de espacio al stream
if (ch != EOF)
ungetc(ch, f);
if (i == 0)
return -1;
return 1;
}
// Función para manejar conversiones de formato
int match_conv(FILE *f, const char **format, va_list ap)
{
/*
* DISPATCHER DE CONVERSIONES:
* - Analizar el carácter de conversión
* - Llamar a la función apropiada
* - Manejar espacios para %d y %s automáticamente
*/
switch (**format)
{
case 'c':
return scan_char(f, ap);
case 'd':
match_space(f); // %d salta espacios automáticamente
return scan_int(f, ap);
case 's':
match_space(f); // %s salta espacios automáticamente
return scan_string(f, ap);
case '\0':
return -1;
default:
return -1; // Conversión no soportada
}
}
// Función principal de scanf
int ft_vfscanf(FILE *f, const char *format, va_list ap)
{
/*
* LÓGICA PRINCIPAL DE SCANF:
* - Analizar string de formato carácter por carácter
* - Manejar caracteres literales y conversiones (%)
* - Contar conversiones exitosas
* - Parar en el primer error
*/
int nconv = 0; // Número de conversiones exitosas
// Verificar que hay datos disponibles
int c = fgetc(f);
if (c == EOF)
return EOF;
ungetc(c, f);
while (*format)
{
if (*format == '%')
{
// Conversión encontrada
format++;
if (match_conv(f, &format, ap) != 1)
break; // Error en conversión
else
nconv++; // Conversión exitosa
}
else if (isspace(*format))
{
// Espacio en blanco en formato: saltar espacios en input
if (match_space(f) == -1)
break;
}
else
{
// Carácter literal: debe coincidir exactamente
if (match_char(f, *format) != 1)
break;
}
format++;
}
// Verificar errores de archivo
if (ferror(f))
return EOF;
return nconv; // Retornar número de conversiones exitosas
}
// Función wrapper para scanf estándar
int ft_scanf(const char *format, ...)
{
/*
* WRAPPER PARA ARGUMENTOS VARIABLES:
* - Inicializar va_list
* - Llamar a la función principal con stdin
* - Limpiar va_list
*/
va_list ap;
va_start(ap, format);
int ret = ft_vfscanf(stdin, format, ap);
va_end(ap);
return ret;
}
/*
* EJEMPLO DE USO:
*
* int main(void)
* {
* int x;
* char str[100];
* char c;
*
* // Leer: número, espacio, string, espacio, carácter
* int converted = ft_scanf("%d %s %c", &x, str, &c);
*
* printf("Convertidos: %d\n", converted);
* printf("Número: %d, String: %s, Carácter: %c\n", x, str, c);
*
* return 0;
* }
*/
/*
* PUNTOS CLAVE PARA EL EXAMEN:
*
* 1. ARGUMENTOS VARIABLES:
* - va_start(ap, last_param) para inicializar
* - va_arg(ap, type) para obtener siguiente argumento
* - va_end(ap) para limpiar
*
* 2. CONTROL DE FLUJO DE ARCHIVO:
* - fgetc() para leer carácter
* - ungetc() para devolver carácter al stream
* - ferror() para verificar errores
*
* 3. MANEJO DE ESPACIOS:
* - %c NO salta espacios en blanco
* - %d y %s SÍ saltan espacios en blanco
* - Espacios en formato coinciden con cualquier whitespace
*
* 4. VALOR DE RETORNO:
* - Número de conversiones exitosas
* - EOF si error de archivo o EOF antes de conversiones
* - Parar en primera conversión fallida
*/

120
level-1/scanf/subject.txt Normal file
View File

@ -0,0 +1,120 @@
Assignment name : ft_scanf
Expected files : ft_scanf.c
Allowed functions: fgetc, ungetc, ferror, feof, isspace, isdigit, stdin, va_start, va_arg, va_copy, va_end
--------------------------------------------------------------------------------
Write a function named `ft_scanf` that will mimic the real scanf with the following constraints:
- It will manage only the following conversions: s, d, and c
- You don't have to handle the options *, m and '
- You don't have to handle the maximum field width
- You don't have to handle the types modifier characters (h, hh, l, etc.)
- You don't have to handle the conversions beginning with %n$
Your function must be declared as follows:
int ft_scanf(const char *, ... );
You will find in this directory a file containing a part of the code you will need, you just have to complete it.
To test your program compare your results with the real scanf.
Hint : You may need to read the man of scanf.
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
int match_space(FILE *f)
{
// You may insert code here
return (0);
}
int match_char(FILE *f, char c)
{
// You may insert code here
return (0);
}
int scan_char(FILE *f, va_list ap)
{
// You may insert code here
return (0);
}
int scan_int(FILE *f, va_list ap)
{
// You may insert code here
return (0);
}
int scan_string(FILE *f, va_list ap)
{
// You may insert code here
return (0);
}
int match_conv(FILE *f, const char **format, va_list ap)
{
switch (**format)
{
case 'c':
return scan_char(f, ap);
case 'd':
match_space(f);
return scan_int(f, ap);
case 's':
match_space(f);
return scan_string(f, ap);
case EOF:
return -1;
default:
return -1;
}
}
int ft_vfscanf(FILE *f, const char *format, va_list ap)
{
int nconv = 0;
int c = fgetc(f);
if (c == EOF)
return EOF;
ungetc(c, f);
while (*format)
{
if (*format == '%')
{
format++;
if (match_conv(f, &format, ap) != 1)
break;
else
nconv++;
}
else if (isspace(*format))
{
if (match_space(f) == -1)
break;
}
else if (match_char(f, *format) != 1)
break;
format++;
}
if (ferror(f))
return EOF;
return nconv;
}
int ft_scanf(const char *format, ...)
{
// ...
int ret = ft_vfscanf(stdin, format, ap);
// ...
return ret;
}

74
level-2/n_queens/Makefile Normal file
View File

@ -0,0 +1,74 @@
# Program name
NAME = n_queens
# Compiler and flags
CC = gcc
CFLAGS = -Wall -Wextra -Werror
# Source files
SRCS = n_queens.c
# Object files
OBJS = $(SRCS:.c=.o)
# Header files
HEADERS = n_queens.h
# Colors for output
GREEN = \033[0;32m
RED = \033[0;31m
RESET = \033[0m
# Main rule
all: $(NAME)
# Compile the program
$(NAME): $(OBJS)
@echo "$(GREEN)Compiling $(NAME)...$(RESET)"
@$(CC) $(CFLAGS) $(OBJS) -o $(NAME)
@echo "$(GREEN)$(NAME) compiled successfully!$(RESET)"
# Compile object files
%.o: %.c $(HEADERS)
@echo "Compiling $<..."
@$(CC) $(CFLAGS) -c $< -o $@
# Clean object files
clean:
@echo "$(RED)Cleaning object files...$(RESET)"
@rm -f $(OBJS)
@echo "$(RED)✓ Object files cleaned!$(RESET)"
# Clean everything
fclean: clean
@echo "$(RED)Cleaning executable...$(RESET)"
@rm -f $(NAME)
@echo "$(RED)✓ Everything cleaned!$(RESET)"
# Rebuild everything
re: fclean all
# Test the program with different values
test: $(NAME)
@echo "$(GREEN)Testing n_queens program:$(RESET)"
@echo "\n$(GREEN)Testing with n=1:$(RESET)"
@./$(NAME) 1
@echo "\n$(GREEN)Testing with n=2 (no solutions):$(RESET)"
@./$(NAME) 2 || echo "No solutions found"
@echo "\n$(GREEN)Testing with n=4:$(RESET)"
@./$(NAME) 4
@echo "\n$(GREEN)Testing with n=8 (counting solutions):$(RESET)"
@echo "Number of solutions for n=8: $$(./$(NAME) 8 | wc -l)"
# Display help
help:
@echo "$(GREEN)Available commands:$(RESET)"
@echo " make - Compile the program"
@echo " make clean - Remove object files"
@echo " make fclean - Remove all generated files"
@echo " make re - Rebuild everything"
@echo " make test - Run tests with different values"
@echo " make help - Show this help message"
# Declare phony targets
.PHONY: all clean fclean re test help

252
level-2/n_queens/README.md Normal file
View File

@ -0,0 +1,252 @@
# N-Queens Problem - Simple Explanation 👑
## What is the N-Queens Problem? 🤔
Imagine you have a chessboard and some queens (the most powerful pieces in chess). The N-Queens problem is like a puzzle where you need to place N queens on an N×N chessboard so that **none of them can attack each other**.
### What's a Queen in Chess? ♛
A queen is the strongest piece in chess. She can move:
- **Horizontally** (left and right) ←→
- **Vertically** (up and down) ↕️
- **Diagonally** (in any diagonal direction) ↗️↖️↙️↘️
She can move as many squares as she wants in these directions!
## The Challenge 🎯
Let's say we want to solve the **4-Queens problem** (N=4):
- We have a 4×4 chessboard (16 squares total)
- We need to place 4 queens
- **NO queen can attack any other queen**
### Example: What Does "Attack" Mean?
If we put a queen at position (0,0) - that's the top-left corner - she can attack:
```
Q . . . ← Queen here attacks all positions marked with X
X . . .
X . . .
X . . .
```
```
Q X X X ← Queen attacks horizontally
. . . .
. . . .
. . . .
```
```
Q . . . ← Queen attacks diagonally
. X . .
. . X .
. . . X
```
So we can't put any other queen in any of those X positions!
## How Our Program Works 🖥️
### Step 1: Understanding the Input
When you run our program like this:
```bash
./n_queens 4
```
The number `4` means "solve the 4-Queens problem" (4×4 board with 4 queens).
### Step 2: How We Represent the Solution
Instead of drawing the whole board, we use a clever trick! Since we know:
- There must be exactly ONE queen in each row
- There must be exactly ONE queen in each column
We can represent a solution as a list of numbers:
```
1 3 0 2
```
This means:
- **Row 0**: Queen is in column 1
- **Row 1**: Queen is in column 3
- **Row 2**: Queen is in column 0
- **Row 3**: Queen is in column 2
### Step 3: Visualizing the Solution
If we draw this on a 4×4 board:
```
. Q . . ← Row 0, Column 1
. . . Q ← Row 1, Column 3
Q . . . ← Row 2, Column 0
. . Q . ← Row 3, Column 2
```
Let's check: Can any queen attack another?
- Queen at (0,1): Can't attack any other queen ✅
- Queen at (1,3): Can't attack any other queen ✅
- Queen at (2,0): Can't attack any other queen ✅
- Queen at (3,2): Can't attack any other queen ✅
Perfect! This is a valid solution!
## How Our Code Works 🔧
### The Main Function (`main`)
```c
int main(int argc, char **argv)
{
int n;
int *queens;
if (argc != 2) // Check if user gave us exactly one number
return (1);
n = atoi(argv[1]); // Convert the text "4" to number 4
if (n <= 0) // Make sure the number is positive
return (1);
queens = (int *)malloc(sizeof(int) * n); // Create space to store queen positions
if (!queens) // Check if we got the memory
return (1);
solve_nqueens(queens, 0, n); // Start solving from row 0
free(queens); // Clean up memory
return (0);
}
```
**What this does in simple terms:**
1. "Did the user give me a number?"
2. "Is it a good number (positive)?"
3. "Let me create space to remember where I put the queens"
4. "Now let me solve the puzzle!"
5. "Clean up when I'm done"
### The Solver Function (`solve_nqueens`)
This is the "brain" of our program. It uses a technique called **backtracking**:
```c
void solve_nqueens(int *queens, int row, int n)
{
int col;
if (row == n) // Did I place all queens?
{
print_solution(queens, n); // Yes! Print this solution
return ;
}
col = 0;
while (col < n) // Try each column in this row
{
if (is_safe(queens, row, col, n)) // Can I put a queen here?
{
queens[row] = col; // Yes! Put the queen here
solve_nqueens(queens, row + 1, n); // Try the next row
}
col++;
}
}
```
**Think of it like this:**
1. "Am I done placing all queens?" → If yes, print the solution!
2. "If not, let me try putting a queen in each column of this row"
3. "For each column, ask: Is it safe to put a queen here?"
4. "If safe, put the queen there and try to solve the next row"
### The Safety Check (`is_safe`)
This function checks if a queen position is safe:
```c
int is_safe(int *queens, int row, int col, int n)
{
int i;
i = 0;
while (i < row) // Check all previously placed queens
{
if (queens[i] == col) // Same column?
return (0); // Not safe!
if (queens[i] - i == col - row) // Same diagonal (\)?
return (0); // Not safe!
if (queens[i] + i == col + row) // Same diagonal (/)?
return (0); // Not safe!
i++;
}
return (1); // Safe to place queen here!
}
```
**The three checks:**
1. **Same column**: `queens[i] == col`
- "Is there already a queen in this column?"
2. **Diagonal 1**: `queens[i] - i == col - row`
- "Is there a queen on the same diagonal going from top-left to bottom-right?"
3. **Diagonal 2**: `queens[i] + i == col + row`
- "Is there a queen on the same diagonal going from top-right to bottom-left?"
## Example Run 🏃‍♂️
Let's trace through what happens when we run `./n_queens 4`:
1. **Start with row 0**: Try putting a queen in each column
- Column 0: Check safety → Safe! Put queen at (0,0)
- Move to row 1
2. **Row 1**: Try each column
- Column 0: Not safe (same column as row 0)
- Column 1: Not safe (same column... wait, no queen there yet)
- Column 2: Check safety → Safe! Put queen at (1,2)
- Move to row 2
3. **Row 2**: Try each column
- Column 0: Not safe (diagonal conflict)
- Column 1: Not safe (diagonal conflict)
- Column 2: Not safe (same column as row 1)
- Column 3: Not safe (diagonal conflict)
- **No solution found!** Go back to row 1
4. **Back to row 1**: Try next column
- Column 3: Check safety → Safe! Put queen at (1,3)
- Move to row 2
5. Continue this process...
Eventually, we find: `1 3 0 2` and `2 0 3 1`
## Results for Different N Values 📊
- **N=1**: `0` (1 solution - just put the queen anywhere)
- **N=2**: No output (impossible to solve)
- **N=3**: No output (impossible to solve)
- **N=4**: `1 3 0 2` and `2 0 3 1` (2 solutions)
- **N=8**: 92 solutions!
## Fun Facts! 🎉
1. **Why no solution for N=2 and N=3?**
- For N=2: You need 2 queens on a 2×2 board, but they'll always attack each other!
- For N=3: Same problem - not enough space!
2. **The 8-Queens problem** (standard chessboard) has exactly **92 solutions**!
3. **This is a classic computer science problem** that teaches us about:
- **Recursion** (functions calling themselves)
- **Backtracking** (trying something, and if it doesn't work, going back and trying something else)
- **Problem solving** (breaking a big problem into smaller pieces)
## How to Use the Program 💻
1. **Compile**: `make`
2. **Run**: `./n_queens 4`
3. **Test different values**: `make test`
4. **Clean up**: `make clean`
Try it with different numbers and see what happens! 🚀

View File

@ -0,0 +1,84 @@
#include "n_queens.h"
void print_number(int n)
{
char c;
if (n >= 10)
print_number(n / 10);
c = '0' + (n % 10);
write(1, &c, 1);
}
void print_solution(int *queens, int n)
{
int i;
i = 0;
while (i < n)
{
if (i > 0)
write(1, " ", 1);
print_number(queens[i]);
i++;
}
write(1, "\n", 1);
}
int is_safe(int *queens, int row, int col, int n)
{
int i;
(void)n;
i = 0;
while (i < row)
{
if (queens[i] == col)
return (0);
if (queens[i] - i == col - row)
return (0);
if (queens[i] + i == col + row)
return (0);
i++;
}
return (1);
}
void solve_nqueens(int *queens, int row, int n)
{
int col;
if (row == n)
{
print_solution(queens, n);
return ;
}
col = 0;
while (col < n)
{
if (is_safe(queens, row, col, n))
{
queens[row] = col;
solve_nqueens(queens, row + 1, n);
}
col++;
}
}
int main(int argc, char **argv)
{
int n;
int *queens;
if (argc != 2)
return (1);
n = atoi(argv[1]);
if (n <= 0)
return (1);
queens = (int *)malloc(sizeof(int) * n);
if (!queens)
return (1);
solve_nqueens(queens, 0, n);
free(queens);
return (0);
}

View File

@ -0,0 +1,13 @@
#ifndef N_QUEENS_H
# define N_QUEENS_H
# include <stdlib.h>
# include <unistd.h>
# include <stdio.h>
int is_safe(int *queens, int row, int col, int n);
void solve_nqueens(int *queens, int row, int n);
void print_solution(int *queens, int n);
void print_number(int n);
#endif

View File

@ -0,0 +1,28 @@
Assignement name : n_queens
Expected files : *.c *.h
Allowed functions : atoi, fprintf, write, calloc, malloc, free, realloc, stdout, stderr
-------------------------------------------------------------------------
Write a program that will print all the solutions to the n queens problem
for a n given as argument.
We will not test with negative values.
The order of the solutions is not important.
You will display the solutions under the following format :
<p1> <p2> <p3> ... \n
where pn are the line index of the queen in each colum starting from 0.
For example this should work :
$> ./n_queens 2 | cat -e
$> ./n_queens 4 | cat -e
1 3 0 2$
2 0 3 1$
$> ./n_queens 7 | cat -e
0 2 4 6 1 3 5$
0 3 6 2 5 1 4$
etc...

View File

@ -0,0 +1,25 @@
NAME = permutations
CC = gcc
CFLAGS = -Wall -Wextra -Werror
SRCS = permutations.c
OBJS = $(SRCS:.c=.o)
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $(CFLAGS) -o $(NAME) $(OBJS)
%.o: %.c permutations.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS)
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY: all clean fclean re

View File

@ -0,0 +1,200 @@
# Permutations Exercise - Simple Explanation 🔄
## What is a Permutation? 🤔
Imagine you have some letters, like the letters in your name. A **permutation** is just a fancy word for "all the different ways you can arrange those letters."
Think of it like this:
- If you have the letters **A** and **B**, you can arrange them as **AB** or **BA**
- If you have **A**, **B**, and **C**, you can make **ABC**, **ACB**, **BAC**, **BCA**, **CAB**, **CBA**
It's like having alphabet blocks and seeing how many different words you can spell by changing the order!
## The Challenge 🎯
Your mission is to write a program that:
1. Takes a word (like "abc")
2. Shows ALL possible ways to rearrange the letters
3. Shows them in **alphabetical order** (like in a dictionary)
## Real Examples 📝
Let's see what our program does:
### Example 1: Single Letter
```bash
./permutations a
```
**Output:** `a`
*Why?* There's only one letter, so there's only one way to arrange it!
### Example 2: Two Letters
```bash
./permutations ab
```
**Output:**
```
ab
ba
```
*Why?* We can put 'a' first or 'b' first. That's it!
### Example 3: Three Letters
```bash
./permutations abc
```
**Output:**
```
abc
acb
bac
bca
cab
cba
```
*Why?* Think of it step by step:
- Start with 'a': we can make **abc** and **acb**
- Start with 'b': we can make **bac** and **bca**
- Start with 'c': we can make **cab** and **cba**
## How Our Code Works 🛠️
### Step 1: Getting Ready
```c
// We take the word you give us
char *str = argv[1]; // This is your word like "abc"
// We make a copy so we don't mess up the original
copy = malloc(sizeof(char) * (len + 1));
```
### Step 2: Sorting First (The Secret Sauce! ✨)
```c
ft_sort_string(copy); // This puts letters in order: "cba" becomes "abc"
```
**Why do we sort first?**
- If someone gives us "cba", we sort it to "abc" first
- This way, we always start with letters in alphabetical order
- Then when we make all arrangements, they come out in the right order!
### Step 3: The Magic Swapping Function
```c
void ft_swap(char *a, char *b)
{
char temp = *a; // Remember the first letter
*a = *b; // Put the second letter in first position
*b = temp; // Put the first letter in second position
}
```
**What's swapping?** It's like switching two cards in your hand:
- You have cards **A** and **B**
- After swapping, you have **B** and **A**
### Step 4: The Next Permutation Magic 🪄
```c
int next_permutation(char *str, int len)
{
int i = len - 2;
while (i >= 0 && str[i] >= str[i + 1]) // Find rightmost character smaller than next
i--;
if (i < 0)
return (0); // No more permutations
int j = len - 1;
while (str[j] <= str[i]) // Find rightmost character greater than str[i]
j--;
ft_swap(&str[i], &str[j]); // Swap them
ft_reverse(str, i + 1, len - 1); // Reverse the suffix
return (1); // Successfully generated next permutation
}
```
**How does this work?** Think of it like counting in a special way:
1. **Find** the rightmost place where we can "increment" (make it bigger)
2. **Swap** with the next bigger character available
3. **Reverse** everything after that position to get the smallest arrangement
4. **Repeat** until no more permutations exist
It's like a smart odometer that counts through all possible letter arrangements in perfect alphabetical order!
### Step 5: The Main Loop 🔄
```c
ft_sort_string(copy); // Start with alphabetically first arrangement
puts(copy); // Print the first permutation
while (next_permutation(copy, len)) // While there are more permutations
puts(copy); // Print each one
```
**How does this work?**
1. **Start** with the smallest (first) arrangement: "abc"
2. **Print** it
3. **Generate** the next alphabetical arrangement using `next_permutation`
4. **Print** it
5. **Repeat** until `next_permutation` says "no more!"
It's like flipping through a dictionary - each page (permutation) comes in perfect alphabetical order!
## Why This Approach is Cool 😎
### The Step-by-Step Process 📋
When we have "abc", our program works like this:
```
1. Start: abc (sorted, print it!)
2. Next: acb (swap 'b' and 'c', print it!)
3. Next: bac (find next in alphabetical order, print it!)
4. Next: bca (continue the pattern, print it!)
5. Next: cab (keep going, print it!)
6. Next: cba (last one, print it!)
7. Done: No more permutations possible
```
**The Magic:** Each step finds the next arrangement that would come after the current one in a dictionary! It's like having a super-smart assistant who knows exactly what comes next alphabetically.
### Memory Management 🧠
```c
copy = malloc(sizeof(char) * (len + 1)); // Ask for memory
// ... do our work ...
free(copy); // Give memory back when done
```
We're polite programmers - we clean up after ourselves!
### Error Handling 🛡️
```c
if (argc != 2) // Did you give us exactly one word?
return (1); // If not, we quit
if (!copy) // Did we get the memory we asked for?
return (1); // If not, we quit safely
```
We check if things went wrong and handle it gracefully.
## Fun Facts! 🎉
- With 1 letter: **1** permutation
- With 2 letters: **2** permutations
- With 3 letters: **6** permutations
- With 4 letters: **24** permutations
- With 5 letters: **120** permutations
**Pattern?** For n letters, you get n × (n-1) × (n-2) × ... × 1 permutations!
## Try It Yourself! 🚀
1. Compile the program: `gcc -Wall -Wextra -Werror permutations.c -o permutations`
2. Try it with your name: `./permutations john`
3. Try it with short words: `./permutations cat`
4. See how the results are always in alphabetical order!
Remember: The computer is doing exactly what we told it to do, step by step, just like following a recipe! 👨‍🍳
---
*Made with ❤️ for curious minds who want to understand how permutations work!*

View File

@ -0,0 +1,98 @@
#include "permutations.h"
int ft_strlen(char *str)
{
int len;
len = 0;
while (str[len])
len++;
return (len);
}
void ft_swap(char *a, char *b)
{
char temp;
temp = *a;
*a = *b;
*b = temp;
}
void ft_sort_string(char *str)
{
int i;
int j;
int len;
len = ft_strlen(str);
i = 0;
while (i < len - 1)
{
j = i + 1;
while (j < len)
{
if (str[i] > str[j])
ft_swap(&str[i], &str[j]);
j++;
}
i++;
}
}
void ft_reverse(char *str, int start, int end)
{
while (start < end)
{
ft_swap(&str[start], &str[end]);
start++;
end--;
}
}
int next_permutation(char *str, int len)
{
int i;
int j;
i = len - 2;
while (i >= 0 && str[i] >= str[i + 1])
i--;
if (i < 0)
return (0);
j = len - 1;
while (str[j] <= str[i])
j--;
ft_swap(&str[i], &str[j]);
ft_reverse(str, i + 1, len - 1);
return (1);
}
int main(int argc, char **argv)
{
char *str;
char *copy;
int len;
int i;
if (argc != 2)
return (1);
str = argv[1];
len = ft_strlen(str);
copy = malloc(sizeof(char) * (len + 1));
if (!copy)
return (1);
i = 0;
while (i < len)
{
copy[i] = str[i];
i++;
}
copy[len] = '\0';
ft_sort_string(copy);
puts(copy);
while (next_permutation(copy, len))
puts(copy);
free(copy);
return (0);
}

View File

@ -0,0 +1,14 @@
#ifndef PERMUTATIONS_H
# define PERMUTATIONS_H
# include <stdlib.h>
# include <unistd.h>
# include <stdio.h>
void ft_swap(char *a, char *b);
void ft_sort_string(char *str);
void ft_reverse(char *str, int start, int end);
int next_permutation(char *str, int len);
int ft_strlen(char *str);
#endif

View File

@ -0,0 +1,27 @@
Assignment name : permutations
Expected files : *.c *.h
Allowed functions: puts, malloc, calloc, realloc, free, write
---------------------------------------------------------------
Write a program that will print all the permutations of a string given as argument.
The solutions must be given in alphabetical order.
We will not try your program with strings containing duplicates (eg: 'abccd').
For example this should work:
$> ./permutations a | cat -e
a$
$> ./permutations ab | cat -e
ab$
ba$
$> ./permutations abc | cat -e
abc$
acb$
bac$
bca$
cab$
cba$

71
level-2/powerset/Makefile Normal file
View File

@ -0,0 +1,71 @@
# Program name
NAME = powerset
# Compiler and flags
CC = gcc
CFLAGS = -Wall -Wextra -Werror
# Source files
SRCS = powerset.c
OBJS = $(SRCS:.c=.o)
# Header files
HEADERS = powerset.h
# Colors for output
GREEN = \033[0;32m
RED = \033[0;31m
RESET = \033[0m
# Default target
all: $(NAME)
# Build the program
$(NAME): $(OBJS)
@echo "$(GREEN)Linking $(NAME)...$(RESET)"
@$(CC) $(CFLAGS) $(OBJS) -o $(NAME)
@echo "$(GREEN)$(NAME) compiled successfully!$(RESET)"
# Compile source files to object files
%.o: %.c $(HEADERS)
@echo "$(GREEN)Compiling $<...$(RESET)"
@$(CC) $(CFLAGS) -c $< -o $@
# Clean object files
clean:
@echo "$(RED)Cleaning object files...$(RESET)"
@rm -f $(OBJS)
# Clean everything
fclean: clean
@echo "$(RED)Cleaning $(NAME)...$(RESET)"
@rm -f $(NAME)
# Rebuild everything
re: fclean all
# Test the program with the examples from the subject
test: $(NAME)
@echo "$(GREEN)Running tests...$(RESET)"
@echo "\n$(GREEN)Test 1: powerset 3 1 0 2 4 5 3$(RESET)"
@./$(NAME) 3 1 0 2 4 5 3
@echo "\n$(GREEN)Test 2: powerset 12 5 2 1 8 4 3 7 11$(RESET)"
@./$(NAME) 12 5 2 1 8 4 3 7 11
@echo "\n$(GREEN)Test 3: powerset 0 1 -1$(RESET)"
@./$(NAME) 0 1 -1
@echo "\n$(GREEN)Test 4: powerset 7 3 8 2 (should be empty)$(RESET)"
@./$(NAME) 7 3 8 2
@echo "$(GREEN)✓ All tests completed!$(RESET)"
# Help
help:
@echo "Available targets:"
@echo " all - Build the program (default)"
@echo " clean - Remove object files"
@echo " fclean - Remove object files and executable"
@echo " re - Rebuild everything"
@echo " test - Run test cases"
@echo " help - Show this help message"
# Declare phony targets
.PHONY: all clean fclean re test help

175
level-2/powerset/README.md Normal file
View File

@ -0,0 +1,175 @@
# Powerset Exercise - Simple Explanation 🧮
## What is this exercise about?
Imagine you have a bag of numbered balls, and you want to find all the different ways you can pick some balls so that the numbers on them add up to a specific target number.
This is exactly what the **powerset** exercise does!
## Real-world example 🎯
Let's say you have these numbered balls: **1, 2, 3, 4, 5**
And you want to find all the ways to pick balls that add up to **5**.
Here are all the possible ways:
- Pick just ball **5** → 5 = 5 ✅
- Pick balls **1** and **4** → 1 + 4 = 5 ✅
- Pick balls **2** and **3** → 2 + 3 = 5 ✅
So the answer would be:
```
5
1 4
2 3
```
## How does our program work? 🤖
### Step 1: Understanding the input
When you run the program like this:
```bash
./powerset 5 1 2 3 4 5
```
- **5** is our target number (what we want the balls to add up to)
- **1 2 3 4 5** are our numbered balls
### Step 2: The magic behind the scenes
Our program is like a smart robot that tries **every possible combination**:
1. **Try no balls at all** → sum = 0 (not 5, so skip)
2. **Try just ball 1** → sum = 1 (not 5, so skip)
3. **Try just ball 2** → sum = 2 (not 5, so skip)
4. **Try just ball 3** → sum = 3 (not 5, so skip)
5. **Try just ball 4** → sum = 4 (not 5, so skip)
6. **Try just ball 5** → sum = 5 ✅ **FOUND ONE!**
7. **Try balls 1 and 2** → sum = 3 (not 5, so skip)
8. **Try balls 1 and 3** → sum = 4 (not 5, so skip)
9. **Try balls 1 and 4** → sum = 5 ✅ **FOUND ANOTHER!**
10. **Try balls 2 and 3** → sum = 5 ✅ **FOUND ANOTHER!**
...and so on until it tries every possible combination!
## Code explanation 📝
### The main parts of our code:
#### 1. Reading the input (`main` function)
```c
target = atoi(argv[1]); // Get the target number (5 in our example)
set[i] = atoi(argv[i + 2]); // Get each ball number (1, 2, 3, 4, 5)
```
#### 2. The smart robot (`find_subsets` function)
This function is like our robot that tries every combination:
```c
// For each ball, we have 2 choices:
find_subsets(..., index + 1); // Don't pick this ball
subset[subset_size] = set[index]; // Pick this ball
find_subsets(..., subset_size + 1, ...); // Continue with this ball included
```
#### 3. Checking if we found a winner (`print_subset`)
```c
if (current_sum == target) // If the sum equals our target
print_subset(subset, subset_size); // Show this combination!
```
## More examples to understand better 🎲
### Example 1: Finding combinations that sum to 3
```bash
./powerset 3 1 0 2 4 5 3
```
**What the robot finds:**
- Ball **3** alone → 3 ✅
- Balls **0** and **3** → 0 + 3 = 3 ✅
- Balls **1** and **2** → 1 + 2 = 3 ✅
- Balls **1**, **0**, and **2** → 1 + 0 + 2 = 3 ✅
**Output:**
```
3
0 3
1 2
1 0 2
```
### Example 2: Finding the empty combination
```bash
./powerset 0 1 -1
```
**What the robot finds:**
- No balls picked → sum = 0 ✅ (shows as empty line)
- Balls **1** and **-1** → 1 + (-1) = 0 ✅
**Output:**
```
1 -1
```
(Notice the empty line at the top!)
### Example 3: When nothing works
```bash
./powerset 7 3 8 2
```
**What the robot finds:**
- No combination of 3, 8, and 2 can make 7
- So nothing gets printed (empty output)
## Important rules 📏
1. **Order matters**: We always keep balls in the same order as given
- ✅ Correct: `1 4` (1 comes before 4 in input)
- ❌ Wrong: `4 1` (this changes the order)
2. **No duplicates**: Each ball can only be used once per combination
3. **Empty combination counts**: If target is 0, picking no balls is valid!
## How to use the program 🚀
1. **Compile it:**
```bash
make
```
2. **Run tests:**
```bash
make test
```
3. **Try your own examples:**
```bash
./powerset [target_number] [ball1] [ball2] [ball3] ...
```
4. **Clean up:**
```bash
make clean # Remove temporary files
make fclean # Remove everything
```
## Fun challenge! 🎮
Try to predict what this will output before running it:
```bash
./powerset 6 1 2 3 4
```
**Think about it:**
- Which combinations of 1, 2, 3, 4 add up to 6?
- Remember: order matters and each number can only be used once!
**Answer:** `2 4` and `1 2 3` (because 2+4=6 and 1+2+3=6)
---
*Now you understand how the powerset exercise works! It's like having a super-smart robot that can instantly try every possible combination of numbers to find the ones that add up to your target. Pretty cool, right?* 🤖✨

View File

@ -0,0 +1,73 @@
#include "powerset.h"
void print_subset(int *subset, int size)
{
int i;
i = 0;
while (i < size)
{
printf("%d", subset[i]);
if (i < size - 1)
printf(" ");
i++;
}
printf("\n");
}
void find_subsets(int *set, int set_size, int target, int *subset,
int subset_size, int index)
{
int current_sum;
int i;
if (index == set_size)
{
current_sum = 0;
i = 0;
while (i < subset_size)
{
current_sum += subset[i];
i++;
}
if (current_sum == target)
print_subset(subset, subset_size);
return ;
}
find_subsets(set, set_size, target, subset, subset_size, index + 1);
subset[subset_size] = set[index];
find_subsets(set, set_size, target, subset, subset_size + 1, index + 1);
}
int main(int argc, char **argv)
{
int *set;
int *subset;
int target;
int set_size;
int i;
if (argc < 2)
return (1);
target = atoi(argv[1]);
set_size = argc - 2;
set = malloc(sizeof(int) * set_size);
if (!set)
return (1);
subset = malloc(sizeof(int) * set_size);
if (!subset)
{
free(set);
return (1);
}
i = 0;
while (i < set_size)
{
set[i] = atoi(argv[i + 2]);
i++;
}
find_subsets(set, set_size, target, subset, 0, 0);
free(set);
free(subset);
return (0);
}

View File

@ -0,0 +1,12 @@
#ifndef POWERSET_H
# define POWERSET_H
# include <stdlib.h>
# include <unistd.h>
# include <stdio.h>
void find_subsets(int *set, int set_size, int target, int *subset,
int subset_size, int index);
void print_subset(int *subset, int size);
#endif

View File

@ -0,0 +1,65 @@
Assignment name : powerset
Expected files : *.c *.h
Allowed functions: atoi, printf, fprintf, malloc, calloc, realloc, free, stdout,
write
--------------------------------------------------------------------------------
Write a program that will take as argument an integer n followed by a set s of
distinct integers.
Your program should display all the subsets of s whose sum of elements is n.
The order of the lines is not important, but the order of the elements in a subset is:
it must match the order in the initial set s.
This way, you should not have any duplicates (eg: '1 2' and '2 1').
For example, using the command ./powerset 5 1 2 3 4 5
this output is valid:
1 4
2 3
5
this one is also valid:
2 3
5
1 4
but not this one:
4 1
3 2
5
In case of a malloc error your program will exit with the code 1.
We will not test with invalid sets (for example '1 1 2').
Hint: the empty subset is a valid subset of any set. It will be displayed as an empty line.
For example this should work:
$> ./powerset 3 1 0 2 4 5 3 | cat -e
3$
0 3$
1 2$
1 0 2$
$> ./powerset 12 5 2 1 8 4 3 7 11 | cat -e
8 4$
1 11$
1 4 7$
1 8 3$
2 3 7$
5 7$
5 4 3$
5 2 1 4$
$> ./powerset 0 1 -1 | cat -e
$
1 -1$
$> ./powerset 7 3 8 2 | cat -e
// Other tests:
$> ./powerset 100 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | cat -e
...
$> ./powerset -1 1 2 3 4 5 -10 | cat -e
...
$> ./powerset 0 -1 1 2 3 -2 | cat -e
...
$> ./powerset 13 65 23 3 4 6 7 1 2 | cat -e
...
$> ./powerset 10 0 1 2 3 4 5 6 7 8 9 | cat -e
...

80
level-2/rip/Makefile Normal file
View File

@ -0,0 +1,80 @@
# Program name
NAME = rip
# Compiler and flags
CC = gcc
CFLAGS = -Wall -Wextra -Werror
# Source files
SRCS = rip.c
# Object files
OBJS = $(SRCS:.c=.o)
# Colors for output
GREEN = \033[0;32m
RED = \033[0;31m
YELLOW = \033[0;33m
NC = \033[0m # No Color
# Default target
all: $(NAME)
# Build the executable
$(NAME): $(OBJS)
@echo "$(YELLOW)Linking $(NAME)...$(NC)"
@$(CC) $(CFLAGS) $(OBJS) -o $(NAME)
@echo "$(GREEN)$(NAME) created successfully!$(NC)"
# Compile source files to object files
%.o: %.c
@echo "$(YELLOW)Compiling $<...$(NC)"
@$(CC) $(CFLAGS) -c $< -o $@
# Clean object files
clean:
@echo "$(RED)🧹 Cleaning object files...$(NC)"
@rm -f $(OBJS)
@echo "$(GREEN)✅ Object files cleaned!$(NC)"
# Clean object files and executable
fclean: clean
@echo "$(RED)🧹 Cleaning executable...$(NC)"
@rm -f $(NAME)
@echo "$(GREEN)✅ Everything cleaned!$(NC)"
# Rebuild everything
re: fclean all
# Test the program with provided examples
test: $(NAME)
@echo "$(YELLOW)🧪 Running tests...$(NC)"
@echo "$(YELLOW)Test 1: '(()'$(NC)"
@./$(NAME) '(((' | cat -e
@echo "$(YELLOW)Test 2: '((()()())())'$(NC)"
@./$(NAME) '((()()())())' | cat -e
@echo "$(YELLOW)Test 3: '()())()''$(NC)"
@./$(NAME) '()())()' | cat -e
@echo "$(YELLOW)Test 4: '(()(()('$(NC)"
@./$(NAME) '(()(()(' | cat -e
@echo "$(GREEN)✅ All tests completed!$(NC)"
# Check for memory leaks (requires valgrind)
valgrind: $(NAME)
@echo "$(YELLOW)🔍 Checking for memory leaks...$(NC)"
@valgrind --leak-check=full --show-leak-kinds=all ./$(NAME) '(()'
@echo "$(GREEN)✅ Memory check completed!$(NC)"
# Show help
help:
@echo "$(GREEN)Available targets:$(NC)"
@echo " $(YELLOW)all$(NC) - Build the program"
@echo " $(YELLOW)clean$(NC) - Remove object files"
@echo " $(YELLOW)fclean$(NC) - Remove object files and executable"
@echo " $(YELLOW)re$(NC) - Rebuild everything (fclean + all)"
@echo " $(YELLOW)test$(NC) - Run all test cases"
@echo " $(YELLOW)valgrind$(NC) - Check for memory leaks"
@echo " $(YELLOW)help$(NC) - Show this help message"
# Declare phony targets
.PHONY: all clean fclean re test valgrind help

179
level-2/rip/README.md Normal file
View File

@ -0,0 +1,179 @@
# The Parentheses Balancing Problem (RIP Exercise)
## What is this exercise about?
Imagine you have a string of parentheses like this: `"(()"`.
Think of parentheses like **doors** in a building:
- `(` is like **opening a door**
- `)` is like **closing a door**
For everything to work properly, every door you open must have a matching door that closes it!
## The Problem
Sometimes we get strings where the doors don't match properly. For example:
- `"((("` - We opened 3 doors but never closed any!
- `"())"` - We opened 1 door, closed 1 door, but then tried to close another door that was never opened!
Our job is to **fix** these strings by removing the minimum number of parentheses (but replacing them with spaces instead of deleting them completely).
## Real-World Example
Let's say you have: `"(()"`
This means:
1. Open door 1: `(`
2. Open door 2: `(`
3. Close door 2: `)`
4. But door 1 is still open! 😱
To fix this, we need to either:
- Remove one of the opening doors: ` ()` (remove first `(`)
- Or remove one of the opening doors: `( )` (remove second `(`)
Both solutions work!
## How Our Program Works
### Step 1: Count the Problems
```c
// This function counts how many parentheses we need to remove
void calculate_removals(char *str, int *left_rem, int *right_rem)
{
int left = 0; // Count of unmatched opening parentheses
int right = 0; // Count of unmatched closing parentheses
// Go through each character
while (str[i])
{
if (str[i] == '(')
left++; // Found an opening door
else if (str[i] == ')')
{
if (left > 0)
left--; // Found a closing door for an open one
else
right++; // Found a closing door with no matching open door
}
}
}
```
**Example with `"(()"`:**
- Start: `left = 0`, `right = 0`
- See `(`: `left = 1` (one unmatched opening)
- See `(`: `left = 2` (two unmatched openings)
- See `)`: `left = 1` (one opening got matched)
- End: `left = 1`, `right = 0`
So we need to remove **1 opening parenthesis**.
### Step 2: Try All Possible Solutions
Think of it like trying different combinations:
```c
// This function tries removing different parentheses to find all valid solutions
void solve(char *s, int pos, int left_rem, int right_rem, int open, char *path)
{
// If we've looked at all characters
if (pos == len)
{
// Check if we removed exactly what we needed and everything balances
if (left_rem == 0 && right_rem == 0 && open == 0)
add_result(results, path); // This is a valid solution!
return;
}
// Try removing this parenthesis (replace with space)
if (s[pos] == '(' && left_rem > 0)
{
path[pos] = ' '; // Replace with space
solve(s, pos + 1, left_rem - 1, right_rem, open, path);
}
// Try keeping this parenthesis
path[pos] = s[pos];
if (s[pos] == '(')
solve(s, pos + 1, left_rem, right_rem, open + 1, path);
// ... and so on
}
```
### Step 3: Avoid Duplicates
We use a list to store all unique solutions:
```c
// This makes sure we don't print the same solution twice
void add_result(t_result **results, char *str)
{
// Check if we already have this solution
current = *results;
while (current)
{
if (ft_strcmp(current->str, str) == 0)
return; // Already have it, don't add again
current = current->next;
}
// It's new, so add it to our list
new->str = ft_strdup(str);
new->next = *results;
*results = new;
}
```
## Example Walkthrough
Let's trace through `"(()"` step by step:
### Initial Analysis:
- Need to remove 1 opening parenthesis (`left_rem = 1`)
- Need to remove 0 closing parentheses (`right_rem = 0`)
### Trying Different Positions:
**Position 0 (first `(`):**
- Try removing it: `" ()"`
- Check: removed 1 opening ✓, no unmatched doors ✓
- **Valid solution!**
**Position 1 (second `(`):**
- Try removing it: `"( )"`
- Check: removed 1 opening ✓, no unmatched doors ✓
- **Valid solution!**
**Position 2 (the `)`):**
- Can't remove it (we need to remove openings, not closings)
### Final Output:
```
()
( )
```
## More Complex Example: `"()())()"`
This string has:
- 3 opening doors: `(`, `(`, `(`
- 4 closing doors: `)`, `)`, `)`, `)`
So we have 1 extra closing door that needs to be removed.
**All possible solutions:**
- Remove closing at position 2: `"()( )()"`
- Remove closing at position 3: `"()() ()"`
- Remove closing at position 4: `"( ())()"`
## Why This Exercise is Useful
This problem teaches us:
1. **Problem-solving**: Break down complex problems into smaller steps
2. **Recursion**: Try all possibilities systematically
3. **Data structures**: Use lists to store and manage results
4. **Optimization**: Find the minimum changes needed
It's like being a **door inspector** - you need to make sure every building (string) has properly matched doors (parentheses) with the minimum number of changes!

180
level-2/rip/rip.c Normal file
View File

@ -0,0 +1,180 @@
#include <unistd.h>
#include <stdlib.h>
typedef struct s_result
{
char *str;
struct s_result *next;
} t_result;
int ft_strlen(char *str)
{
int len;
len = 0;
while (str[len])
len++;
return (len);
}
int ft_strcmp(char *s1, char *s2)
{
int i;
i = 0;
while (s1[i] && s2[i] && s1[i] == s2[i])
i++;
return (s1[i] - s2[i]);
}
char *ft_strdup(char *src)
{
char *dup;
int len;
int i;
len = ft_strlen(src);
dup = malloc(len + 1);
if (!dup)
return (NULL);
i = 0;
while (i < len)
{
dup[i] = src[i];
i++;
}
dup[len] = '\0';
return (dup);
}
void add_result(t_result **results, char *str)
{
t_result *new;
t_result *current;
current = *results;
while (current)
{
if (ft_strcmp(current->str, str) == 0)
return ;
current = current->next;
}
new = malloc(sizeof(t_result));
if (!new)
return ;
new->str = ft_strdup(str);
if (!new->str)
{
free(new);
return ;
}
new->next = *results;
*results = new;
}
void print_results(t_result *results)
{
t_result *current;
int i;
current = results;
while (current)
{
i = 0;
while (current->str[i])
{
write(1, &current->str[i], 1);
i++;
}
write(1, "\n", 1);
current = current->next;
}
}
void free_results(t_result *results)
{
t_result *temp;
while (results)
{
temp = results;
results = results->next;
free(temp->str);
free(temp);
}
}
void solve(char *s, int pos, int left_rem, int right_rem, int open, char *path, t_result **results)
{
int len;
len = ft_strlen(s);
if (pos == len)
{
if (left_rem == 0 && right_rem == 0 && open == 0)
add_result(results, path);
return ;
}
if (s[pos] == '(' && left_rem > 0)
{
path[pos] = ' ';
solve(s, pos + 1, left_rem - 1, right_rem, open, path, results);
}
if (s[pos] == ')' && right_rem > 0)
{
path[pos] = ' ';
solve(s, pos + 1, left_rem, right_rem - 1, open, path, results);
}
path[pos] = s[pos];
if (s[pos] == '(')
solve(s, pos + 1, left_rem, right_rem, open + 1, path, results);
else if (s[pos] == ')' && open > 0)
solve(s, pos + 1, left_rem, right_rem, open - 1, path, results);
else
solve(s, pos + 1, left_rem, right_rem, open, path, results);
}
void remove_invalid_parentheses(char *s)
{
int left_rem;
int right_rem;
int len;
char *path;
int i;
t_result *results;
left_rem = 0;
right_rem = 0;
len = ft_strlen(s);
results = NULL;
i = 0;
while (s[i])
{
if (s[i] == '(')
left_rem++;
else if (s[i] == ')')
{
if (left_rem > 0)
left_rem--;
else
right_rem++;
}
i++;
}
path = malloc(len + 1);
if (!path)
return ;
path[len] = '\0';
solve(s, 0, left_rem, right_rem, 0, path, &results);
print_results(results);
free_results(results);
free(path);
}
int main(int argc, char **argv)
{
if (argc != 2)
return (1);
remove_invalid_parentheses(argv[1]);
return (0);
}

28
level-2/rip/subject.txt Normal file
View File

@ -0,0 +1,28 @@
Assignment name : rip
Expected files : *.c *.h
Allowed functions: puts, write
--------------------------------------------------------------------------------
Write a program that will take as argument a string containing only parenthesis.
if the parenthesis are unbalanced (for example "())")
your program shall remove the minimum number of parentheses for the expression to be balanced.
By removing we mean replacing by spaces.
You will then print all the solutions (can be more than one).
The order of the solutions is not important.
For example this should work:
$> ./rip '(()' | cat -e
()$
( )$
$> ./rip '((()()())())' | cat -e
((()()())())$
$> ./rip '()())()'| cat -e
()() ()$
()( )()$
( ())()$
$> ./rip '(()(()(' | cat -e
(() ) $
( )( ) $
( ) () $
()( ) $

26
level-2/tsp/Makefile Normal file
View File

@ -0,0 +1,26 @@
NAME = tsp
SRCS = tsp.c
OBJS = $(SRCS:.c=.o)
CC = gcc
CFLAGS = -Wall -Wextra -Werror
MATH_FLAGS = -lm
all: $(NAME)
$(NAME): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(NAME) $(MATH_FLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS)
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY: all clean fclean re

View File

@ -0,0 +1,247 @@
# The Traveling Salesman Problem (TSP) - Simple Explanation
## What is the Traveling Salesman Problem? 🗺️
Imagine you're a delivery person who needs to visit several houses in your neighborhood and then return home. You want to find the **shortest possible route** that visits every house exactly once and brings you back to where you started.
That's exactly what the Traveling Salesman Problem is about!
### Real-World Example
Let's say you need to deliver pizza to 4 houses:
- Your home: (0, 0)
- House A: (1, 0)
- House B: (1, 1)
- House C: (0, 1)
```
C ---- B
| |
| |
Home - A
```
You could go in many different orders:
- Home → A → B → C → Home
- Home → A → C → B → Home
- Home → B → A → C → Home
- ... and many more!
But which path is the shortest? That's what our algorithm finds out!
## How Our Algorithm Works 🔍
Our solution uses a "brute force" approach, which means:
**"Let's try EVERY possible path and pick the shortest one!"**
It's like trying on every shirt in your closet to find the one that fits best - not the fastest way, but it guarantees you'll find the perfect answer!
### Step 1: Calculate Distance Between Two Points
First, we need to know how far apart two places are:
```c
static float distance(t_city a, t_city b)
{
float dx = b.x - a.x; // How far left/right?
float dy = b.y - a.y; // How far up/down?
return (sqrtf(dx * dx + dy * dy)); // Pythagorean theorem!
}
```
**What's happening here?**
- `dx` = horizontal distance (like counting steps left or right)
- `dy` = vertical distance (like counting steps up or down)
- `sqrtf(dx*dx + dy*dy)` = the straight-line distance (like a bird flying)
**Example:**
From Home (0,0) to House A (1,0):
- dx = 1 - 0 = 1
- dy = 0 - 0 = 0
- distance = √(1² + 0²) = √1 = 1.0
### Step 2: Calculate Total Path Length
Now we add up all the distances in a complete round trip:
```c
static float path_length(t_city *cities, int *route, int n)
{
float total = 0;
int i = 0;
while (i < n)
{
// Add distance from current city to next city
total += distance(cities[route[i]], cities[route[(i + 1) % n]]);
i++;
}
return (total);
}
```
**What's that weird `% n` thing?**
It's like a clock! When you get to hour 12, the next hour is 1, not 13.
- When `i = 3` (last city) and `n = 4` (total cities)
- `(i + 1) % n = (3 + 1) % 4 = 4 % 4 = 0`
- So we go from the last city back to the first city (completing the circle!)
### Step 3: Try All Possible Routes
This is where the magic happens! We generate every possible order of visiting the cities:
```c
static void solve(t_city *cities, int *route, int pos, int n, float *best)
{
float current;
int i, temp;
// If we've arranged all cities, check this route
if (pos == n)
{
current = path_length(cities, route, n);
if (current < *best)
*best = current; // Found a better route!
return;
}
// Try putting each remaining city in the current position
i = pos;
while (i < n)
{
// Swap cities (like switching the order of your visits)
temp = route[pos];
route[pos] = route[i];
route[i] = temp;
// Try all arrangements with this city in this position
solve(cities, route, pos + 1, n, best);
// Swap back (undo the change)
temp = route[pos];
route[pos] = route[i];
route[i] = temp;
i++;
}
}
```
**Think of it like this:**
- Position 0: "Which city should I visit first?"
- Position 1: "Which city should I visit second?"
- Position 2: "Which city should I visit third?"
- And so on...
For each position, we try putting each remaining city there and see what happens!
## Complete Example Walkthrough 🚶‍♂️
Let's trace through a simple 3-city example:
- City 0: (0, 0)
- City 1: (1, 0)
- City 2: (0, 1)
```
2
|
|
0 --- 1
```
**All possible routes:**
1. 0 → 1 → 2 → 0: distance = 1 + √2 + 1 = 3.41
2. 0 → 2 → 1 → 0: distance = 1 + √2 + 1 = 3.41
3. 1 → 0 → 2 → 1: distance = 1 + 1 + √2 = 3.41
4. 1 → 2 → 0 → 1: distance = √2 + 1 + 1 = 3.41
5. 2 → 0 → 1 → 2: distance = 1 + 1 + √2 = 3.41
6. 2 → 1 → 0 → 2: distance = √2 + 1 + 1 = 3.41
**Result:** All routes have the same length! (This makes sense - it's a triangle, so any direction around it is the same distance.)
## The Main Program 🏠
```c
int main(void)
{
t_city cities[11]; // Space for up to 11 cities
int route[11]; // The order we'll visit cities
int n = 0; // How many cities we actually have
float best = 999999.0f; // Start with a really big number
// Read city coordinates from input
while (n < 11 && fscanf(stdin, "%f, %f", &cities[n].x, &cities[n].y) == 2)
{
route[n] = n; // Initially: visit cities in order 0,1,2,3...
n++;
}
if (n < 2)
return (1); // Need at least 2 cities for a trip!
solve(cities, route, 0, n, &best); // Find the best route
fprintf(stdout, "%.2f\n", best); // Print the shortest distance
return (0);
}
```
## Why This Works (But Is Slow) ⏰
**The Good:**
- ✅ **Always finds the perfect answer** - we check every possibility!
- ✅ **Simple to understand** - no complex tricks or shortcuts
- ✅ **Works for any arrangement of cities**
**The Not-So-Good:**
- ❌ **Gets very slow with more cities**
- For 3 cities: 6 routes to check
- For 4 cities: 24 routes to check
- For 10 cities: 3,628,800 routes to check! 😱
**Why it gets so big:**
- 1st city: 10 choices
- 2nd city: 9 choices (can't repeat)
- 3rd city: 8 choices
- ...
- Total: 10 × 9 × 8 × 7 × 6 × 5 × 4 × 3 × 2 × 1 = 3,628,800
This is called "factorial growth" - it explodes very quickly!
## Key Programming Concepts Used 💻
### 1. **Recursion**
The `solve()` function calls itself! It's like Russian nesting dolls - each call handles one position, then asks a smaller version of itself to handle the rest.
### 2. **Backtracking**
We try something (swap cities), explore all possibilities, then undo it (swap back). Like trying different paths in a maze and backing up when we hit a dead end.
### 3. **Permutations**
We generate every possible ordering of the cities. It's like having cards numbered 1,2,3 and arranging them in every possible order: 123, 132, 213, 231, 312, 321.
### 4. **Global Optimization**
We keep track of the best solution found so far and update it whenever we find something better.
## Fun Facts! 🎯
1. **This problem is NP-hard** - that's computer science speak for "really, really hard to solve quickly"
2. **Real delivery companies** use approximate algorithms that find "pretty good" solutions much faster than perfect ones
3. **With 11 cities** (the maximum our program handles), there are 39,916,800 possible routes!
4. **The problem has applications** in:
- GPS navigation systems
- Circuit board manufacturing
- DNA sequencing
- Planning efficient tours for bands or sports teams
## Summary 📝
Our TSP algorithm is like a very thorough friend who insists on checking every possible pizza delivery route before deciding which one is shortest. It's not the fastest approach, but it guarantees the perfect answer!
The main steps are:
1. **Read** all the city locations
2. **Generate** every possible visiting order
3. **Calculate** the total distance for each order
4. **Remember** the shortest distance found
5. **Print** the answer
Even though it's not the most efficient algorithm for large problems, it's perfect for learning because it's easy to understand and always gives the correct answer! 🎉

8
level-2/tsp/square.txt Normal file
View File

@ -0,0 +1,8 @@
0, 0
1, 0
2, 0
0, 1
1, 1
2, 1
5, 2
2, 2

64
level-2/tsp/subject.txt Normal file
View File

@ -0,0 +1,64 @@
Assignment name: tsp
Expected files: *.c *.h
Allowed functions: write, sqrtf, getline, fseek, fscanf, ferror, feof,
fabsf, memcpy, fprintf, fclose, malloc, calloc, realloc, free, fopen,
errno, stderr, stdin, stdout
-------------------------------------------------------
The first publication referring to this problem as the "traveling salesman
problem" is found in the 1949 RAND Corporation report by Julia Robinson,
"On the Hamiltonian game (a traveling salesman problem)."
Here is how she defines the problem:
"The purpose of this note is to give a method for solving a problem related
to the traveling salesman problem. It seems worthwhile to give a description
of the original problem. One formulation is to find the shortest route for a
salesman starting from Washington, visiting all the state capitals and then
returning to Washington.
More generally, to find the shortest CLOSED CURVE containing n given points
in the plane."
for example with the following set of cities:
0, 0
1, 0
2, 0
0, 1
1, 1
2, 1
1, 2
2, 2
which can be presented as follows:
+ + +
+ + +
+ +
the shortest path is:
_____
|__ |
|__|
so you should print the length of this path that is:
8.00
Write a program that will read a set of city coordinates in the form
'%f, %f\n' from the standard input and will print the length of the shortest
possible path containing all these cities under the form '%.2f'.
Your program will not be tested with more than 11 cities.
You will find in this directory a file tsp.c containing all the boring parts of
this exercise and example files to help you test your program.
hint: in order to use sqrtf, add -lm at the end of your compilation command.
For example this should work:
$> cat square.txt
1, 1
0, 1
1, 0
0, 0
$> ./tsp < square.txt | cat -e
4.00$
Hint : in order to use sqrtf , add -lm at the end of your compilation command

66
level-2/tsp/tsp.c Normal file
View File

@ -0,0 +1,66 @@
#include "tsp.h"
static float distance(t_city a, t_city b)
{
float dx = b.x - a.x;
float dy = b.y - a.y;
return (sqrtf(dx * dx + dy * dy));
}
static float path_length(t_city *cities, int *route, int n)
{
float total = 0;
int i = 0;
while (i < n)
{
total += distance(cities[route[i]], cities[route[(i + 1) % n]]);
i++;
}
return (total);
}
static void solve(t_city *cities, int *route, int pos, int n, float *best)
{
float current;
int i, temp;
if (pos == n)
{
current = path_length(cities, route, n);
if (current < *best)
*best = current;
return;
}
i = pos;
while (i < n)
{
temp = route[pos];
route[pos] = route[i];
route[i] = temp;
solve(cities, route, pos + 1, n, best);
temp = route[pos];
route[pos] = route[i];
route[i] = temp;
i++;
}
}
int main(void)
{
t_city cities[11];
int route[11];
int n = 0;
float best = 999999.0f;
while (n < 11 && fscanf(stdin, "%f, %f", &cities[n].x, &cities[n].y) == 2)
{
route[n] = n;
n++;
}
if (n < 2)
return (1);
solve(cities, route, 0, n, &best);
fprintf(stdout, "%.2f\n", best);
return (0);
}

14
level-2/tsp/tsp.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef TSP_H
# define TSP_H
# include <stdio.h>
# include <stdlib.h>
# include <math.h>
typedef struct s_city
{
float x;
float y;
} t_city;
#endif