Partial modification
841
content/Algorithms/The Dining Philosophers Problem.md
Normal 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);
|
||||
}
|
||||
```
|
||||
372
content/Algorithms/Understanding Binary Insertion Sort.md
Normal 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]
|
||||
$$
|
||||
248
content/Algorithms/Understanding Insertion Sort.md
Normal 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]
|
||||
$$
|
||||
BIN
content/Algorithms/media/DALL·E Feb 19 Design.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
content/Algorithms/media/drawing.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
content/Algorithms/media/philo.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
141
content/C++/Makefile for C++ Projects.md
Normal 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.
|
||||
173
content/C++/The Orthodox Canonical Class Form.md
Normal 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, let’s 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 object’s 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 object’s 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
|
||||
}
|
||||
...
|
||||
};
|
||||
```
|
||||
93
content/C++/Understanding Casts in C++.md
Normal 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 you’re 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 it’s 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 it’s 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 you’re not sure whether the object you’re 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
|
After Width: | Height: | Size: 318 KiB |
BIN
content/C++/media/makefile.jpg
Normal file
|
After Width: | Height: | Size: 388 KiB |
216
content/C/Replicating the Print Function in C.md
Normal 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.
|
||||
113
content/Computer Science/Pointers.md
Normal 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 variable’s 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 let’s try assigning it the address of the variable `num`
|
||||
|
||||
```c
|
||||
pointer = #
|
||||
```
|
||||
|
||||
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 = #
|
||||
```
|
||||
|
||||
## 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?
|
||||
That’s 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 variable’s 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
|
||||
BIN
content/Computer Science/media/pointers.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
90
content/Data Structures/Stack Data Structure.md
Normal 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. Let’s 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 stack’s 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, let’s 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()***.
|
||||
BIN
content/Data Structures/media/stacks.jpg
Normal file
|
After Width: | Height: | Size: 423 KiB |
78
content/Linux/Signals in Linux.md
Normal 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 program’s ***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 (don’t 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);
|
||||
```
|
||||
BIN
content/Linux/media/signals.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
98
content/Miscellaneous/The XOR Swap.md
Normal 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.
|
||||
BIN
content/Miscellaneous/media/swap.jpg
Normal file
|
After Width: | Height: | Size: 347 KiB |
446
content/Swift/NeoMnemo.md
Normal 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
|
||||
|
||||
I’m 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 wasn’t easy. My education in Brazil didn’t 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. That’s 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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 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 text’s opacity in each card. If it’s on top, the text is fully visible, otherwise, it’s 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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
### Landscape Card View
|
||||
|
||||
I also implemented a different design for when the card is displayed in landscape mode:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
and this in portrait mode:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||

|
||||
|
||||
### 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 device’s 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:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
In portrait mode:
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
231
content/Vim/Introduction to Vim.md
Normal 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 Vim’s 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
|
After Width: | Height: | Size: 2.2 MiB |
38
content/index.md
Normal 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;">
|
||||
Japanese–Brazilian software engineer and Google AI Student Ambassador. École 42 (Paris/Tokyo) alum, currently at Tokyo University of Foreign Studies.
|
||||
</p>
|
||||
</div>
|
||||
BIN
content/media/index/icon.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
@ -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(),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@ -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/",
|
||||
}
|
||||
}))
|
||||
],
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
39
quartz/components/LinksList.tsx
Normal 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
|
||||
@ -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;
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||