Skip to content

Commit

Permalink
feat(fenwick): implement Fenwick trees
Browse files Browse the repository at this point in the history
  • Loading branch information
BastiDood committed Oct 15, 2024
1 parent 8661662 commit dbced16
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This repository contains reference implementations for various basic data struct
* [Binary Heap](./src/binary-heap/)
* [Singly-Linked List](./src/singly-linked-list/)
* [Doubly-Linked List](./src/doubly-linked-list/)
* [Fenwick Tree](./src/fenwick-tree/)

## Running the Code

Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
add_subdirectory(slice)
add_subdirectory(stack)
add_subdirectory(queue)
add_subdirectory(binary-heap)

add_subdirectory(binary-heap)
add_subdirectory(avl-tree)
add_subdirectory(fenwick-tree)

add_subdirectory(singly-linked-list)
add_subdirectory(doubly-linked-list)
9 changes: 9 additions & 0 deletions src/fenwick-tree/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_library(${PROJECT_NAME}_FenwickTree STATIC fenwick.c)
target_link_libraries(${PROJECT_NAME}_FenwickTree ${PROJECT_NAME}_Slice)
target_include_directories(${PROJECT_NAME}_FenwickTree PUBLIC .)

if(BUILD_TESTING)
add_executable(${PROJECT_NAME}_FenwickTree_Test test.c)
target_link_libraries(${PROJECT_NAME}_FenwickTree_Test ${PROJECT_NAME}_FenwickTree unity)
add_test(NAME ${PROJECT_NAME}_FenwickTree_Suite COMMAND $<TARGET_FILE:${PROJECT_NAME}_FenwickTree_Test>)
endif()
52 changes: 52 additions & 0 deletions src/fenwick-tree/fenwick.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>

#include "fenwick.h"
#include "slice.h"

// Obtain a mask for the least significant bit of `n` in constant time.
size_t least_significant_bit(const size_t n) {
return n & -n;
}

void fenwick_init(struct FenwickTree * const self, const struct Slice slice) {
// Accumulate the range indexes by only adding the current element to their immediate parent
for (size_t i = 1; i < slice.length; ++i) {
const size_t direct = i + least_significant_bit(i);
uint8_t * const parent = slice_try_get(slice, direct);
if (parent == NULL) continue;
*parent += slice_get(slice, i);
}
self->slice = slice;
}

struct FenwickTree fenwick_create(const struct Slice slice) {
struct FenwickTree self;
fenwick_init(&self, slice);
return self;
}

void fenwick_add(const struct FenwickTree * const self, size_t n, const size_t value) {
assert(n > 0);
while (n < self->slice.length) {
uint8_t * const sum = slice_try_get(self->slice, n);
assert(sum != NULL);
*sum += value;
n += least_significant_bit(n);
}
}

uint8_t fenwick_prefix_sum(const struct FenwickTree * const self, size_t n) {
uint8_t sum = 0;
while (n > 0) {
sum += slice_get(self->slice, n);
n -= least_significant_bit(n);
}
return sum;
}

uint8_t fenwick_range_sum(const struct FenwickTree * const self, const size_t start, const size_t end) {
assert(start <= end);
return fenwick_prefix_sum(self, end) - fenwick_prefix_sum(self, start);
}
26 changes: 26 additions & 0 deletions src/fenwick-tree/fenwick.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef FENWICK_H
#define FENWICK_H

#include "slice.h"

struct FenwickTree {
// Implementation is one-indexed, so the first item is always ignored.
struct Slice slice;
};

// Initializes a Fenwick tree index in-place. The internal `slice` is modified.
void fenwick_init(struct FenwickTree * self, struct Slice slice);

// Initializes a Fenwick tree index in-place. The internal `slice` is modified.
struct FenwickTree fenwick_create(struct Slice slice);

// Adds `value` to the item currently at index `n`.
void fenwick_add(const struct FenwickTree * self, size_t n, size_t value);

// Compute the prefix sum up to index `start` (inclusive).
uint8_t fenwick_prefix_sum(const struct FenwickTree * self, size_t n);

// Compute the range sum `(start, end]`.
uint8_t fenwick_range_sum(const struct FenwickTree * self, size_t start, size_t end);

#endif // !FENWICK_H
79 changes: 79 additions & 0 deletions src/fenwick-tree/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "fenwick.h"
#include "slice.h"
#include "unity.h"
#include "unity_internals.h"

void setUp(void) {}

void tearDown(void) {}

void prefix_sums_power_of_two_length(void) {
uint8_t bytes[9] = {0, 1, 5, 8, 2, 0, 8, 9, 3}; // 0th is unused
const struct FenwickTree fenwick = fenwick_create(slice_create_valid(bytes, 9));
TEST_ASSERT_EQUAL_UINT(0, fenwick_prefix_sum(&fenwick, 0));
TEST_ASSERT_EQUAL_UINT(1, fenwick_prefix_sum(&fenwick, 1));
TEST_ASSERT_EQUAL_UINT(6, fenwick_prefix_sum(&fenwick, 2));
TEST_ASSERT_EQUAL_UINT(14, fenwick_prefix_sum(&fenwick, 3));
TEST_ASSERT_EQUAL_UINT(16, fenwick_prefix_sum(&fenwick, 4));
TEST_ASSERT_EQUAL_UINT(16, fenwick_prefix_sum(&fenwick, 5));
TEST_ASSERT_EQUAL_UINT(24, fenwick_prefix_sum(&fenwick, 6));
TEST_ASSERT_EQUAL_UINT(33, fenwick_prefix_sum(&fenwick, 7));
TEST_ASSERT_EQUAL_UINT(36, fenwick_prefix_sum(&fenwick, 8));
}

void prefix_sums_non_power_of_two_length(void) {
uint8_t bytes[7] = {0, 6, 3, 9, 4, 10, 8}; // 0th is unused
const struct FenwickTree fenwick = fenwick_create(slice_create_valid(bytes, 7));
TEST_ASSERT_EQUAL_UINT(0, fenwick_prefix_sum(&fenwick, 0));
TEST_ASSERT_EQUAL_UINT(6, fenwick_prefix_sum(&fenwick, 1));
TEST_ASSERT_EQUAL_UINT(9, fenwick_prefix_sum(&fenwick, 2));
TEST_ASSERT_EQUAL_UINT(18, fenwick_prefix_sum(&fenwick, 3));
TEST_ASSERT_EQUAL_UINT(22, fenwick_prefix_sum(&fenwick, 4));
TEST_ASSERT_EQUAL_UINT(32, fenwick_prefix_sum(&fenwick, 5));
TEST_ASSERT_EQUAL_UINT(40, fenwick_prefix_sum(&fenwick, 6));
}

void range_sums_power_of_two_length(void) {
uint8_t bytes[9] = {0, 1, 5, 8, 2, 0, 8, 9, 3}; // 0th is unused
const struct FenwickTree fenwick = fenwick_create(slice_create_valid(bytes, 9));
TEST_ASSERT_EQUAL_UINT(35, fenwick_range_sum(&fenwick, 1, 8));
TEST_ASSERT_EQUAL_UINT(15, fenwick_range_sum(&fenwick, 1, 4));
TEST_ASSERT_EQUAL_UINT(17, fenwick_range_sum(&fenwick, 5, 7));
TEST_ASSERT_EQUAL_UINT(20, fenwick_range_sum(&fenwick, 4, 8));
}

void range_sums_non_power_of_two_length(void) {
uint8_t bytes[5] = {0, 1, 5, 8, 2}; // 0th is unused
const struct FenwickTree fenwick = fenwick_create(slice_create_valid(bytes, 5));
TEST_ASSERT_EQUAL_UINT(5, fenwick_range_sum(&fenwick, 1, 2));
TEST_ASSERT_EQUAL_UINT(13, fenwick_range_sum(&fenwick, 1, 3));
TEST_ASSERT_EQUAL_UINT(15, fenwick_range_sum(&fenwick, 1, 4));
TEST_ASSERT_EQUAL_UINT(8, fenwick_range_sum(&fenwick, 2, 3));
TEST_ASSERT_EQUAL_UINT(10, fenwick_range_sum(&fenwick, 2, 4));
TEST_ASSERT_EQUAL_UINT(2, fenwick_range_sum(&fenwick, 3, 4));
}

void update_tree_tests(void) {
uint8_t bytes[9] = {0, 1, 5, 8, 2, 0, 8, 9, 3}; // 0th is unused
const struct FenwickTree fenwick = fenwick_create(slice_create_valid(bytes, 9));
TEST_ASSERT_EQUAL_UINT(15, fenwick_range_sum(&fenwick, 1, 4));
TEST_ASSERT_EQUAL_UINT(22, fenwick_range_sum(&fenwick, 3, 8));

fenwick_add(&fenwick, 2, 8);
TEST_ASSERT_EQUAL_UINT(23, fenwick_range_sum(&fenwick, 1, 4));
TEST_ASSERT_EQUAL_UINT(22, fenwick_range_sum(&fenwick, 3, 8));

fenwick_add(&fenwick, 5, 1);
TEST_ASSERT_EQUAL_UINT(23, fenwick_range_sum(&fenwick, 1, 4));
TEST_ASSERT_EQUAL_UINT(23, fenwick_range_sum(&fenwick, 3, 8));
}

int main(void) {
UNITY_BEGIN();
RUN_TEST(prefix_sums_power_of_two_length);
RUN_TEST(prefix_sums_non_power_of_two_length);
RUN_TEST(range_sums_power_of_two_length);
RUN_TEST(range_sums_non_power_of_two_length);
RUN_TEST(update_tree_tests);
return UNITY_END();
}
4 changes: 3 additions & 1 deletion src/slice/slice.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ struct Slice slice_skip_n(struct Slice self, size_t n);
// Takes up to the first `n` elements in the slice.
struct Slice slice_take_n(struct Slice self, size_t n);

// Checked version of the subscript operator.
// Checked version of the subscript operator. Returns `NULL` if not found.
uint8_t * slice_try_get(struct Slice self, size_t n);

uint8_t slice_get(struct Slice self, size_t n);
uint8_t slice_get_first(struct Slice self);
uint8_t slice_get_last(struct Slice self);
Expand Down

0 comments on commit dbced16

Please sign in to comment.