Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,42 @@ on:

jobs:
# Golang check
lint-go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.25'
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
with:
working-directory: ./backend
# lint-go:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4

# - name: Check if backend exists
# run: |
# if [ ! -d "./backend" ] || [ -z "$(ls -A ./backend 2>/dev/null)" ]; then
# echo "No go files to lint, skipping."
# exit 0
# fi

# - uses: actions/setup-go@v5
# with:
# go-version: '1.23'
# - name: Run golangci-lint
# uses: golangci/golangci-lint-action@v6
# with:
# working-directory: ./backend

# C (Google Style)
lint-c:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install clang-tools
run: sudo apt-get install -y clang-format clang-tidy
run: sudo apt-get update && sudo apt-get install -y clang-format clang-tidy cmake

- name: Check C formatting (clang-format)
run: find ./algorithm -name '*.c' -o -name '*.h' | xargs clang-format --Werror --dry-run
run: |
find ./algorithm -name "*.c" -o -name "*.h" | xargs clang-format --Werror --dry-run

- name: Run C linter (clang-tidy)
run: find ./algorithm -name '*.c' | xargs clang-tidy --warnings-as-errors="*" --
run: |
clang-tidy ./algorithm/src/algorithm.c -- -I./algorithm/include
clang-tidy ./algorithm/tests/test_algorithm.c -- -I./algorithm/include

# Python Ruff
lint-python:
Expand Down
4 changes: 1 addition & 3 deletions algorithm/.clang-format
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
Language: Cpp
BasedOnStyle: Google

Expand All @@ -9,5 +8,4 @@ UseTab: Never

PointerAlignment: Left

SortIncludes: true
---
SortIncludes: true
17 changes: 2 additions & 15 deletions algorithm/.clang-tidy
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
---
Checks: >
-*,
bugprone-*,
clang-analyzer-*,
performance-*,
portability-*,
readability-*,
-readability-identifier-length,
-readability-magic-numbers,
-bugprone-easily-swappable-parameters

WarningsAsErrors: '*'
Checks: '-*,bugprone-*,cert-*,clang-analyzer-*,performance-*,portability-*,readability-*, -readability-identifier-length, -readability-magic-numbers, -bugprone-easily-swappable-parameters'

HeaderFilterRegex: '.*'

CheckOptions:
- key: readability-braces-around-statements.ShortStatementLines
value: '1'
---
value: '1'
15 changes: 14 additions & 1 deletion algorithm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@ project(SpacedRepetition C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED True)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Werror")
endif()

include_directories(include)

add_library(spaced_rep STATIC
src/algorithm.c
)
)

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_algorithm.c")
add_executable(test_algorithm
tests/test_algorithm.c
)
target_link_libraries(test_algorithm spaced_rep)
endif()
35 changes: 35 additions & 0 deletions algorithm/include/algorithm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef ALGORITHM_H
#define ALGORITHM_H

#ifdef __cplusplus
extern "C" {
#endif

// struct, current state card
struct CardState {
int interval; // current interval
float easiness; // diff level
int repetitions; // accurace repeat
};

// struct for returning new data
struct ReviewResult {
int next_interval; // new interval
float new_easiness; // update diff level
int repetitions; // update quantity repeat
int quality; // grade
};

/**
* main function
* @param similarity_percent match percentage
* @param current_state current param card
* @return update param card for save in DB
*/
struct ReviewResult calculate_next_review(float similarity_percent, struct CardState current_state);

#ifdef __cplusplus
}
#endif

#endif
54 changes: 54 additions & 0 deletions algorithm/src/algorithm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "algorithm.h"

struct ReviewResult calculate_next_review(float similarity_percent,
struct CardState current_state) {
struct ReviewResult result;
int quality = 0;

// percent -> grade
if (similarity_percent < 40.0F) {
quality = 0; // bad
} else if (similarity_percent < 60.0F) {
quality = 2; // normal
} else if (similarity_percent < 80.0F) {
quality = 4; // good
} else {
quality = 5; // perfect
}

result.quality = quality;

// new Easiness Factor (EF) on formula SM-2
// EF = EF + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02))
float q_diff = 5.0F - (float)quality;
float new_ef = current_state.easiness + (0.1F - (q_diff * (0.08F + (q_diff * 0.02F))));

// EF != <1.3
if (new_ef < 1.3F) {
new_ef = 1.3F;
}
result.new_easiness = new_ef;

// update counter
if (quality < 3) {
// bad answer -> progress 0
result.repetitions = 0;
result.next_interval = 1;
} else {
// good answer -> +1 counter
result.repetitions = current_state.repetitions + 1;

// calculation new interval
if (result.repetitions == 1) {
result.next_interval = 1;
} else if (result.repetitions == 2) {
result.next_interval = 6;
} else {
// + 0.5F for correct rounding
result.next_interval =
(int)((((float)current_state.interval) * result.new_easiness) + 0.5F);
}
}

return result;
}
70 changes: 70 additions & 0 deletions algorithm/tests/test_algorithm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <assert.h>
#include <stdio.h>

#include "algorithm.h"

// auxiliary function
struct CardState apply_result_to_card(struct ReviewResult res) {
struct CardState new_card;
new_card.interval = res.next_interval;
new_card.easiness = res.new_easiness;
new_card.repetitions = res.repetitions;
return new_card;
}

int main() {
printf("Starting algorithm tests...\n");

// new card
struct CardState card = {0, 2.5F, 0};
struct ReviewResult result;

// test 1: bad answer
printf("Test 1: Bad answer (< 40%%)\n");
result = calculate_next_review(20.0F, card);

assert(result.quality == 0);
assert(result.next_interval == 1);
assert(result.repetitions == 0);
assert(result.new_easiness < 2.5F);
card = apply_result_to_card(result);

// test 2 perfect answer
printf("Test 2: Perfect answer (1st time)\n");
result = calculate_next_review(95.0F, card);

assert(result.quality == 5);
assert(result.repetitions == 1);
assert(result.next_interval == 1);
assert(result.new_easiness > card.easiness);
card = apply_result_to_card(result);

// test 3 perfect answer (2)
printf("Test 3: Perfect answer (2nd time)\n");
result = calculate_next_review(85.0F, card);

assert(result.quality == 5);
assert(result.repetitions == 2);
assert(result.next_interval == 6);
card = apply_result_to_card(result);

// test 4 perfect answer (3)
printf("Test 4: Perfect answer (3rd time)\n");
result = calculate_next_review(100.0F, card);

assert(result.quality == 5);
assert(result.repetitions == 3);
assert(result.next_interval > 6);
card = apply_result_to_card(result);

// test 5 bad answer
printf("Test 5: Bad answer after streak\n");
result = calculate_next_review(10.0F, card);

assert(result.quality == 0);
assert(result.repetitions == 0);
assert(result.next_interval == 1);

printf("SUCCESS! All assertions passed. Algorithm works perfectly!\n");
return 0;
}
Loading