Partial modification

This commit is contained in:
komeno 2025-11-11 13:32:41 +09:00
parent ef29c69828
commit ca485ef86d
32 changed files with 3307 additions and 89 deletions

View File

View File

@ -0,0 +1,841 @@
---
date: 2024-02-17
---
# The Dining Philosophers Problem
## Overview
The **Dining Philosophers Problem** is a computer science problem formulated in 1965 by [Edsger Dijkstra](https://en.wikipedia.org/wiki/Edsger_W._Dijkstra). It involves dealing with concurrent programming, synchronization issues, threads, deadlocks, and race conditions.
#### The Problem
There are one or more philosophers sitting around a table, with a large bowl of spaghetti placed in the middle. In order for a philosopher to eat, they need to use both their left and right forks simultaneously. There are as many forks as there are philosophers. Additionally, the philosophers cannot communicate with each other.
## Organizing data
We will define our main data structure as:
```c
struct s_dinner
{
t_rules rules;
t_philo philos[PHILO_MAX];
pthread_mutex_t forks[PHILO_MAX];
pthread_t supervisor;
bool stop;
pthread_mutex_t stop_mutex;
int exit_status;
};
```
Here is the definition of the data structure for storing the dinning party rules:
```c
typedef struct s_rules
{
t_time start_time;
unsigned philo_count;
time_t lifespan;
time_t dining_duration;
time_t rest_duration;
unsigned min_meals;
} t_rules;
```
### The Philosopher
```c
typedef struct s_philo
{
int id;
pthread_t thread;
t_dinner *dinner;
int forks[2];
int times_eaten;
pthread_mutex_t times_eaten_mutex;
time_t last_meal_time;
pthread_mutex_t last_meal_time_mutex;
} t_philo;
```
## Starting the dinner party
Before actually start creating threads and running the philosopher routines we have to prepare the dinner party. By preparing the dinner party I mean doing some input error checking and variable initialization.
### can_prepare_dinner()
```c
bool can_prepare_dinner(t_dinner *dinner, int argc, char **argv)
{
initialize_exit_status(dinner);
if (correct_input(dinner, argc, argv))
{
set_dinner_rules(dinner, argc, argv);
if (can_initialize_forks(dinner))
{
initialize_philosophers(dinner);
if (can_initialize_stop_mutex(dinner))
if (can_initialize_print_mutex(dinner))
return (true);
}
}
return (false);
}
```
Here, we are first initializing the `exit_status` variable to a macro called `SUCCESS` that I defined to `0`:
```c
void initialize_exit_status(t_dinner *dinner)
{
dinner->exit_status = SUCCESS;
}
```
After that, I check the user's input and throw an error in the following cases:
- If the number of args is less than 4 and more than 5
- If there are non-alphabetic characters
- If `atoi()` fails converting an argument
- If a negative argument is provided
```c
bool correct_input(t_dinner *dinner, int argc, char **argv)
{
int i;
int curr_arg;
if (incorrect_num_of_args(argc, dinner))
return (false);
i = 1;
while (i < argc)
{
if (not_only_digits(argv[i], dinner))
return (false);
if (!can_convert_str_to_int(dinner, argv[i], &curr_arg))
return (false);
if (wrong_num(i, curr_arg, dinner))
return (false);
i++;
}
return (true);
}
```
After checking the input, we can insert this data into the `dinner` data structure:
```c
void set_dinner_rules(t_dinner *dinner, int argc, char **argv)
{
set_dinner_start_time(dinner);
dinner->rules.philo_count = my_atoi(argv[1]);
dinner->rules.lifespan = my_atoi(argv[2]);
dinner->rules.dining_duration = my_atoi(argv[3]);
dinner->rules.rest_duration = my_atoi(argv[4]);
if (argc == 6)
dinner->rules.min_meals = my_atoi(argv[5]);
else
dinner->rules.min_meals = MIN_MEALS_NOT_SET;
}
```
In this problem, the 5th argument is optional, so in case it is not set, we set it as `MIN_MEALS_NOT_SET`.
As there are as many forks as philosophers we can loop through the forks array `philo_count` times and initialize the mutexes:
```c
bool can_initialize_forks(t_dinner *dinner)
{
int i;
i = 0;
while (i < dinner->rules.philo_count)
{
if (pthread_mutex_init(&dinner->forks[i], NULL) != SUCCESS)
{
handle_mutex_init_failure(dinner, i);
report_and_set_error(dinner, ERR_MUTEX_INIT, MSG_MUTEX_INIT);
return (false);
}
i++;
}
return (true);
}
```
> [!note] Note
> When creating mutexes, we have to handle possible errors just like when
> using malloc. In case of an error, `pthread_mutex_create()` returns a value
> different from `0` and we have to call a function called
> `pthread_mutex_destroy()` to destroy all the created mutexes
```c
static void handle_mutex_init_failure(t_dinner *dinner, int i)
{
while (i > 0)
{
i--;
pthread_mutex_destroy(&dinner->forks[i]);
}
}
```
Now, let's initialize the philosophers array:
Where:
- The philosopher's `id` starts at 1
- `times_eaten` is initialized to 0
- `dinner` is a pointer back to the `dinner` main data structure
- `last_meal_time` is initialized to the current time
- extra mutexes for variables shared between threads like `last_meal_time` and `times_eaten` are created
```c
void initialize_philosophers(t_dinner *dinner)
{
int i;
i = 0;
while (i < dinner->rules.philo_count)
{
dinner->philos[i].id = i + 1;
dinner->philos[i].times_eaten = 0;
dinner->philos[i].dinner = dinner;
assign_forks(&dinner->philos[i]);
dinner->philos[i].last_meal_time = get_time_in_ms();
pthread_mutex_init(&dinner->philos[i].times_eaten_mutex, NULL);
pthread_mutex_init(&dinner->philos[i].last_meal_time_mutex, NULL);
i++;
}
}
```
### Determining if a philosopher is left or right handed
```c
void assign_forks(t_philo *philo)
{
if (philosopher_is_left_handed(philo))
assign_left_fork_first(philo);
else
assign_right_fork_first(philo);
}
```
When assigning forks, we first need to determine whether the philosopher takes the left fork first and then the right fork, or vice versa. To determine whether the philosopher is left or right-handed, we check their index to see if it is even or odd. If it is even, then the philosopher will be considered left-handed. This is entirely arbitrary, and I could have designated an even ID for a right-handed philosopher.
```c
static bool philosopher_is_left_handed(t_philo *philo)
{
return (philo->id % 2);
}
```
### Assigning forks
```c
static void assign_left_fork_first(t_philo *philo)
{
philo->forks[0] = philo->id;
philo->forks[1] = (philo->id + 1) % philo->dinner->rules.philo_count;
}
static void assign_right_fork_first(t_philo *philo)
{
philo->forks[1] = philo->id;
philo->forks[0] = (philo->id + 1) % philo->dinner->rules.philo_count;
}
```
So the first philosopher's left fork will be the `i` in the forks array and their right fork will be the `i + 1` fork.
The modulo operation here is used to make the last philosopher's right fork be the first fork in the array. This way we can make the table circular by making the last fork be the first fork:
let the number of philosophers be $n$ and the current philosopher id be $i + 1$:
$$
(i + 1) \mod n
$$
Imagine we have 3 philosophers:
First iteraction:
The philosopher's left fork will be $i$, or 0 and their right fork will be 1:
$$
\begin{align*}
(0 + 1) \mod 3 \\
1 \mod 3 \\
1
\end{align*}
$$
Resulting into:
```
ID: 1 L: 0 R: 1
```
Second iteraction:
Now $i = 1$ so the second philosopher's left fork will be 1 and their right fork will be 2:
$$
\begin{align*}
(1 + 1) \mod 3 \\
2 \mod 3 \\
2
\end{align*}
$$
Output:
```
ID: 2 L: 1 R: 2
```
Third iteraction:
Now $i = 2$ and since we are running this loop until $i < n$ this is the last iteraction:
So the third philosopher's left fork will be 2 and their right fork will be 0:
$$
\begin{align*}
(2 + 1) \mod 3 \\
3 \mod 3 \\
0
\end{align*}
$$
Output:
```
ID: 3 L: 2 R: 0
```
## Displaying the timestamps
In this project, we are required to use the [[gettimeofday()]] function for displaying time in microseconds ($\mu s$)
By accessing `tv_sec` and `tv_usec`, both members of `struct timeval`, we can calculate the current time in microseconds ($\mu s$) using the following formula:
$$
\begin{align*}
T_{ms} \; = \; (T_{s} \; \times \; 10^3) \; + \; \frac{T_{\mu s}}{10^3}
\end{align*}
$$
See: [[Seconds Unit Conversion]]
### Implementation
We can create 3 separate functions for each conversion seen above to get the current time in miliseconds ($ms$):
```c
time_t s_to_ms(time_t s)
{
return (s * 1000);
}
time_t us_to_ms(time_t us)
{
return (us / 1000);
}
time_t get_time_in_ms(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (s_to_ms(tv.tv_sec) +
us_to_ms(tv.tv_usec));
}
```
## Displaying and returning errors
In case of any error in this program, the error message will be displayed with a print statement protected by a mutex and return an error code.
```c
void report_and_set_error(t_dinner *dinner, int code, char *msg)
{
pthread_mutex_lock(&dinner->print_mutex);
printf("philo: %s\n", msg);
dinner->exit_status = code;
pthread_mutex_unlock(&dinner->print_mutex);
}
```
Here are the error messages of possible errors in the program:
```c
# define MSG_NUM_ARGS "Incorrect number of arguments."
# define MSG_NOT_ONLY_DIGITS "Not only digits."
# define MSG_ATOI "Atoi error."
# define MSG_NUM_PHILOS "Wrong number of philosophers."
# define MSG_NUM "Argument must be positive"
# define MSG_MUTEX_INIT "Error initializing mutex."
# define MSG_THREAD_CREATE "Error creating thread."
# define MSG_THREAD_JOIN "Error joining thread."
```
with their error codes declared into an enum:
```c
enum e_exit_status
{
SUCCESS,
ERR_NUM_ARGS,
ERR_NOT_ONLY_DIGITS,
ERR_ATOI,
ERR_NUM_PHILOS,
ERR_NUM,
ERR_MUTEX_INIT,
ERR_THREAD_CREATE,
ERR_THREAD_JOIN
};
```
## Printing the philosopher status
There are 5 kinds of messages a philosopher can print:
1. When a philosopher dies
2. When a philosopher is eating
3. When a philosopher is sleeping
4. When a philosopher is thinking
4. When a philosopher takes a fork
So let's define these messages that will be printed as string macros:
```c
# define MSG_DEAD "died"
# define MSG_EATING "is eating"
# define MSG_SLEEPING "is sleeping"
# define MSG_THINKING "is thinking"
# define MSG_TAKING_FORK "has taken a fork"
```
Then a number to represent each status:
```c
enum e_philo_status
{
DEAD,
EATING,
SLEEPING,
THINKING,
TAKING_FORK
};
```
Now, using the `print_philo_status()` function, we can select which status we want to print, then print the current status as required by the subject. Of course, we have to protect the print statement by mutexes:
```c
void print_philo_status(t_philo *philo, t_philo_status status)
{
if (check_stop_condition_safely(&philo->dinner->stop_mutex,
&philo->dinner->stop))
return ;
if (status == DEAD)
print_in_required_format(philo, MSG_DEAD);
else if (status == EATING)
print_in_required_format(philo, MSG_EATING);
else if (status == SLEEPING)
print_in_required_format(philo, MSG_SLEEPING);
else if (status == THINKING)
print_in_required_format(philo, MSG_THINKING);
else if (status == TAKING_FORK)
print_in_required_format(philo, MSG_TAKING_FORK);
}
```
Printing in the required format:
```c
static void print_in_required_format(t_philo *philo, char *action)
{
pthread_mutex_lock(&philo->dinner->print_mutex);
printf("%ld %d %s\n", get_time_in_ms() - philo->dinner->rules.start_time,
philo->id, action);
pthread_mutex_unlock(&philo->dinner->print_mutex);
}
```
## Working with threads
Now that we finished preparing the `dinner`, we can start the dinner by getting the current time (the dinner start time) and create threads for each philosopher. To create a thread we need to use the [[pthread_create()]] function.
```c
void start_dinner(t_dinner *dinner)
{
int i;
i = 0;
dinner->rules.start_time = get_time_in_ms();
while (i < dinner->rules.philo_count)
{
if (!can_create_thread(&dinner->philos[i].thread, philo_routine,
&dinner->philos[i]))
{
report_and_set_error(dinner, ERR_THREAD_CREATE, MSG_THREAD_CREATE);
return ;
}
i++;
}
}
```
Here, for readability purposes, I separeted the `pthread_create()` proccess into a separate function.
```c
bool can_create_thread(pthread_t *thread, void *(*routine)(void *),
void *arg)
{
if (pthread_create(thread, NULL, routine, arg) != SUCCESS)
return (false);
return (true);
}
```
Here, for each philosopher thread that gets created, the `philo_routine` function will get called through a function pointer.
```c
void *philo_routine(void *arg)
{
t_philo *philo;
philo = (t_philo *)arg;
align_start_times(philo->dinner->rules.start_time);
if (philo->dinner->rules.philo_count == 1)
return (lonely_philosopher(philo));
while (!check_stop_condition_safely(&philo->dinner->stop_mutex,
&philo->dinner->stop))
{
eat(philo);
rest(philo);
think(philo);
}
return (NULL);
}
```
As the `routine` function is defined to always receive and return void pointers, we have to cast the argument to a pointer to `philo`.
Then, using the `align_times()` function, we can make all threads start at the same time. This function basically keeps getting the current time and waits until the current thread reaches the `start_time`:
```c
void align_start_times(time_t start_time)
{
while (get_time_in_ms() < start_time)
continue ;
}
```
After that, we have to check for a special case, when there is only one philosopher. As each philosopher needs 2 forks to eat and the number of forks is equal to the number of philosopher, the only thing the philosopher can do when he is alone is to get a fork and wait until he dies.
Here the philosopher:
- Takes a fork with a print statement
- Waits until his death
- Announces the death
- Unlock the fork
```c
void *lonely_philosopher(t_philo *philo)
{
pthread_mutex_lock(&philo->dinner->forks[philo->forks[0]]);
print_philo_status(philo, TAKING_FORK);
life_check_and_wait(philo, philo->dinner->rules.lifespan);
print_philo_status(philo, DEAD);
pthread_mutex_unlock(&philo->dinner->forks[philo->forks[0]]);
return (NULL);
}
```
Now, if there are more than 1 philosopher, we repeat the routine of `eat()`, `rest()` and `think()` until someone dies or the number that each philosopher must eat is reached. In other words, when `stop` becomes true.
```c
while (!check_stop_condition_safely(&philo->dinner->stop_mutex,
&philo->dinner->stop))
{
eat(philo);
rest(philo);
think(philo);
}
```
## The routine
### eat()
To implement `eat()` we first:
- Take 2 forks and announce it
- Announce that a philosopher is eating
- Wait until `dining_duration` is met
- Increment the `times_eaten` variable
- Update the time of the `last_meal_time` variable
- Release the forks
```c
void eat(t_philo *philo)
{
take_forks(philo);
print_philo_status(philo, EATING);
life_check_and_wait(philo, philo->dinner->rules.dining_duration);
update_times_eaten_safely(&philo->times_eaten_mutex, &philo->times_eaten,
philo->times_eaten + 1);
update_last_meal_time_safely(&philo->last_meal_time_mutex,
&philo->last_meal_time, get_time_in_ms());
release_forks(philo);
}
```
Here, the `take_forks()` and `release_forks()` implementation are almost the same. We lock/mutex the mutex for a specific fork and announce it:
```c
void take_left_fork(t_philo *philo)
{
pthread_mutex_lock(&philo->dinner->forks[philo->forks[0]]);
print_philo_status(philo, TAKING_FORK);
}
void take_right_fork(t_philo *philo)
{
pthread_mutex_lock(&philo->dinner->forks[philo->forks[1]]);
print_philo_status(philo, TAKING_FORK);
}
void take_forks(t_philo *philo)
{
take_left_fork(philo);
take_right_fork(philo);
}
```
```c
void release_left_fork(t_philo *philo)
{
pthread_mutex_unlock(&philo->dinner->forks[philo->forks[0]]);
}
void release_right_fork(t_philo *philo)
{
pthread_mutex_unlock(&philo->dinner->forks[philo->forks[1]]);
}
void release_forks(t_philo *philo)
{
release_left_fork(philo);
release_right_fork(philo);
}
```
The functions `update_times_eaten_safely()` and `update_last_meal_time_safely()` are almost the same and only their types are different. That is something that would not be necessary if we used generics.
```c
void update_times_eaten_safely(pthread_mutex_t *mutex,
unsigned int *times_eaten, unsigned int new_value)
{
pthread_mutex_lock(mutex);
*times_eaten = new_value;
pthread_mutex_unlock(mutex);
}
void update_last_meal_time_safely(pthread_mutex_t *mutex,
time_t *last_meal_time, time_t new_value)
{
pthread_mutex_lock(mutex);
*last_meal_time = new_value;
pthread_mutex_unlock(mutex);
}
```
These functions basically updates a variable that is shared between threads by protecting them using a mutex before the operation. I created a similar function but that checks a value instead of updating it:
```c
unsigned int check_times_eaten_safely(pthread_mutex_t *mutex,
unsigned int *times_eaten)
{
unsigned int after_check;
pthread_mutex_lock(mutex);
after_check = *times_eaten;
pthread_mutex_unlock(mutex);
return (after_check);
}
time_t check_last_meal_time_safely(pthread_mutex_t *mutex,
time_t *last_meal_time)
{
time_t after_check;
pthread_mutex_lock(mutex);
after_check = *last_meal_time;
pthread_mutex_unlock(mutex);
return (after_check);
}
```
### rest()
`rest()` is simpler. It just prints and announces that a philosopher is sleeping:
```c
void rest(t_philo *philo)
{
print_philo_status(philo, SLEEPING);
life_check_and_wait(philo, philo->dinner->rules.rest_duration);
}
```
### think()
`think()` is also similar:
```c
void think(t_philo *philo)
{
print_philo_status(philo, THINKING);
life_check_and_wait(philo, calculate_thinking_duration(philo));
}
```
In the case of `think()` as it is not a value provided as a command line argument, we can calculate a value for which a philosopher will stay thinking:
```c
thinking_duration = (lifespan - fasting_duration - dining_duration) / 2
```
In case this results into a negative value or a value that is too big, we set the `thinking_duration` to these values as default:
```c
if (thinking_duration <= 0)
thinking_duration = 1;
if (thinking_duration > 600)
thinking_duration = 200;
```
## The supervisor
Back to the `start_dinner()` function, after creating threads for each philosopher, we are going to create one extra thread for the supervisor. The supervisor's roles is to update the `end` variable that gets changed if a philosopher dies or if all the philosophers ate at least `min_meals` times.
> [!info] info
> The supervisor only gets created if there are more than 1 philosophers.
### The supervisor routine
```c
void *supervisor_routine(void *arg)
{
int i;
t_dinner *dinner;
dinner = (t_dinner *)arg;
align_start_times(dinner->rules.start_time);
while (true)
{
i = 0;
while (i < dinner->rules.philo_count)
{
pthread_mutex_lock(&dinner->philos[i].last_meal_time_mutex);
if (died_from_starvation(dinner, i))
return (NULL);
if (philosopher_is_full(dinner, i))
return (NULL);
pthread_mutex_unlock(&dinner->philos[i].last_meal_time_mutex);
i++;
}
}
return (NULL);
}
```
As with the `philo_routine()`, here we wait for all the other threads to start at the same time using the `align_times()` function. Then, we loop infinitely through all the philosophers and see if someone `died_from_starvation()` or if a `philosopher_if_full()`
#### died_from_starvation()
This function checks if the span a philosopher spent without eating (the difference between the current time and the last meal time) is bigger than his lifespan:
```c
bool died_from_starvation(t_dinner *dinner, int i)
{
if (get_time_in_ms()
- dinner->philos[i].last_meal_time >= dinner->rules.lifespan)
{
print_philo_status(&dinner->philos[i], DEAD);
update_stop_condition_safely(&dinner->stop_mutex, &dinner->stop, true);
pthread_mutex_unlock(&dinner->philos[i].last_meal_time_mutex);
return (true);
}
return (false);
}
```
#### philosopher_is_full()
This function just compares the times a philosopher ate with the minimal number of times a philosopher must it if it is set:
```c
bool philosopher_is_full(t_dinner *dinner, int i)
{
if (dinner->rules.min_meals != (unsigned int)MIN_MEALS_NOT_SET)
{
if (check_times_eaten_safely(&dinner->philos[i].times_eaten_mutex,
&dinner->philos[i].times_eaten) > dinner->rules.min_meals)
{
update_stop_condition_safely(&dinner->stop_mutex, &dinner->stop,
true);
pthread_mutex_unlock(&dinner->philos[i].last_meal_time_mutex);
return (true);
}
}
return (false);
}
```
## end_dinner()
`end_dinner()` is really similar to `start_dinner()` but the difference is that here we are not creating threads but joining them back to the main thread. In other words, even if a thread finished after or before the main thread, they will all wait until the other threads stops executing:
```c
void end_dinner(t_dinner *dinner)
{
int i;
i = 0;
while (i < dinner->rules.philo_count)
{
if (!can_join_thread(dinner->philos[i].thread))
{
report_and_set_error(dinner, ERR_THREAD_JOIN, MSG_THREAD_JOIN);
return ;
}
i++;
}
if (dinner->rules.philo_count > 1)
{
if (!can_join_thread(dinner->supervisor))
{
report_and_set_error(dinner, ERR_THREAD_JOIN, MSG_THREAD_JOIN);
return ;
}
}
}
```
I separated the joining proccess into another function just like when using `pthread_create()` for readability purposes:
```c
bool can_join_thread(pthread_t thread)
{
if (pthread_join(thread, NULL) != SUCCESS)
return (false);
return (true);
}
```

View File

@ -0,0 +1,372 @@
---
date: 2024-12-24
---
# Understanding Binary Insertion Sort
Binary Insertion Sort is a sorting algorithm that combines [[Insertion Sort]] with Binary Search for finding the location where an element should be inserted.
## Implementation
```cpp
void binaryInsertionSort(int arr[], int size)
{
int key;
int sortedIndex;
int insertionIndex;
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
insertionIndex = binarySearch(arr, key, 0, sortedIndex);
for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
arr[insertionIndex] = key;
}
}
```
Where:
- `arr` is the array to be sorted
- `size` is the number of elements in the array
- `key` is the selected element in the unsorted portion of the array
- `sortedIndex` is the reverse index of the sorted portion of the array
- `insertionIndex` is the position to insert the `key` into the sorted portion of the array
### Binary Search Implementation
The different between the original binary search and the binary dearch used here is that its purpose is to find the correct position to insert the **key** value into the sorted array
```cpp
int binarySearch(int arr[], int key, int low, int high)
{
if (high <= low)
return (key > arr[low] ? low + 1 : low);
int mid = (low + high) / 2;
if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
return (binarySearch(arr, key, low, mid - 1));
}
```
where:
- `arr` is the sorted array in which to search
- `key` is the value to search for
- `low` is the lower bound index of the current search range
- `high` is the higher bound index of the current search range
Consider the following array $A$ with 3 elements $(n = 3)$:
$$
A = [6, 5, 3]
$$
### Step by step
Consider the following `for` loop that is the main part of the algorithm:
```cpp
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
insertionIndex = binarySearch(arr, key, 0, sortedIndex);
for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
arr[insertionIndex] = key;
}
```
---
#### First Iteration
Values:
```
key = 5
i = 1
sortedIndex = 0
insertionIndex = ?
```
Those are the initial values for the first iteration. Now, to know where to insert the key into the sorted portion of the array, we need to call `binarySearch()`:
```cpp
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
> insertionIndex = binarySearch(arr, key, 0, sortedIndex);
for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
arr[insertionIndex] = key;
}
```
##### Inside `binarySearch()`:
Values:
```
key = 5
low = 0
high = 0
```
As soon as we enter the function, the condition is met as $h \leq l$, so we enter the `return` statement. In this section the `key` gets compared with the first element of the array. As $5 \nleq 6$, `low` gets returned.
```cpp
if (high <= low)
> return (key > arr[low] ? low + 1 : low);
int mid = (low + high) / 2;
if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
return (binarySearch(arr, key, low, mid - 1));
```
##### Back to `binaryInsertionSort()`:
Now, we got to know the value for the `insertionIndex`:
```
key = 5
i = 1
sortedIndex = 0
insertionIndex = 0
```
Back to the execution, we find a for loop that goes through the sorted portion of the array in reverse order:
```cpp
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
insertionIndex = binarySearch(arr, key, 0, sortedIndex);
> for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
arr[insertionIndex] = key;
}
```
In the first iteration, `j` gets the value of 0 and as $0 \geq 0$, we enter the loop.
Then, the array's element at index 1 gets the value of the array's element at index 0.
$$
A = [6, 6, 3]
$$
In the second iteration, `j` gets decremented to -1, breaking out of the loop. After that, the key is inserted at the index 0 of the array:
```cpp
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
insertionIndex = binarySearch(arr, key, 0, sortedIndex);
for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
> arr[insertionIndex] = key;
}
```
Result:
$$
A = [5, 6, 3]
$$
---
#### Second Iteration
Values:
```
key = 3
i = 2
sortedIndex = 1
insertionIndex = ?
```
Now, we are going to enter `binarySearch()` again to retrieve the index for inserting the `key`:
##### Inside `binarySearch()`:
Values:
```
key = 3
low = 0
high = 1
```
The condition base condition is false as $1 \nleq 0$ so we go to the next line:
```cpp
> if (high <= low)
return (key > arr[low]) ? (low + 1) : low;
int mid = (low + high) / 2;
if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
return (binarySearch(arr, key, low, mid - 1));
```
The value for `mid` gets calculated resulting in 0:
```
key = 3
low = 0
high = 1
mid = 0
```
```cpp
if (high <= low)
return (key > arr[low]) ? (low + 1) : low;
> int mid = (low + high) / 2;
if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
return (binarySearch(arr, key, low, mid - 1));
```
Then, we check if the `key` is equal to `arr[mid]`. As $3 \neq 5$, we skip this line:
$$
A = [5, 6, 3]
$$
```cpp
if (high <= low)
return (key > arr[low]) ? (low + 1) : low;
int mid = (low + high) / 2;
> if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
return (binarySearch(arr, key, low, mid - 1));
```
After that, we check if the `key` is greater than the `arr[mid]`. As $3 \not> 5$m we go to the next line:
```cpp
if (high <= low)
return (key > arr[low]) ? (low + 1) : low;
int mid = (low + high) / 2;
if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
> return (binarySearch(arr, key, low, mid - 1));
```
Here, we recursively enter `binarySearch()` with the following values:
```
key = 3
low = 0
high = -1
```
As $-1 \leq 0$, we enter the base case. Inside it, the `key` which is 3 gets compared with `arr[0]` which is 5, as $3 \not> 5$, the value for `low`, 0 gets returned.
```cpp
> if (high <= low)
return (key > arr[low]) ? (low + 1) : low;
int mid = (low + high) / 2;
if (key == arr[mid])
return (mid + 1);
if (key > arr[mid])
return (binarySearch(arr, key, mid + 1, high));
return (binarySearch(arr, key, low, mid - 1));
```
##### Back to `binaryInsertionSort()`:
Now, we got to know the value for the `insertionIndex`:
```
key = 3
i = 2
sortedIndex = 1
insertionIndex = 0
```
Back to the execution, we find a for loop that goes through the sorted portion of the array in reverse order:
```cpp
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
insertionIndex = binarySearch(arr, key, 0, sortedIndex);
> for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
arr[insertionIndex] = key;
}
```
In the first iteration, `j` gets the value of 1 and as $1 \geq 0$, we enter the loop.
Then, the array's element at index 2 gets the value of the array's element at index 1.
$$
A = [5, 6, 6]
$$
`j` gets decremented to 0 and the condition is checked again. It checks as true as $0 \geq 0$. Then, inside the loop, the array's second element gets the value of the array's first element.
$$
A = [5, 5, 6]
$$
`j` gets decremented to -1 and we break out of the loop as $-1 \ngeq 0$. After that, the key is inserted at the `insertionIndex` position in the array:
```cpp
for (int i = 1; i < size; i++)
{
key = arr[i];
sortedIndex = i - 1;
insertionIndex = binarySearch(arr, key, 0, sortedIndex);
for (int j = sortedIndex; j >= insertionIndex; j--)
arr[j + 1] = arr[j];
> arr[insertionIndex] = key;
}
```
Result:
$$
A = [3, 5, 6]
$$

View File

@ -0,0 +1,248 @@
---
date: 2024-12-22
---
# Understanding Insertion Sort
Insertion sort is a straightforward yet inefficient sorting algorithm with a time complexity of $O(n^2)$. It works by treating the left portion of the array as already sorted, while the right portion remains unsorted. At each step, an element (referred to as the **key**) is taken from the unsorted portion and inserted into its correct position within the sorted section. The process begins by considering the first element already sorted, as it forms a one-element array. To insert the **key**, elements in the sorted section are shifted one position to the right until the key can be placed in its proper spot.
## Implementation
Here is the complete function for the algorithm:
```cpp
void insertion_sort(int *a, int n) {
int key;
int j;
for (int i = 1; i < n; i++) {
key = a[i];
j = i - 1;
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j];
j = j - 1;
}
a[j + 1] = key;
}
}
```
Where:
- `a` is the array
- `n` is the number of elements
- `j` is the index of the sorted portion
- `key` is the selected element in the unsorted portion
Consider the following array $A$ with 3 elements $(n = 3)$.
$$
A = [6, 5, 3]
$$
### Step by step
Consider the following `for` loop that is the main part of the algorithm:
```cpp
for (int i = 1; i < n; i++) {
key = a[i];
j = i - 1;
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = key;
}
```
---
#### First Iteration
Values:
```
key = 5
i = 1
j = 0
```
Indices:
```
[6, 5, 3]
^ ^
j i
```
1. In the nested loop, $0 \geq 0$ and $6 > 5$ so the loop is entered.
```cpp
while (j >= 0 && a[j] > key)
```
2. `a[j + 1]` which is 5 gets the value of `a[j]` which is 6.
```cpp
a[j + 1] = a[j];
```
Indices:
```
[6, 6, 3]
^ ^
j i
```
3. `j` gets decremented becoming `-1`.
```cpp
j--;
```
Values:
```
key = 5
i = 1
j = -1
```
4. The condition is checked again but now $j \ngeq 0$ so we break out of the loop.
```cpp
while (j >= 0 && a[j] > key)
```
5. `a[j + 1]` which is 6, the first element, gets replaced by the key which is 5.
```cpp
a[j + 1] = key;
```
Indices:
```
[5, 6, 3]
^ ^
j i
```
---
#### Second Iteration
Values:
```
key = 3
i = 2
j = 1
```
Indices:
```
[5, 6, 3]
^ ^
j i
```
1. In the nested loop, $1 \geq 0$ and $6 > 5$ so the loop is entered.
```cpp
while (j >= 0 && a[j] > key)
```
2. `a[j + 1]` which is 3 gets the value of `a[j]` which is 6.
```cpp
a[j + 1] = a[j];
```
Indices:
```
[5, 6, 6]
^ ^
j i
```
3. `j` gets decremented becoming `0`.
```cpp
j--;
```
Values:
```
key = 3
i = 2
j = 0
```
Indices:
```
[5, 6, 6]
^ ^
j i
```
4. The condition is checked again $(j \geq 0)$ and it matches it as $(0 \geq 0)$. `a[j]` which is 5 is also greater than the key which is 3.
```cpp
while (j >= 0 && a[j] > key)
```
5. `a[j + 1]` which is 6 gets the value of `a[j]` which is 5.
```cpp
a[j + 1] = a[j];
```
Indices:
```
[5, 5, 6]
^ ^
j i
```
6. `j` gets decremented becoming `-1`.
```cpp
j--
```
Indices:
```
[5, 5, 6]
^ ^
j i
```
7. The condition is checked again but now $j \ngeq 0$ so we break out of the loop.
```cpp
while (j >= 0 && a[j] > key)
```
8. `a[j + 1]` which is 5, the first element, gets replaced by the key which is 3.
```cpp
a[j + 1] = key;
```
Indices:
```
[3, 5, 6]
^ ^
j i
```
The array got sorted!
$$
A = [3, 5, 6]
$$

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -0,0 +1,141 @@
---
date: 2024-04-18
---
# Writing Makefile for C++ Projects
## Setting variables
Let's start by setting some basic variables:
```makefile
# The program name:
NAME = program
# The compiler
CXX = c++
```
> [!notes] Considerations
> > **Note:**
> The variable `CXX` is used to specify the C++ compiler. The command `c++` is analogous to using `cc` for compiling C programs. This is similar to how `g++`/`gcc` are used for GNU Compilers, and `clang`/`clang++` for Clang compilers.
## Flags
Here, we are setting the compilation flags used in École 42's C++ modules.
More specifically:
- `-Wall`: Enable all compiler's warning messages
- `-Wextra`: Enable extra warning messages that are not turned on by `-Wall`
- `-Werror`: Turn all warning messages into errors
- `-std=c++98`: Specify the C++ version to conform
```makefile
CXXFLAGS = -Wall -Wextra -Werror -std=c++98
```
## Specifying the source files
In the `SRCS` variable we can specify all the `.cpp` files used in the project. These are the files that are going to be compiled into object files (`.o`) and then combined into an executable file.
```makefile
SRCS = main.cpp
OBJS = $(SRCS:.cpp=.o)
INCLUDES = main.hpp
```
> [!notes] Notes on OBJS
> The `OBJS` variable is a special computed variable that takes all the values from the `SRCS` variable that has the `.cpp` extension and turns into a `.o` file.
## Defining some basic rules
When writing a Makefile we usually define 4 basic rules. Those are:
- `all`: Compile the program
- `clean`: Clean all object files
- `fclean`: Clean all object files + the executable file
- `re`: Clean everything that was generated and generate everything again (`fclean` + `all`)
```makefile
all: $(NAME)
$(NAME): $(OBJS)
$(CXX) $(CXXFLAGS) $(OBJS) -o $(NAME)
clean:
$(RM) $(OBJS)
fclean: clean
$(RM) $(NAME)
re: fclean all
```
Let's analyze each line here.
### all
To compile the program we just have to run `make all` but as `all` is the first rule that is getting specified, simply running `make` will execute `make all`.
```makefile
all: $(NAME)
```
As we can see here, `all` depends on the creation of `$(NAME)` which is the program name, in other words the executable file. At this point, we don't have the executable file yet, so `make` searches for a rule that creates `$(NAME)`:
```makefile
$(NAME): $(OBJS)
$(CXX) $(CXXFLAGS) $(OBJS) -o $(NAME)
```
The creation of `$(NAME)` depends on the creation of all object files on this project so they are created. After the creation of `$(OBJS)` we finally can run `$(CXX)` (the compiler) specifying `$(CXXFLAGS)` (our compilation flags) passing all the object files to compile and naming the final product to `$(NAME)`.
To better illustrate this imagine you have 2 C++ files:
```sh
main.cpp
other.cpp
```
We want to name our executable file as `program`:
```makefile
c++ -Wall -Wextra -Werror -std=c++98 main.o other.o -o program
```
This is the actual command that gets executed.
### Creating object files
This is the rule used to create object files. It basically says: For any C++ file, generate an object file. It also depends on the `INCLUDES` so if any header file changes, all object files are going to get regenerated. Then it compiles each `.cpp` file (represented by the automatic variable `$<` here) to an object file (with the `-c` flag) and outputs a file with the same name but with the `.o` extension. Here `$@` represents each `.o` file:
```makefile
%.o: %.cpp $(INCLUDES)
$(CXX) $(CXXFLAGS) -c $< -o $@
```
### Implementing clean
To implement `clean`, `fclean` and `re` is really simple.
`clean` just removes all object files so:
```makefile
clean:
$(RM) $(OBJS)
```
`fclean` calls clean to remove all object files and in addition, also removes the executable file:
```makefile
fclean: clean
$(RM) $(NAME)
```
`re` runs `fclean` and `all`:
```makefile
re: fclean all
```
### .PHONY?
`.PHONY` is used to indicate that a target is not a real file but a command or routine to be executed. For example, running `make clean` executes the `clean` routine, even though there is no file named `clean`.
```makefile
.PHONY: all clean fclean re
```
This sums up the creation of a basic Makefile for some basic C++ projects.

View File

@ -0,0 +1,173 @@
---
date: 2024-08-15
---
# Understanding the Orthodox Canonical Class Form
The **Orthodox Canonical Form** in C++ involves defining 5 special member functions for a class.
1. Default Constructor
2. Parameterized Constructor
3. Copy Constructor
4. Assignment Operator
5. Destructor
Considering the following `Human` class, lets talk about each of these member functions.
```cpp
class Human {
private:
std::string _name;
int _age;
public:
const std::string &getName() const {
return (_name);
}
int getAge() const {
return (_age);
}
};
```
### Default Constructor
The **Default Constructor** is a special member function in a class that initializes an object with **default values** during the objects instantiation.
```cpp
class Human {
private:
...
public:
Human() : _name("Default Name"), _age(0) {
std::cout << "Human Default Constructor Called!" << std::endl;
}
...
};
```
Usage:
```cpp
int main(void) {
Human h1;
std::cout << h1.getName() << std::endl;
std::cout << h1.getAge() << std::endl;
return (0);
}
```
### Parameterized Constructor
The **Parameterized Constructor** initializes an object with **specific values** provided as arguments during the objects instantiation.
```cpp
class Human {
private:
...
public:
Human(const std::string &name, int age) : _name(name), _age(age) {
std::cout << "Human Parameterized Constructor Called!" << std::endl;
}
...
};
```
Usage:
```cpp
int main(void) {
Human h1("Mark", 42);
std::cout << h1.getName() << std::endl;
std::cout << h1.getAge() << std::endl;
return (0);
}
```
### Copy Constructor
The **Copy Constructor** initializes a new object as a copy of an existing object. This is useful when passing an object by value or when we need to duplicate an object.
```cpp
class Human {
private:
...
public:
Human(const Human &other) : _name(other._name), _age(other._age) {
std::cout << "Human Copy Constructor Called!" << std::endl;
}
...
};
```
Usage:
```cpp
int main(void) {
Human h1("Mark", 42);
Human h2(h1);
std::cout << h2.getName() << std::endl;
std::cout << h2.getAge() << std::endl;
return (0);
}
```
### Assignment Operator
The **Assignment Operator** assigns the value of one object to another already-existing object. Here, we need to handle deep copying and self-assignment.
```cpp
class Human {
private:
...
public:
Human &operator=(const Human &other) {
if (this != &other) {
_name = other._name;
_age = other._age;
}
std::cout << "Human Assignment Operator Called!" << std::endl;
return (*this); //Required for chaining
}
...
};
```
Note: `this` is a pointer of type `Human *` which points to the current object. Dereferencing it gives us access to the current object. If the assign operator gets called like this `h2 = h1` then, `h1` refers to `other` and `h2` refer to `this` in this case.
Usage:
```cpp
int main() {
Human h1("Mark", 42);
Human h2("John", 30);
h2 = h1;
std::cout << h2.getName() << std::endl;
std::cout << h2.getAge() << std::endl;
return (0);
}
```
### Destructor
The **Destructor** is called when an object goes out of scope or is explicitly deleted. It is used to clean up resources such as memory or file handles.
```cpp
class Human {
private:
...
public:
Human(const std::string& name, int age) : _age(age) {
_name = new std::string(name); // Dynamic Memory Allocation
}
~Human() {
std::cout << "Human Destructor Called!" << std::endl;
delete _name; // Clean Up
}
...
};
```

View File

@ -0,0 +1,93 @@
---
date: 2024-09-07
---
# Understanding Casts in C++
## Implicit Conversion (Coersion)
When the conversion is done **implicitly** by the compiler.
```cpp
int x = 7;
int y = 3;
float res = x / y;
```
## Explicit Conversion
When the conversion is done **explicitly** by the programmer.
## Types of Cast
### Static Cast
**What it is**: A cast similar to implicit conversion (coercion), but done explicitly by the programmer.
**When to use**: Use `static_cast` when you want to perform safe conversions, like from one numeric type to another (e.g., `double` to `int`), and youre sure the types are compatible.
**Why it works**: It performs the conversion at compile-time but **does not check** at runtime if the cast is valid.
```cpp
double m = 2.1 * 3.5;
int res = static_cast<int>(m);
```
### Upcasting and Downcasting
First Consider these 2 classes where `player` is inherited from `entity`:
```cpp
Entity *entity = new Entity;
Player *player = new Player;
```
### Upcasting
**What it is**: Casting a derived class pointer to a base class pointer.
**Why its safe**: Every Player is also an Entity, so this cast is safe.
```cpp
Entity *ep = player;
```
### Downcasting
**What it is**: Casting a base class pointer to a derived class pointer.
**Why its dangerous**: Not every Entity is a Player, so this cast can be unsafe.
```cpp
Player *pp = entity;
```
### Dynamic Cast
**What it is**: A cast used when downcasting. It checks at runtime if the cast is valid.
**When to use**: You use `dynamic_cast` when youre not sure whether the object youre pointing to is actually of the derived type.
**Why virtual functions?**: For `dynamic_cast` to work, the base class must have at least one virtual function. This is because the virtual function creates something called a **vtable** (a table of virtual functions), which stores information about the actual type of the object. This information helps `dynamic_cast` check if the object is really of the derived type at runtime.
```cpp
Player *pp = dynamic_cast<Player>(entity);
```
### Reinterpret Cast
**What it is**: A cast used to convert one pointer type to another pointer type without checking compatibility.
**When to use**: Use `reinterpret_cast` when you want to treat the memory address of one object as if it were a different type, even though the types may be unrelated.
**Why it works**: It simply **reinterprets** the memory address, but does not ensure the types are compatible.
```cpp
class Apple {
public:
int x = 10;
};
struct Banana {
int y;
};
```
```cpp
Apple* apple = new Apple();
Banana* banana = reinterpret_cast<Banana*>(apple);
banana->y = 20;
```

BIN
content/C++/media/class.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

View File

@ -0,0 +1,216 @@
---
date: 2024-02-17
---
# Replicating the Print Function in C
`printf()` is one of the most useful functions in almost every programming language. In this project, we are going to learn how to implement this function in C using variadic parameters.
## What is a variadic function?
A variadic function, just like `printf()` is a function that can receive an indeterminate number of parameters.
## Prototype
```c
int my_printf(char *fmt, ...);
```
> [!note] Note
> The three dots (`...`) also called **ellipsis** indicates that a function is variadic. A variadic function in C requires at least one parameter, in this case the format string.
## Getting started
The first thing we are going to do is to include the `stdarg` header file:
```c
#include <stdarg.h>
```
Now, inside `my_printf()` function let's declare a variable to store the variadic parameters:
```c
va_list args;
```
Here, `va_list` is a special abstract type that we will use to store all the variadic arguments that got passed to `my_printf()`
To actually initialize this list, we are going to call `va_start()` by passing our argument list with the format string:
```c
va_start(args, fmt);
```
> [!note] Note
> Just like when using something like `malloc()` with `free()`, `va_start()` requires us to use `va_end()` after handling the list.
## Looping through the format string
Now, let's create a loop through the format string and print the current character or the current argument if a `%` is found:
```c
for (i = 0; fmt[i] != '\0'; i++)
{
if (fmt[i] == '%')
{
i++;
put_fmt(&args, fmt, &size);
}
else
{
my_putchar(fmt[i]);
size++;
}
}
```
> [!note] Note
> Here we are passing the address of `i` because we are going to modify it into `put_fmt()` and we want to keep this modification in the caller function (`my_printf()` in this case)
### Implementing our own `putchar()`
To print a single character to the standard output, we have to use the `write()` function passing the file descriptor we want to write (in this case, standard output), the character we want to print and its size. We are also going to increment the size variable that corresponds to the number of characters `my_printf()` printed:
```c
void my_putchar(int c)
{
write(STDOUT_FILENO, &c, sizeof(char));
(*size)++;
}
```
## Printing the format string
In this example, we are only going to support the following formats:
- `%s` for printing a string
- `%d` for printing a decimal number
- `%x` for printing a number in hexadecimal format
### Prototype
```c
void put_fmt(va_list *args, const char c, size_t *size);
```
Here, we are going to check if the current character (in this case, `c`) is equal to one of the format specifiers. We are going to use `va_arg` here to represent the current argument in the argument list passing the type to handle the variadic argument:
```c
switch (c)
{
case 's':
my_putstr(va_arg(*args, char *), size);
break ;
case 'd':
my_putnbr(va_arg(*args, int), size);
break ;
case 'x':
my_puthex(va_arg(args, unsigned int), size);
}
```
### When the argument parameter is a string:
```c
void my_putstr(char *str, size_t *size)
{
if (str == NULL)
{
my_putstr("(null)", size);
return ;
}
for (int i = 0; str[i] != '\0'; i++)
my_putchar(str[i], size);
}
```
Here, we just loop through the string parameter and print each character. If `str == NULL` we print the string `(null)` just like the original `printf()` from `stdio.h`
### When the argument parameter is a decimal number:
First, we declare a string containing all the decimal characters:
```c
#define DCM "0123456789"
```
Then, use this recursive function to print a number:
```c
void my_putnbr(int n, size_t *size)
{
long long ll_n;
ll_n = (long long)n;
if (ll_n < 0)
{
my_putchar('-', size);
ll_n = -ll_n;
}
if (ll_n < 10)
my_putchar(DCM[ll_n], size);
else
{
my_putnbr(ll_n / 10, size);
my_putnbr(ll_n % 10, size);
}
}
```
This function works as follows:
1. Checks if a number is negative. If so, it prints the minus sign and converts the number to positive.
2. If a number is less than 10, we print the number indexing it from the string.
3. If a number is greater or equal to 10, we call the `my_putnbr()` function recursivelly dividing the number by 10 until we get only one digit.
example:
Imagine we want to print the number `42`:
1. As 42 is positive, we skip the first `if` statement.
2. 42 is also greater than 10 so it gets skipped as well.
3. Now, in the `else` statement, $42 \div 10 = 4$ so we call `my_putnbr()` recursivelly passing the number 4.
4. As 4 is less than 10, 4 gets printed and returns to the caller function.
5. Now that `my_putnbr(ll_n / 10, size)` returned, we call `my_putnbr(ll_n % 10, size)` that calls recursivelly `my_putnbr()` passing $42 \mod 10 = 2$ as a parameter.
6. As 2 is less than 10, 2 gets printed and returns to the caller function.
7. The caller function returns.
8. 42 got printed to the standard output successfully.
### When the argument parameter is a hexadecimal number:
The hexadecimal version is really similar but it does not support negative numbers because `%x` treats every number as `unsigned`:
```c
#define HEX "0123456789abcdef"
```
```c
void my_puthex(unsigned int n, size_t *size)
{
if (n < 16)
my_putchar(HEX[n], size);
else
{
my_puthex(n / 16, size);
my_puthex(n % 16, size);
}
}
```
## Optional error handling
When using the original `printf()`, you will notice that if you pass a parameter that does not match the format specifier (e.g., passing a string when the format specifier is `%d`), the compiler will issue a warning.
To replicate this behavior in our custom `printf()` function, we can define the prototype for `my_printf()` with this `__attribute__`:
```c
int my_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
```
This tells the compiler that our function behaves similarly to `printf()`. It specifies that the first parameter contains the format string, and the variadic parameters start from the second parameter.

View File

@ -0,0 +1,113 @@
---
title: Introduction to pointers in C
date: 2021-06-09
---
# Introduction to pointers in C
When we declare a variable in C, we generally do something like this:
```c
int num = 1;
```
As you might know, variables get stored in memory, and the size of each variable will differ depending on its data type.
For instance, an integer variable like `num` declared above is 4 bytes long on my Mac, but the size could vary depending on the machine.
You can always check the size of a particular data type by using the `sizeof()` operator.
```c
printf("%lu\n", sizeof(int));
```
output: 4
## The address-of operator (&)
The address-of operator is just an operator we place before some variable name to get that variables address in memory. We print this address using the `%p` format specifier to get the address in hexadecimal.
```c
int num = 1;
printf("%p", &num);
```
output: *0x7ffee7ea278c*
## The concept of a pointer
A pointer is just a variable that holds the address in memory of some other variable.
When declaring a pointer variable, we have to place a `*` symbol just before the variable name.
```c
int *pointer;
```
Now that we declared our pointer variable lets try assigning it the address of the variable `num`
```c
pointer = &num;
```
Now `pointer` will hold *0x7ffee7ea278c*, the address in memory of the variable `num`.
You can also declare a pointer variable and assign it a value at the same line.
```c
int *pointer = &num;
```
## Dereferencing a pointer
Dereferencing a pointer means accessing or manipulating data stored at an address in memory through a pointer variable.
Say we wanted to change the value of `num` from 1 (the value we initialized it with) to 2.
We could do something like this:
```c
num = 2;
```
But, what if we wanted to use the pointer we declared to change `num`s value?
Thats when we use the *dereference operator* (*)
```c
*pointer = 2;
printf("%d\n", num);
```
output: 2
- - - -
## Use Example
Say we wanted to make a function that receives two numbers and swap them.
That would be kind of tricky to do because we can only return a single value from a function.
But with pointers, we can access some variables memory location and change it directly.
Consider the following example:
We are declaring a function that receives two pointers and, we want to swap their values.
- First, we create a temporary variable and assign it the value pointed by `a`.
- Second, we dereference the pointer `a` and set it to be equal to what `b` is pointing to. (That is, if `a` is pointing to a variable x containing 1 and `b` is pointing to a variable y containing 2 then, `a` would still be pointing to x but x would now contain 2.)
- Third, we set the value pointed by `b` to be equal to `temp`.
```c
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
```
Now, we can call the swap function on main and see if it works.
```c
int main(int argc, char **argv)
{
int x = 1;
int y = 2;
swap(&x, &y);
printf("x: %i\ny: %i\n", x, y);
}
```
output:
x: 2
y: 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@ -0,0 +1,90 @@
---
title: Understanding the stack data structure
date: 2022-08-23
---
# Understanding the stack data structure
**Stack** is an ADT (Abstract Data Structure) which follows the LIFO (Last In First Out) order. It might be easier to understand this concept by imagining an actual stack of books.
When working with stacks, you can use mainly two operations. `push()` to add an element to the top of a stack and `pop()` to literally pop out or remove an element of a stack. As it is considered an ADT (Abstract Data Structure) you can implement it in many ways with other basic data structures such as arrays or linked lists. Lets try implementing it using an array first.
## Implementing push():
```c
void push(int element)
{
stack[depth] = element;
depth++;
}
```
in which, `element` is the value (an integer in this case) you want to put on the top of the stack and `depth` is the size of the stack. I defined the stack and the depth as global variables with the following default values:
```c
#define MAX_DEPTH 256
int stack[MAX_DEPTH];
int depth = 0;
```
You can also add some error handling such as returning when the stacks depth has reached the `MAX_DEPTH`:
```c
void push(int element)
{
if (depth == MAX_DEPTH)
return ;
stack[depth] = element;
depth++;
}
```
## Implementing pop():
```c
int pop(void)
{
depth--;
return (stack[depth]);
}
```
Simple as that.
Like `push()`, you can also add some error handling to this function such as exiting out from the program when the stack is empty (in other words, when the depth is 0).
```c
int pop(void)
{
if (depth == 0)
exit(1);
depth--;
return (stack[depth]);
}
```
---
## Usage:
From the `main()` function, lets try pushing some elements into our stack and then, printing the whole stack out.
```c
int main(void)
{
push(1);
push(2);
push(3);
for (int i = 0; i < 3; i++)
printf("%d ", stack[i]);
return (0);
}
```
***output:*** 3 2 1
***note:*** The output will be printed in reverse order.
You can also try popping out an element from the stack simply by calling ***pop()***.

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

View File

@ -0,0 +1,78 @@
---
title: Understanding signals in Linux
date: 2022-08-12
---
# Understanding signals in Linux
## What is a process ID?
A process ID a.k.a. ***PID*** is literally what the name says, it is a number to uniquely identify a running process. You can print your programs ***PID*** in C using the ***getpid()*** function included on the header file ***unistd.h***.
```c
int main(void)
{
while (1)
{
printf("PID: %d\n", getpid());
sleep(1);
}
}
```
***output: “*** PID: 12345 ”
***note:*** “12345” is a PID for an arbitrary process.
## What is a signal?
A signal is a one-way message to inform that something important happened sent by a process to a process, the kernel to the process, or a process to itself. Some examples of signals are ***SIGINT*** and ***SIGSTOP*** mapped to “ctrl-C” and “ctrl-Z” respectively on **Unix-like Operating Systems.**
## Sending signals:
You can send a signal with the command ***kill*** through the command line specifying as the first parameter the signal you want to send, and as the second parameter the PID of the process you want to send it to.
```bash
kill -INT 12345
```
or in C (dont forget to include the header file ***signal.h***):
```c
kill(12345, SIGINT);
```
---
## Handling signals:
You can use the ***signal()*** function in C to handle a specific signal defined as the first parameter in the ***signal()*** function call, and pass the address of a function you would like to run when the specified signal is received.
```c
signal(SIGINT, &sigint_handler);
```
Now, I will define the ***sigint_handler()*** function as:
```c
void sigint_handler(int signal_number)
{
printf("sigint's signal number is %d\n", signal_number);
}
```
The function above will be run when ***SIGINT*** (when the user presses ***ctrl-C*** or uses the kill program/function to send a signal) is sent. It will simply print the signal number for ***SIGINT*** based
on the table shown on the manual page for ***signal***. To see it, just run:
```bash
man signal
```
By the way, there are some pre-existing functions that you can pass to ***signal()*** such as ***SIG_IGN*** (to ignore a signal) and ***SIG_DFL*** (for default handling of a certain signal).
Usage:
```c
signal(SIGINT, SIG_IGN);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

View File

@ -0,0 +1,98 @@
---
date: 02-05-2024
---
# The XOR Swap
The $\text{XOR}$ swap algorithm is a clever programming trick used to swap the values of two variables without using a third temporary variable. This method exploits the properties of the $\text{XOR}$ bitwise operation to perform the swap efficiently and in a mathematically elegant manner. The $\text{XOR}$, or "exclusive or," operation on two bits results in a value of 1 if and only if the bits are different; otherwise, the result is 0.
## Algorithm
The algorithm is described as follows:
$$
\begin{align*}
x_1 &= x_0 \oplus y_0 \\
y_1 &= x_1 \oplus y_0 \\
x_2 &= x_1 \oplus y_1
\end{align*}
$$
Expanding this:
$$
\begin{align*}
x_1 &= x_0 \oplus y_0 \\
y_1 &= (x_0 \oplus y_0) \oplus y_0 \\
x_2 &= (x_0 \oplus y_0) \oplus [(x_0 \oplus y_0) \oplus y_0]
\end{align*}
$$
When changing the order of operations:
$$
\begin{align*}
x_1 &= x_0 \oplus y_0 \\
y_1 &= (y_0 \oplus y_0) \oplus x_0 \\
x_2 &= (x_0 \oplus x_0) \oplus (y_0 \oplus y_0) \oplus y_0
\end{align*}
$$
Based on the $\text{XOR}$ properties, where we know that $x \oplus x = 0$ and that $x \oplus 0 = x$, $0 \oplus x = x$, we arrive at the following conclusions, completing the swap process:
$$
\begin{align*}
y_1 &= x_0 \\
x_2 &= y_0
\end{align*}
$$
## Practical Example
Suppose we have two numbers we want to swap:
$$
\begin{align*}
x_0 = 101_2 \\
y_0 = 010_2
\end{align*}
$$
> [!info] Info
> Let $x_0$ and $y_0$ denote the initial values of variables $x$ and $y$, respectively. Here, the subscript $2$ indicates that the numbers are in base-2 (binary) notation.
Applying the XOR operation on these values:
$$
\begin{align*}
x_1 &= 101 \oplus 010 \\
x_1 &= 111
\end{align*}
$$
Continuing with the process:
$$
\begin{align*}
y_1 &= 111 \oplus 010 \\
y_1 &= 101
\end{align*}
$$
And finally:
$$
\begin{align*}
x_2 &= 111 \oplus 101 \\
x_2 &= 010
\end{align*}
$$
Thus, after applying the $\text{XOR}$ swap algorithm, $x_0$ (originally $101$) has been swapped with $y_0$ (originally $010$), demonstrating the algorithm's effectiveness with a practical example.

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

446
content/Swift/NeoMnemo.md Normal file
View File

@ -0,0 +1,446 @@
---
date: 2025-02-25
---
# NeoMnemo: My Submission for the 2025 Swift Student Challenge
In 2023, I got the chance to go to an event organized by the try! Swift Tokyo Student Club at Apple's headquarters in Tokyo. I met many students and developers from Japan's iOS community and learned about the yearly [try! Swift Tokyo](https://tryswift.jp/) event and the [Swift Student Challenge](https://developer.apple.com/swift-student-challenge/). The next year, I joined the try! Swift Tokyo for the first time. I listened to some great talks and talked with a few companies in Japan. I also made new friends who helped me learn more about the iOS community, and learned a lot from them.
This year, I decided to participate for the first time in the Swift Student Challenge with my new app **NeoMnemo**. In this article, I will be sharing some background on why I developed the app and how the developing process was for me.
## The App Concept and Idea
> **NeoMnemo** is a visual learning app that transforms stories used for memorization into vivid images, making complex concepts easier to understand and remember.
### Background
Im currently a freshman at Tokyo University of Foreign Studies. As a Japanese Brazilian born and raised in Brazil, studying in Tokyo was a long-held goal of mine, but achieving it wasnt easy. My education in Brazil didnt include Japan-specific subjects like Japanese history and politics. Before taking the university entrance exam, I attended a preparatory course in Tokyo offered by the Japanese government, where I studied these topics, even though I found them challenging to understand. As the exam drew nearer, I knew I had to try something new. Thats when I decided to explore the use of mnemonics by creating short stories to connect key concepts.
### Bringing Mnemonics to Life through Image Generation
After watching last yearʼs WWDC, where the Image Playground was introduced, I immediately thought of integrating the Image Playground API into an app to create visual mnemonics as our brains tend to remember pictures better than words.
![image](https://github.com/user-attachments/assets/5911fe22-c628-4999-b64e-91e19d3f14b1)
### Branding and Design
The name “**NeoMnemo**” reflects a new approach to mnemonics by turning stories into images. The icon, designed with Freeform, Figma and Illustrator features a card displaying a Macaw, a bird renowned for its strong memory in the Brazilian Amazon. Its colorful feathers symbolize NeoMnemo's abilities to generate a diverse range of images. The card is encased in a bubble, emphasizing its integration with Image Playground.
![image](https://github.com/user-attachments/assets/e6bfe258-66ed-4718-b1a7-105339b5a9fd)
### User Experience
When users first open the app, they see a screen that explains NeoMnemoʼs core features. These include creating custom flashcards by pairing concepts with creative stories for smarter learning, generating visual mnemonics by transforming stories into vivid and memorable images, and learning with engaging visuals that make complex concepts easier to recall. This introduction was inspired by Appleʼs native apps, such as [Numbers](https://www.apple.com/in/numbers/), which highlight core features on the first launch.
![image](https://github.com/user-attachments/assets/2e2af520-605d-4af7-87d7-f564af12d9a4)
After the introduction, users arrive at the main screen, which displays a flashcard grid featuring sample flashcards that showcase the variety of topics NeoMnemo can cover. In the card review section, I added smooth animations to create a more intuitive and enjoyable experience. Users can tap a card to flip it and reveal the answer, then swipe left or right to navigate through the deck. Once all cards have been reviewed, a congratulatory screen with a confetti effect appears, delivering positive reinforcement by celebrating progress and inspiring continued learning.
![image](https://github.com/user-attachments/assets/c4d6f6c8-2cd4-4ab7-ba70-ac81981fe955)
For the add/edit card screen, I focused on input validation to ensure a seamless user experience. Each field includes placeholder text with sample content for the story, concept, and notes, guiding users on where to insert each element. As users type, labels update to indicate which fields are required or optional for generating an image and adding a card. I also implemented a word count limit in the story field, with a smooth animation updating the count as users type. This limit ensures that the Image Playground API can generate accurate images from the stories.
![image](https://github.com/user-attachments/assets/d6634b9d-64ae-4a1f-a11f-33f5c482aeb3)
## Development
On January 22, 2025, I began developing my app from scratch. This project marked my first time creating a full-fledged app using Apple's APIs and SwiftUI. Even though I had worked with SwiftUI in courses before, it was my first experience building an app entirely from the ground up. On day one, I envisioned the design for the view to add new cards, so I started by developing that component. While testing the Image Playground API, I discovered that it could be initialized via a sheet using the `imagePlaygroundSheet()` instance method.
Here is the prototype for this instance method:
```swift
@MainActor @preconcurrency
func imagePlaygroundSheet(
isPresented: Binding<Bool>,
concepts: [ImagePlaygroundConcept] = [],
sourceImageURL: URL,
onCompletion: @escaping (URL) -> Void,
onCancellation: (() -> Void)? = nil
) -> some View
```
Here:
- `isPresented` is a boolean value which determines if the sheet is presented or not.
- `concepts` is an array of `ImagePlaygroundConcept`, a type which contains elements to include in the image sent to the Image Playground. In the case of **NeoMnemo**, it is the **Mnemonic Story** field.
- `sourceImageURL` wasn't used for this project but it represents the input image that can be sent to Image Playground so that it can use it as a base image to generate an image.
- `onCompletion` is the block that receives the URL for the image generated by the Image Playground in case of success.
- `onCancellation` is a block that we can specify an action in case the user exits the Image Playground without choosing an image. In my project, I opted for not using this block.
This is the code for generating a button that triggers the Image Playground:
```swift
Button("Generate Mnemonic Image", systemImage: "apple.intelligence") {
isPresented = true
}
.imagePlaygroundSheet(isPresented: $isPresented, concepts: [concept]) { url in
imageURL = url
}
```
Where the concept is a computed property which returns an Image Playground concept containing the contents of the **Mnemonic Story** text field:
```swift
private var concept: ImagePlaygroundConcept {
ImagePlaygroundConcept.text(story)
}
```
The resulting initial views looked like this:
![image](https://github.com/user-attachments/assets/50a29a8e-12fe-4724-b216-ddfe42ffc8e2)
### Managing Data
After that, I began considering how to store my app's data. I learned about [SwiftData](https://developer.apple.com/xcode/swiftdata/) and watched tutorial videos from Apple and other content creators. Thankfully, its usage was straightforward, and I was able to implement the model in a short period.
The first step is to mark your model with the `@Model` macro. Here is an example of the model for my app:
```swift
import SwiftData
@Model
class Mnemonic: Identifiable {
var keyword: String
var story: String
var image: Data
init(_ keyword: String, _ story: String, _ image: Data) {
self.keyword = keyword
self.story = story
self.image = image
}
}
```
Next, define the `modelContainer` inside the main app struct:
```swift
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Mnemonic.self)
}
}
```
Within `ContentView`, you can use the `@Query` macro to access the contents of the database and the `@Environment(\.modelContext)` property to modify its elements:
```swift
@Query private var mnemonics: [Mnemonic]
@Environment(\.modelContext) private var context
```
These properties allow you to perform various operations on the database, such as:
- Deleting an element:
```swift
context.delete(mnemonic)
try? context.save()
```
- Inserting an element:
```swift
context.insert(Mnemonic(keyword, story, imageData))
try? context.save()
```
For inserting images into the database, it is best to convert them to `Data` first. You can achieve this by using the `fetchData()` function and passing the image URL returned by the Image Playground:
```swift
if let url = imageURL {
imageData = fetchData(from: url) ?? Data()
context.insert(Mnemonic(keyword, story, imageData))
}
```
### The Card View
To test whether my data was stored correctly, I created a simple view that displays the retrieved elements, both images and text. I also experimented with a blurred background for the card's back, but eventually discarded that idea to keep the information clear and concise.
![image](https://github.com/user-attachments/assets/3c62789e-794e-4e2f-9744-e5d1a7a6d86e)
### Animating the Card
For giving the card a little bit of life and making it more intuitive to use, I used 3 animations:
#### The flip animation
I used `rotation3DEffect()` to flip the card 180° in the vertical axis when tapped:
```swift
content
.rotation3DEffect(
.degrees(isFlipped ? 180 : 0),
axis: (x: 0, y: 1, z: 0)
)
.onTapGesture {
withAnimation(.easeInOut(duration: 0.6)) {
isFlipped.toggle()
}
}
```
#### The drag animation
I use the offset to slide the card horizontally, rotation to tilt it slightly, and a drag gesture so if you swipe far enough, it removes the card:
```swift
content
.rotationEffect(.degrees(offset.width / 5.0))
.offset(x: offset.width * 2)
.gesture(
DragGesture()
.onChanged { gesture in
withAnimation(.easeOut(duration: 0.6)) {
offset = gesture.translation
}
}
.onEnded { _ in
if abs(offset.width) > 100 {
triggerHaptic.toggle()
removal?()
} else {
offset = .zero
}
}
)
```
from: [Hacking with Swift](https://www.hackingwithswift.com/books/ios-swiftui/moving-views-with-draggesture-and-offset)
#### The Opacity animation
I use it to manage the texts opacity in each card. If its on top, the text is fully visible, otherwise, its completely transparent. This effect prevents distractions when the user flips the top card and might briefly see the card underneath:
```swift
content
.opacity(opacity)
.onChange(of: isTopCard) { _, newValue in
withAnimation(.easeInOut(duration: 0.3)) {
opacity = newValue ? 1.0 : 0.0
}
}
.onAppear {
if isTopCard {
opacity = 1.0
}
}
```
### The Card Grid
Then, I started working on the view for the card grid. I used a `LazyVStack` to position the cards in a grid format:
```swift
LazyVGrid(columns: gridColumns) {
ForEach(mnemonics) { mnemonic in
CardMiniatureView(mnemonic: mnemonic)
}
}
```
I also included a new design for the button that triggers the view to add new cards and added actions to the card's context menu such as editing and deleting a card:
```swift
@ViewBuilder
private var contextMenuContent: some View {
Button("Edit", systemImage: "pencil") {
showEditSheet = true
}
Button(role: .destructive) {
showDeleteConfirmation = true
} label: {
Label("Delete", systemImage: "trash")
}
}
```
I applied this to the individual cards in the following way:
```swift
cardView
.onTapGesture {
isSheetPresented.toggle()
}
.fullScreenCover(isPresented: $isSheetPresented) {
MnemonicSheet(mnemonic: mnemonic)
.interactiveDismissDisabled()
}
.contentShape(ContentShapeKinds.contextMenuPreview, RoundedRectangle(cornerRadius: 16))
.contextMenu {
contextMenuContent
}
.alert("Are you sure you want to delete this mnemonic?", isPresented: $showDeleteConfirmation) {
alertButtons
}
.sheet(isPresented: $showEditSheet) {
CardFormView(card: mnemonic)
.interactiveDismissDisabled()
}
```
The resulting grid view looked like this:
![image](https://github.com/user-attachments/assets/79c36715-9a0f-477d-a3e4-d33ce3d035cd)
After that, I experimented with several card sizes and spacing to produce a symmetrical spacing and size to the cards. I also added a view for when the user has no mnemonic cards available:
![image](https://github.com/user-attachments/assets/4c03455a-1e42-461b-a384-eee3df9f687c)
### Landscape Card View
I also implemented a different design for when the card is displayed in landscape mode:
![image](https://github.com/user-attachments/assets/51e298e3-126b-48b4-af07-048467095aad)
To identify landscape mode, I used `GeometryReader` to compare the screen's width and height:
```swift
var body: some View {
GeometryReader { geometry in
adaptiveLayout(isWide: isWideLayout(geometry))
}
}
@ViewBuilder
private func adaptiveLayout(isWide: Bool) -> some View {
if isWide {
HStack {
image
content
}
} else {
VStack {
image
content
Spacer()
}
}
}
private func isWideLayout(_ geometry: GeometryProxy) -> Bool {
geometry.size.width > geometry.size.height
}
```
The final version looked like this in landscape mode:
![image](https://github.com/user-attachments/assets/221b43c4-05ba-4895-9252-442939fd9c67)
and this in portrait mode:
![image](https://github.com/user-attachments/assets/ccd9ad41-bdbe-42ad-a79e-88d60c6f9075)
### Input Validation
I improved the add/edit card section by adding a “remaining words” feature. To accomplish this, I defined two computed properties:
```swift
private var wordCount: Int {
story.split { $0.isWhitespace || $0.isNewline }.count
}
private var remainingWords: Int {
max(47 - wordCount, 0)
}
```
Then, I added these properties into the header of the story text field section in my form:
```swift
Section(
header: HStack {
Text("Mnemonic Story")
Spacer()
Text("Remaining: \(remainingWords)")
.foregroundColor(47 - wordCount < 0 ? .red : .gray)
}
) {
TextField(...)
}
```
I also introduced a transition to animate the numbers as they increase or decrease:
```swift
Text("Remaining: \(remainingWords)")
.foregroundColor(47 - wordCount < 0 ? .red : .gray)
.contentTransition(.numericText(countsDown: true))
.transaction { transform in
transform.animation = .default
}
```
I also decided to change the labels as the user types. This helps guide them on what to enter for each field. Once they start typing, the labels switch to indicate which fields are required and which are optional:
![image](https://github.com/user-attachments/assets/2cb67501-781c-4e6e-b9ed-b37b7ddadb0a)
![image](https://github.com/user-attachments/assets/db128d0f-a2f3-4059-b16b-97a6a87a6b86)
### Building Adaptive Layouts
I created adaptive layouts for every iPad split-screen variation by applying padding to the card. After experimenting with multiple methods to ensure the card view adjusted properly to different screen sizes, I decided to measure the width and height of the view itself, whether in full-screen or split-screen using `GeometryReader` and comparing those values to the devices full resolution. While this approach might not be the most elegant, it was a practical solution given the limited time for the project. I plan to improve the logic in the future.
```swift
card
// iPad landscape fullscreen
.padding(.vertical, isIpad() && width > height && width == screenWidth ? 120 : 0)
.padding(.horizontal, width > height && width == screenWidth ? 40 : 0)
// iPad landscape split
.padding(.vertical, isIpad() && width > height && width < screenWidth ? 180 : 0)
// iPad fullscreen portrait
.padding(.horizontal, isIpad() && width < height && width == screenWidth ? 120 : 0)
.padding(.vertical, isIpad() && width < height && width == screenWidth ? 80 : 0)
// iPad non-fullscreen portrait ("little iPhone" view on landscape)
.padding(.vertical, isIpad() && width < height && width != screenWidth && screenWidth / 2 - 5 != width ? 20 : 0)
.padding(.horizontal, isIpad() && width < height && width != screenWidth && screenWidth / 2 - 5 != width ? 0 : 0)
// iPad exact split
.padding(.horizontal, isIpad() && width < height && screenWidth / 2 - 5 == width ? 80 : 0)
.padding(.vertical, isIpad() && width < height && screenWidth / 2 - 5 == width ? 0 : 0)
// iPhone
.padding(24)
```
Here, `isIpad()` is defined as:
```swift
private func isIpad() -> Bool {
UIDevice.current.userInterfaceIdiom == .pad
}
```
and `screenWidth` as:
```swift
let screenWidth = UIScreen.main.bounds.width
```
In landscape mode:
![image](https://github.com/user-attachments/assets/ae566dbd-38bf-44e2-8040-d96a8e1f6213)
![image](https://github.com/user-attachments/assets/90d68b6e-9438-410e-bdc1-c3ad81636a12)
![image](https://github.com/user-attachments/assets/0ae42c4e-e9b5-41ff-9d40-e0c06e10d0e2)
![image](https://github.com/user-attachments/assets/4c8b7ddb-7b4d-4a52-8d3d-11aadc1cbd4a)
In portrait mode:
![image](https://github.com/user-attachments/assets/88b41f24-cae6-4834-8445-517e7fcc3f9c)
## Conclusion
Building **NeoMnemo** was an interesting experience for me. It pushed me to explore more SwiftUI, Apple's APIs, and rethink how app development works. As someone who has been writing C/C++ code for the last few years as part of my school curriculum, building an app was a truly rewarding experience. I also realized once more the power of visual mnemonics and how it can be applied to improve education, and I felt how fulfilling it was to craft an app that helps others study more effectively. I'm proud of the growth I've experienced while developing **NeoMnemo** and what I was able to accomplish in just one month. I plan on expanding its features and continuing to update the app, and I hope this article inspires you to experiment with your own creative ideas and share them with the world using Swift.

View File

@ -0,0 +1,231 @@
---
title: Introduction to Vim
date: 2022-02-06
---
# Introduction to Vim
Vim is one of the most powerful text editors you can think of. In this article, I will introduce you some basic and useful Vim commands.
## Vim modes
There are two main modes in Vim. Normal Mode and Insert Mode.
In Insert Mode you can type on the document as in any other text editor. In Normal Mode you can execute commands to navigate though the file or modify it.
## Entering Normal Mode
Press the escape key to enter Normal Mode. (Most Vim users usually remap escape to caps lock. On a Mac you can do it using [Karabiner](https://github.com/pqrs-org/Karabiner-Elements).)
## Entering Insert Mode
To enter insert mode you just have to type `i` or `a` while on Normal Mode. If you entered Insert Mode with the `i` key, you will be able to insert text on the left side of your cursor. In contrast, with `a`, you will insert text on the right side of the cursor. `C` (Shift-c) deletes all the text from the cursor position to the end of the line and puts you in Insert Mode.
There is also `o` that allows you to insert text on the next line.
### Uppercased versions
`I` (Shift-i) will put you on insert mode on the beginning of the line.
`A` (Shift-a) will put you on insert mode on the end of the line. `O` (Shift-o) will put on insert mode on the previous line.
## Visual Mode
Visual Mode is used for selecting text.
- `v` Enter visual mode.
- `V` Enter visual line mode.
- `ctrl-v` Enter visual block mode.
## Exiting Vim
The most known way of exiting Vim is using `:wq` (write and quit) or `:q!` (quit without saving).
But, there is a more efficient ways of exiting Vim. You type `ZZ` (shift-zz) to exit Vim saving or `ZQ` (shift-zq) to exit it without saving.
- - - -
## Cursor Movement
### Basic Movement (HJKL)
- `H` Move to the left.
- `J` Move down.
- `K` Move up.
- `L` Move to the right.
### Movement word per word
- `w` Move to the next word.
- `W` Move to the next word (separated by whitespace).
- `b` Move a word backwards.
- `B` Move a word backwards (separated by whitespace).
- `e` Move to the end of the next word.
- `E` Move to the end of the next word (separated by whitespace).
You can use all of these commands with a count. e.g. `5w` to move the cursor 5 words forward.
### Moving the cursor
- `H` Put the cursor on the top.
- `M` Put the cursor on the middle.
- `L` Put the cursor on the bottom.
> Mnemonic: High, Middle, Low.
### Movement by paragraph
- `{` Move the cursor a paragraph up.
- `}` Move the cursor a paragraph down.
## Movement through the line
### Including whitespace
`0` Go to the beginning of the line.
`$` Go to the end of the line.
### Not including whitespace
`^` Go to the beginning of the line.
`g_` Go to the end of the line.
## G and gg
Use `G` Go to the bottom of the file.
Use`gg` Go to the top of the file.
## f and t
- `f` followed by a word `a` moves the cursor the the next occurrence of the word `a` on a line.
- `t` followed by a word `a` moves your cursor a word before the the next occurrence of the word `a` word on a line.
### Example
```
The quick brown fox jumps over the lazy dog.
^
```
Using `fb`
```
The quick brown fox jumps over the lazy dog.
^
```
Using `tb`
```
The quick brown fox jumps over the lazy dog.
^
```
## Moving the screen
- `zt` Put the current line on the top of the screen.
- `zz` Put the current line on the middle of the screen.
- `zb` Put the current line on the bottom of the screen.
- - - -
## Deleting text
To delete text, you can use the `d` followed by what you want to delete.
(You can use all of the following commands with `c` instead of `d` to delete and CHANGE what you just deleted. In other words, it deletes and puts you on Insert Mode.)
### Counts
In Vim you can specify the number of times to execute a command. For example, you can delete a line with `dd` and `5dd` for deleting 5 lines at once. It also works for other commands like `5dap`, `5daw`, etc.
### Some delete commands
- `diw` Delete a word.
- `daw` Delete a word w/ surrounding whitespace.
- `di(` Delete inside a ().
- `da(` Delete a () w/ surrounding whitespace.
- `di[` Delete inside a [].
- `da[` Delete a [] w/ surrounding whitespace.
- `di{` Delete inside a {}.
- `da{` Delete a {} w/ surrounding whitespace.
- `dip` Delete inside a paragraph.
- `dap` Delete a paragraph w/ surrounding whitespace.
- `dit` Delete inside an HTML tag.
- `dat` Delete an HTML tag w/ surrounding whitespace.
- `dw` Delete word, only works if the cursor is positioned on the beginning of the word.
- `D` Delete from the cursor to the end of the line.
- `dd` Delete the current line.
- `5dd` Delete the next 5 lines.
- `dG` Delete from the current line until the end of the document.
- `dgg` Delete form the current line until the beginning of the document.
## Copying and Pasting
- `y` (for YANK) copy. (e.g. to copy a word type `yw`)
- `yy` Copy a whole line.
- `p` Paste.
## Searching
- `/` Search. (Navigate with n/N)
- `?` Search backwards. (Navigate with n/N)
- `//` Search for the last pattern searched.
- `??` Search for the last pattern searched backwards.
- `*` Search the current word.
- `#` Search the current word backwards.
- `:set ic` Search case insensitively.
## Replacing Text
```
:%s/old/new/g
:%s/old/new/gi (case insensitive)
:%s/old/new/gc (prompts before each replacement)
```
## Undo and Redo
- `u` Undo the last change.
- `U` Undo the last changes on the current line.
- `ctrl-r` Redo the last change.
## Tabs
- `:tabnew` Open a new tab.
- `:tabnext` Go to the next tab. (Also `:tabn`)
- `:tabprevious` Go to the previous tab. (Also `:tabp`)
- `:tabfirst` Go to the first tab.
- `:tablast` Go to the last tab.
- `:tabmove (num)` Move the current tab to the specified `num`.
## Editing
`edit` or `e` open a file in Vim. (On a new tab for example.)
## Macros
Start / Stop recording a macro with `q` on one of Vims 26 registers (a-z).
Use `@` with the register key to play the recorded macro.
You can play it `n` times with `n@q` with `q` being the register.
## Marks
Record your current position in a register.
- `mb` Set a mark on register `b`. (Using an uppercased mark like `mB` makes it accessible on all the files being edited.)
- `b` Go the the mark set on `b`.
- `:marks` List all the current marks.
- `:delmarks b` Delete a mark.
- `:delmarks a-c` Delete a range of marks.
- `:demarks!` Delete all the marks.
## . command
Rerun the last executed command on Normal Mode.
## g
- `g` + hjkl, 0, $, etc navigates an one line paragraph.
- `gq` Reformat an one line paragraph.
- `ga` Give you the ASCII value of the current character.
- `gf` Open the file under the cursor.
- `gi` Continue inserting text to where you were before.
## Indentation
Indent the current line using `<` and `>`.
## Running a Shell command
Use `:!` to run a Shell command.
e.g. `:! ls` to run `ls`.
## %
`%` Jump to the matching parenthesis, brackets, etc.

BIN
content/Vim/media/vim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

38
content/index.md Normal file
View File

@ -0,0 +1,38 @@
---
title: "Komeno"
---
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
}
@media (min-width: 801px) {
.container {
justify-content: center;
min-height: 70vh;
}
}
@media (max-width: 800px) {
.container {
justify-content: space-evenly;
min-height: 40vh;
padding-top: 4vh;
padding-bottom: 4vh;
}
}
</style>
<div class="container" style="display: flex; flex-direction: column; align-items: center; text-align: center;">
<img src="media/index/icon.png" alt="icon" width="150" />
<div style="font-size: 24px; font-weight: bold; margin-top: 6px;">
Komeno
</div>
<p style="margin-top: 6px; max-width: 500px; padding: 0 15px;">
JapaneseBrazilian software engineer and Google AI Student Ambassador. École 42 (Paris/Tokyo) alum, currently at Tokyo University of Foreign Studies.
</p>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -2,13 +2,13 @@ import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
/**
* Quartz 4 Configuration
* Quartz 4.0 Configuration
*
* See https://quartz.jzhao.xyz/configuration for more information.
*/
const config: QuartzConfig = {
configuration: {
pageTitle: "Quartz 4",
pageTitle: "🍚 riceset",
pageTitleSuffix: "",
enableSPA: true,
enablePopovers: true,
@ -16,16 +16,17 @@ const config: QuartzConfig = {
provider: "plausible",
},
locale: "en-US",
baseUrl: "quartz.jzhao.xyz",
baseUrl: "riceset.com",
ignorePatterns: ["private", "templates", ".obsidian"],
defaultDateType: "modified",
defaultDateType: "created",
generateSocialImages: false,
theme: {
fontOrigin: "googleFonts",
cdnCaching: true,
typography: {
header: "Schibsted Grotesk",
body: "Source Sans Pro",
code: "IBM Plex Mono",
header: "Bricolage Grotesque",
body: "Poppins",
code: "JetBrains Mono",
},
colors: {
lightMode: {
@ -57,7 +58,7 @@ const config: QuartzConfig = {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "git", "filesystem"],
priority: ["frontmatter", "filesystem"],
}),
Plugin.SyntaxHighlighting({
theme: {
@ -86,10 +87,7 @@ const config: QuartzConfig = {
}),
Plugin.Assets(),
Plugin.Static(),
Plugin.Favicon(),
Plugin.NotFoundPage(),
// Comment out CustomOgImages to speed up build time
Plugin.CustomOgImages(),
],
},
}

View File

@ -8,8 +8,6 @@ export const sharedPageComponents: SharedLayout = {
afterBody: [],
footer: Component.Footer({
links: {
GitHub: "https://github.com/jackyzha0/quartz",
"Discord Community": "https://discord.gg/cRFFHYye7t",
},
}),
}
@ -17,52 +15,70 @@ export const sharedPageComponents: SharedLayout = {
// components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = {
beforeBody: [
Component.ConditionalRender({
component: Component.Breadcrumbs(),
condition: (page) => page.fileData.slug !== "index",
}),
Component.ArticleTitle(),
Component.ContentMeta(),
Component.TagList(),
],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Flex({
components: [
{
Component: Component.Search(),
grow: true,
},
{ Component: Component.Darkmode() },
{ Component: Component.ReaderMode() },
],
}),
//Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.LinksList({
links: {
"E-Mail": "mailto:riceset@icloud.com",
GitHub: "https://github.com/riceset",
LinkedIn: "https://www.linkedin.com/in/riceset/",
}
})),
Component.Explorer(),
],
right: [
Component.Graph(),
Component.DesktopOnly(Component.TableOfContents()),
Component.Backlinks(),
Component.DesktopOnly(Component.RecentNotes({
title: "Latest",
limit: 8
})),
Component.MobileOnly(Component.RecentNotes({
title: "Latest",
limit: 1
})),
Component.MobileOnly(Component.LinksList({
links: {
GitHub: "https://github.com/riceset",
LinkedIn: "https://www.linkedin.com/in/riceset/",
}
}))
],
}
// components for pages that display lists of pages (e.g. tags or folders)
export const defaultListPageLayout: PageLayout = {
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()],
beforeBody: [],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Flex({
components: [
{
Component: Component.Search(),
grow: true,
},
{ Component: Component.Darkmode() },
],
}),
//Component.Search(),
Component.Darkmode(),
Component.DesktopOnly(Component.LinksList({
links: {
"E-Mail": "mailto:riceset@icloud.com",
GitHub: "https://github.com/riceset",
LinkedIn: "https://www.linkedin.com/in/riceset/",
}
})),
Component.Explorer(),
],
right: [],
right: [
Component.DesktopOnly(Component.RecentNotes({
title: "Latest",
limit: 8
})),
Component.MobileOnly(Component.RecentNotes({
title: "Latest",
limit: 1
})),
Component.MobileOnly(Component.LinksList({
links: {
GitHub: "https://github.com/riceset",
LinkedIn: "https://www.linkedin.com/in/riceset/",
}
}))
],
}

View File

@ -1,48 +1,10 @@
// @ts-ignore
import darkmodeScript from "./scripts/darkmode.inline"
import styles from "./styles/darkmode.scss"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"
const Darkmode: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
return (
<button class={classNames(displayClass, "darkmode")}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
class="dayIcon"
x="0px"
y="0px"
viewBox="0 0 35 35"
style="enable-background:new 0 0 35 35"
xmlSpace="preserve"
aria-label={i18n(cfg.locale).components.themeToggle.darkMode}
>
<title>{i18n(cfg.locale).components.themeToggle.darkMode}</title>
<path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5 c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"></path>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
version="1.1"
class="nightIcon"
x="0px"
y="0px"
viewBox="0 0 100 100"
style="enable-background:new 0 0 100 100"
xmlSpace="preserve"
aria-label={i18n(cfg.locale).components.themeToggle.lightMode}
>
<title>{i18n(cfg.locale).components.themeToggle.lightMode}</title>
<path d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571 C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23 c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369 c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65 c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z"></path>
</svg>
</button>
)
const Darkmode: QuartzComponent = (_props: QuartzComponentProps) => {
return null
}
Darkmode.beforeDOMLoaded = darkmodeScript
Darkmode.css = styles
export default (() => Darkmode) satisfies QuartzComponentConstructor

View File

@ -0,0 +1,39 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/footer.scss"
interface Options {
links: Record<string, string>
}
export default ((opts?: Options) => {
const LinksList: QuartzComponent = ({ displayClass }: QuartzComponentProps) => {
const links = opts?.links ?? []
return (
<>
<style>
{`
@media (max-width: 800px) {
.contact-header {
text-align: center;
}
.contact-list {
text-align: center;
}
}
`}
</style>
<div class={`${displayClass ?? ""}`}>
<h3 class="contact-header" style={{ margin: "0.5rem 0 0 0", fontSize: "1rem" }}>Social</h3>
<ul class="contact-list" style={{ listStyleType: "none", padding: 0, margin: "1rem 0 0 0" }}>
{Object.entries(links).map(([text, link]) => (
<li><a href={link}>{text}</a></li>
))}
</ul>
</div>
</>
)
}
LinksList.css = style
return LinksList
}) satisfies QuartzComponentConstructor

View File

@ -7,17 +7,40 @@ const PageTitle: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzCompo
const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title
const baseDir = pathToRoot(fileData.slug!)
return (
<h2 class={classNames(displayClass, "page-title")}>
<a href={baseDir}>{title}</a>
</h2>
<>
<link
href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@400;600;700&display=swap"
rel="stylesheet"
/>
<h2 class={classNames(displayClass, "page-title")}>
<a href={baseDir}>{title}</a>
</h2>
</>
)
}
PageTitle.css = `
/* Default (light mode) */
:root {
--primary-color: black;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
:root {
--primary-color: white;
}
}
.page-title {
font-size: 1.75rem;
font-size: 1.5rem;
margin: 0;
font-family: var(--titleFont);
color: var(--primary-color);
}
.page-title a {
text-decoration: none;
color: inherit;
}
`

View File

@ -23,6 +23,7 @@ import Breadcrumbs from "./Breadcrumbs"
import Comments from "./Comments"
import Flex from "./Flex"
import ConditionalRender from "./ConditionalRender"
import LinksList from "./LinksList"
export {
ArticleTitle,
@ -50,4 +51,5 @@ export {
Comments,
Flex,
ConditionalRender,
LinksList,
}