Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lexa #38

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions algorithms/RabinCarp/_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title: "Язык C"
description: "Алгоритмы"
copyright: "© Группа 22210"

header_pages:
- index.md
25 changes: 25 additions & 0 deletions algorithms/RabinCarp/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Алгоритм Рабина-Карпа #
## Краткое введение ##
Шо это такое, этот ваш Рабин-Карп? Вроде ни рыба, ни мясо, а оказывается это целый алгоритм поиска шаблона в некоторой строке, изюминкой которого является использование скользящего хэширования для выполнения своей задачи.
## Описание алгоритма ##
Нам выдаётся некоторый шаблон длиной **n**, который нам нужно найти в тексте или же в другой строке. Шаблоном может быть какой-то набор символов, набор слов и т.д. Хэш-функция, которой мы будем пользоваться, называется полиномиальной и базируется на принципах модульной арифметики, всё это для того, чтобы наш хэш (к слову, хэш - это какое-то число) не превратился в цифрового монстра, состоящего из десятков десятичных разрядов, а выглядит эта хэш-функция как-то так:

**hash(s[n]) = ((s[0] * 1) + (s[1] * p) + ... + (s[n-1] * p^(n-1))) mod q**;

Где **s[n]** - последоватльность символов, хэш которой надо вычислить, **s[i]** - какой-то символ, лежащий в нашей строке, **n** - длина этой последоватльности, **q** - большое простое число,
**p** - натуральное число (**p** < **q**).
Мы заранее вычисляем хэш от нашего шаблона длиной **n** и запоминаем его. Далее считываем **n** символов из нашего текста и также вычисляем для этой последовавтельности хэш, используя нашу хэш-функцию. Теперь на руках мы имеем два числа: хэш шаблона и хэш прочитанного из текста, если эти два числа равны, то мы подробно сравниваем наш шаблон и то, что мы прочитали из текста, и тогда уже выносим вердикт о том, нашли ли мы шаблон в тексте, если же они не равны, то подробно рассматривать их смысла нет, так как хэш-функция дала ответ об их похожести.
Алгоритм Рабина-Карпа подразумевает, что мы будем сдвигать наше "окошко" считывания на один символ, значит нам надо как-то оптимизировать процесс вычисления хэша, не будем же мы как глупые хомячки считать заново эти большие числа, для строки, которая отличается от придыдущей всего на одну букоуку. Воспользумся слудующим "**приколом**":

**hash = (hash - (s[0] mod q) / p** (здесь мы убрали из уже полученного хэша значение первого элемента подстроки, считанной из текста);
*/#Здесь должен был быть код, но мне лень, так что опишу словами. После того, как мы пересчитали хэш подстроки, мы удаляем первый символ из нашей подстроки и смещаем все оставшиеся элементы влево на 1. На конце строки у нас появилось вакантное место, куда мы считываем новый символ из текста.#/*

**hash = hash + (s[n-1] mod q) * p^(n-1)** (здесь мы добавили новый символ в значение хэша);

Таким образом мы избавили себя от необходимости пересчитывать весь хэш заново. Благодаря этому "**приколу**" данный подход иногда обзыват скользящим хэшированием.
А дальше бы проводим все те же сравнения, которые были описаны выше, пока не дойдём до конца текста.
## В чём фичи данного алгоритма, и где он нужен? ##
Вся крутость этого алгоритма раскрывается, когда необходимо найти в тексте вхождения множества разных шаблонов. Если же нам просто необходимо найти какое-то конкретное слово в тексте, то это обращайтесь по другому адресу, таким мы не занимаемся. В наилучшем и среднем случае время выполения алгоритма равно **O(n)**, в худшме же - **O(nm)**, где **n** и **m** длины текста и шаблона соответственно, зависит скорость от выбора коэффицентов **p** и **q**. Чаще всего этот алгоритм встречается в системах антиплагиата.

Вроде бы на этом всё, больше ничего не знаю. Я умею только дышать и жаловаться на общагу №7.
Алгоритм Рабина-Карпа в 20 строк C-кода [здесь](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
6 changes: 6 additions & 0 deletions c-language/Multithreading/_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
title: "Язык C"
description: "Вещественные типы"
copyright: "© Группа 21212"

header_pages:
- multithreading.md
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
209 changes: 209 additions & 0 deletions c-language/Multithreading/multithreading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# Многопоточное программирование.
## Что появилось раньше - курица или яйцо?
-Давным-давно кампутеры не были такими крутыми и балдёжными и имели в своём процессоре всего одно ядро.
-И чё?
-А то, что выполянть какие-либо команды приходилось поочерёдно, что естественно было скучно и грустно, но потом крутые и умные ребята придумали делать процессоры **многоядерными**, что позволило выполнять разные задачи на разных ядрах одновременно.
-А как это связано с многопоточностью?
-Ну, практически никак, потому что **многозадачность** - способность выполнять **много** задач одновременно, а **многопоточность** - способность выполянть **одну** задачу, **но** все необходимые вычисления производить **параллельно**.
-И как это всё работает?
-Смотри, у нас есть процессор, он имеет несколько ядер, каждое ядро работает с каким-то одним процессом (программой), этот процесс ядро может выполнять в один поток, либо же в несколько.
Вот посмотри на картиночку:
![](https://github.com/Maslyanko/c-language-course/blob/main/c-language/Multithreading/image/2023-06-11_22-00-26.png)
-На картиночке красной рамкой обозначены ядра, **в ядре находится** какой-то **процесс**, у которого есть собственное адресное пространство, код, и другие приколдесы. **В каждом процессе находится один или несколько потоков** (зависит от того, как написана программа), каждый поток имеет свой набор регистров, стек, счётчик команд и ID потока. Для реализации нескольких потоков требуется определённый опыт, потому что необходимо значть что вообще нужно параллелить, может быть и такое, что многопоточнсть вообще навредит программе и всё только замедлит.
-Если такое может произойти, тогда зачем это всё?
-Кто не рискует, тот не пьёт молочный улун. Многие современные программы написаны под работу на множестве потоков, и это действительно всё ускоряет в несколько раз. Существует **закон Амдаля**, позволяющий найти во сколько раз множество потоков быстрее одного потока: **k = 1/(F + (1-F)/N)**, где **F** - доля кода, которая выполняется в один поток, **N** - кол-во потоков.
## Перейдём к практике
-Пусть нам даны две матрицы, **A** - размера **SxN** и **B** - размера **NxM**, нам необходимо эти матрицы перемножить. Если делать наивным способом (перемножение строк и стобцов поочерёдно), то время работы такого алгоритма будет равно **O(SxNxM)**, давайте попробуем сюда подвести потоки.
-С чего начнём?
-Начнём с того, что определим для себя что мы будем параллелить. Мы будем заставлять каждый поток искать сразу строчку новой матрицы. Хорошо, с задачей определились, теперь подключаем `pthreads.h` и погнали. Для рассмотрения различных ситуюёвин усложним себе задачу и ограничим кол-во потоков. Допустим, что мы уже умеем считывать и запоминать матрицу, поэтому сразу переходим окунёмся в параллелизм. Для начала сформируем струкутуру, в которой будет лежать всё необходимое для работы потока:
```
typedef struct TTask {
int** A;
int** B;
int** C;
int S, N, M, ROW;
} TTask;
```
-Теперь поток всё знает для работы: матрицы A, B, C (матрица-ответ), размеры наших матриц, а также ROW - номер строки, которую поток должен вычислять в матрице С. Так как мы определелил для себя, что будем вычислять сразу строку в матрице С, то таких задач будет всего S штук.
-А где потоки то?
-Да подожи ты, сейчас всё будет:
```
int threadsSize = 10;
pthread_t* threads = malloc(threadsSize * sizeof(pthread_t));
```
-Таким образом мы создали 10 потоков, объединённых в один массив. Как можно заметить ptrhread_t - это отдельный "тип данных", если так можно сказать. Теперь мы должны их заставить работать над нашей задачей, напишем функцию `Mult(...)`, которая будет вычислять строку в матрице С:
```
void Mult (TTask* task) {
for (int i = 0; i < task->M; ++i) {
int row = task->ROW;
for (int j = 0; j < task->N; ++j) {
task->C[row][i] += task->A[row][j] * task->B[j][i];
}
}
}
```
-A где потоки то?
-Паренёк, не беги вперёд паравоза, дело в том, что задач может быть больше, чем 10, тогда что делать, а?
-Может заставить их работать как-то поочерёдно? Чтобы поток взял задачу из списка и начал на дней работать, потом другой взял и тд, пока весь список задач не закончится.
-Да ты прав, НО потоки могут заходить в одну область кода одновременно, и получится, что все десять потоков будут обрабатывать одну задачу, тогда получится какая-то белеберда.
-А как тогда?
-А я сейчас покажу, как ты и говорил, организуем структуру:
```
typedef struct TTaskQueue{
pthread_mutex_t Mutex;
TTask* tasks;
int size;
int head;
} TTaskQueue;
```
-Здесь мы организовали очередь на массиве, но тут есть какой-то самозванец - какой-то Mutex.
-Это что такое?
-Если вкратце, то это такая структура данных, которая позволяет ограничивать вход в часть кода другим потокам, если один поток уже находится в этой части кода. Таким образом мы организуем работу потоков: при помощи `Mutex`-a будем раздавать потокам задачи, пока наша очередь не кончится. Отлично напишем функцию работы потока:
```
void* FindRow(void* data) {
TTaskQueue* queue = (TTaskQueue*) data;
while(1) {
TTask* curtask = NULL;
pthread_mutex_lock(&queue->Mutex);
if (queue->head < queue->size) {
curtask = &queue->tasks[queue->head++];
}
pthread_mutex_unlock(&queue->Mutex);
if (curtask == NULL) {
return 0;
}
Mult(curtask);
}
}
```
-Что значит фугкция работы потока?
-Это такая функция, в которой поток будет производить вычисления. При помощи вот этой штуковины:
```
for (int i = 0; i < threadsSize; ++i) {
pthread_create(&threads[i], NULL, FindRow, &queue);
}
```
Мы все наши потоки бросаем в бой, в качестве первого аргумента функции `pthread_create(...)` мы передаём адрес ID потока, вторым аргументом явлются атрибуты потока (NULL - стандартный набор атрибутов), третиьим - потоковая функция, четвёртым - данные, над которыми будет работать поток.
-Ага это понятно, но допустим, что мы заставили все эти 10 потоков бегать по коду для вычисления нашей матрицы, а остальная программа типа что делает делает? Что в это время происходит в `main`?
-Замечательный вопрос! Мы заставим основной поток программы (который находится в `main`) дожидаться потоков-малышей при помощи:
```
for (int i = 0; i < threadsSize; ++i) {
pthread_join(threads[i], NULL);
}
```
Эта функция заставляет ждать встретивший её поток до тех пор, пока не завершится поток, который указан в первом аргументе.
-Это всё?
-Да это всё! Вот весь код:
```
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct TTask {
int** A;
int** B;
int** C;
int S, N, M, ROW;
} TTask;

typedef struct TTaskQueue{
pthread_mutex_t Mutex;
TTask* tasks;
int size;
int head;
} TTaskQueue;

void Mult (TTask* task) {
for (int i = 0; i < task->M; ++i) {
int row = task->ROW;
for (int j = 0; j < task->N; ++j) {
task->C[row][i] += task->A[row][j] * task->B[j][i];
}
}
}

void FillMatrices(int** A, int** B, int** C, int S, int N, int M) {
printf("Enter matrix A (SxN)\n");
for (int i = 0; i < S; ++i) {
A[i] = (int*) malloc(N * sizeof(int));
for (int j = 0; j < N; ++j) {
scanf("%d", &A[i][j]);
}
}
printf("Enter matrix B (NxM)\n");
for (int i = 0; i < N; ++i) {
B[i] = (int*) malloc(M * sizeof(int));
for (int j = 0; j < M; ++j) {
scanf("%d", &B[i][j]);
}
}
for (int i = 0; i < S; ++i) {
C[i] = (int*) calloc(M, sizeof(int*));
}
}

void* FindRow(void* data) {
TTaskQueue* queue = (TTaskQueue*) data;
while(1) {
TTask* curtask = NULL;
pthread_mutex_lock(&queue->Mutex);
if (queue->head < queue->size) {
curtask = &queue->tasks[queue->head++];
}
pthread_mutex_unlock(&queue->Mutex);
if (curtask == NULL) {
return 0;
}
Mult(curtask);
}
}

int main() {
int S, N, M;
printf("Enter the dimensions of the matrices A (SxN), B (NxM)\n");
scanf("%d%d%d", &S, &N, &M);
int** A = (int**) malloc(S * sizeof(int*));
int** B = (int**) malloc(N * sizeof(int*));
int** C = (int**) malloc(S * sizeof(int*));
FillMatrices(A, B, C, S, N, M);

//-------------------------------------------------------------//

int threadsSize = 10;
pthread_t* threads = malloc(threadsSize * sizeof(pthread_t));
TTaskQueue queue;
queue.tasks = (TTask*) malloc(S * sizeof(TTask));
queue.size = S;
queue.head = 0;
pthread_mutex_init(&queue.Mutex, NULL);
for (int i = 0; i < S; ++i) {
queue.tasks[i].A = A;
queue.tasks[i].B = B;
queue.tasks[i].C = C;
queue.tasks[i].S = S;
queue.tasks[i].N = N;
queue.tasks[i].M = M;
queue.tasks[i].ROW = i;
}

//-------------------------------------------------------------//

for (int i = 0; i < threadsSize; ++i) {
pthread_create(&threads[i], NULL, FindRow, &queue);
}
for (int i = 0; i < threadsSize; ++i) {
pthread_join(threads[i], NULL);
}

//-------------------------------------------------------------//

printf("Result:\n");
for (int i = 0; i < S; ++i) {
for (int j = 0; j < M; ++j) {
printf("%d ", C[i][j]);
}
printf("\n");
}
}
```
-Ну вот и сё, чё, не так уж и сложно это было :)
Loading