Compare commits

...

7 Commits

Author SHA1 Message Date
Bananymous 3963afe343 BAN: Add unstable version of hash map
This version differs only when doing rebucket. If rebucket fails,
the whole hash map is invalidated. This allows rebucketing to use
moving instead of copying.
2023-12-19 22:23:28 +02:00
Bananymous 3b21cc90ae AOC2023: Add helper for downloading puzzle input 2023-12-19 22:22:31 +02:00
Bananymous 4e900804b8 AOC2023: Implement day16 2023-12-19 22:22:16 +02:00
Bananymous 9ec733904f AOC2023: Implement day15 2023-12-19 21:42:59 +02:00
Bananymous 3352640d09 LibC: strlen had to be marked not optimized... 2023-12-19 21:42:59 +02:00
Bananymous 637397dd2f LibC: Make memcpy and memset not optimized
GCC does some weird optimizations and breaks these functions
2023-12-19 21:42:59 +02:00
Bananymous 5edbb1d5c4 LibC: make execvp fail if no executable found 2023-12-19 21:42:59 +02:00
11 changed files with 511 additions and 56 deletions

View File

@ -7,10 +7,7 @@
namespace BAN
{
template<typename Container>
class HashMapIterator;
template<typename Key, typename T, typename HASH = BAN::hash<Key>>
template<typename Key, typename T, typename HASH = BAN::hash<Key>, bool STABLE = true>
class HashMap
{
public:
@ -35,12 +32,12 @@ namespace BAN
public:
HashMap() = default;
HashMap(const HashMap<Key, T, HASH>&);
HashMap(HashMap<Key, T, HASH>&&);
HashMap(const HashMap<Key, T, HASH, STABLE>&);
HashMap(HashMap<Key, T, HASH, STABLE>&&);
~HashMap();
HashMap<Key, T, HASH>& operator=(const HashMap<Key, T, HASH>&);
HashMap<Key, T, HASH>& operator=(HashMap<Key, T, HASH>&&);
HashMap<Key, T, HASH, STABLE>& operator=(const HashMap<Key, T, HASH, STABLE>&);
HashMap<Key, T, HASH, STABLE>& operator=(HashMap<Key, T, HASH, STABLE>&&);
ErrorOr<void> insert(const Key&, const T&);
ErrorOr<void> insert(const Key&, T&&);
@ -77,26 +74,26 @@ namespace BAN
friend iterator;
};
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>::HashMap(const HashMap<Key, T, HASH>& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>::HashMap(const HashMap<Key, T, HASH, STABLE>& other)
{
*this = other;
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>::HashMap(HashMap<Key, T, HASH>&& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>::HashMap(HashMap<Key, T, HASH, STABLE>&& other)
{
*this = move(other);
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>::~HashMap()
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>::~HashMap()
{
clear();
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>& HashMap<Key, T, HASH>::operator=(const HashMap<Key, T, HASH>& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>& HashMap<Key, T, HASH, STABLE>::operator=(const HashMap<Key, T, HASH, STABLE>& other)
{
clear();
m_buckets = other.m_buckets;
@ -104,8 +101,8 @@ namespace BAN
return *this;
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>& HashMap<Key, T, HASH>::operator=(HashMap<Key, T, HASH>&& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>& HashMap<Key, T, HASH, STABLE>::operator=(HashMap<Key, T, HASH, STABLE>&& other)
{
clear();
m_buckets = move(other.m_buckets);
@ -114,21 +111,21 @@ namespace BAN
return *this;
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::insert(const Key& key, const T& value)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::insert(const Key& key, const T& value)
{
return insert(key, move(T(value)));
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::insert(const Key& key, T&& value)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::insert(const Key& key, T&& value)
{
return emplace(key, move(value));
}
template<typename Key, typename T, typename HASH>
template<typename Key, typename T, typename HASH, bool STABLE>
template<typename... Args>
ErrorOr<void> HashMap<Key, T, HASH>::emplace(const Key& key, Args&&... args)
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::emplace(const Key& key, Args&&... args)
{
ASSERT(!contains(key));
TRY(rebucket(m_size + 1));
@ -140,15 +137,15 @@ namespace BAN
return {};
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::reserve(size_type size)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::reserve(size_type size)
{
TRY(rebucket(size));
return {};
}
template<typename Key, typename T, typename HASH>
void HashMap<Key, T, HASH>::remove(const Key& key)
template<typename Key, typename T, typename HASH, bool STABLE>
void HashMap<Key, T, HASH, STABLE>::remove(const Key& key)
{
if (empty()) return;
auto& bucket = get_bucket(key);
@ -163,15 +160,15 @@ namespace BAN
}
}
template<typename Key, typename T, typename HASH>
void HashMap<Key, T, HASH>::clear()
template<typename Key, typename T, typename HASH, bool STABLE>
void HashMap<Key, T, HASH, STABLE>::clear()
{
m_buckets.clear();
m_size = 0;
}
template<typename Key, typename T, typename HASH>
T& HashMap<Key, T, HASH>::operator[](const Key& key)
template<typename Key, typename T, typename HASH, bool STABLE>
T& HashMap<Key, T, HASH, STABLE>::operator[](const Key& key)
{
ASSERT(!empty());
auto& bucket = get_bucket(key);
@ -181,8 +178,8 @@ namespace BAN
ASSERT(false);
}
template<typename Key, typename T, typename HASH>
const T& HashMap<Key, T, HASH>::operator[](const Key& key) const
template<typename Key, typename T, typename HASH, bool STABLE>
const T& HashMap<Key, T, HASH, STABLE>::operator[](const Key& key) const
{
ASSERT(!empty());
const auto& bucket = get_bucket(key);
@ -192,8 +189,8 @@ namespace BAN
ASSERT(false);
}
template<typename Key, typename T, typename HASH>
bool HashMap<Key, T, HASH>::contains(const Key& key) const
template<typename Key, typename T, typename HASH, bool STABLE>
bool HashMap<Key, T, HASH, STABLE>::contains(const Key& key) const
{
if (empty()) return false;
const auto& bucket = get_bucket(key);
@ -203,20 +200,20 @@ namespace BAN
return false;
}
template<typename Key, typename T, typename HASH>
bool HashMap<Key, T, HASH>::empty() const
template<typename Key, typename T, typename HASH, bool STABLE>
bool HashMap<Key, T, HASH, STABLE>::empty() const
{
return m_size == 0;
}
template<typename Key, typename T, typename HASH>
typename HashMap<Key, T, HASH>::size_type HashMap<Key, T, HASH>::size() const
template<typename Key, typename T, typename HASH, bool STABLE>
typename HashMap<Key, T, HASH, STABLE>::size_type HashMap<Key, T, HASH, STABLE>::size() const
{
return m_size;
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::rebucket(size_type bucket_count)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::rebucket(size_type bucket_count)
{
if (m_buckets.size() >= bucket_count)
return {};
@ -226,15 +223,20 @@ namespace BAN
if (new_buckets.resize(new_bucket_count).is_error())
return Error::from_errno(ENOMEM);
// NOTE: We have to copy the old entries to the new entries and not move
// since we might run out of memory half way through.
for (auto& bucket : m_buckets)
if constexpr(STABLE)
{
for (Entry& entry : bucket)
// NOTE: We have to copy the old entries to the new entries and not move
// since we might run out of memory half way through.
for (auto& bucket : m_buckets)
{
size_type bucket_index = HASH()(entry.key) % new_buckets.size();
if (new_buckets[bucket_index].push_back(entry).is_error())
return Error::from_errno(ENOMEM);
for (Entry& entry : bucket)
{
size_type bucket_index = HASH()(entry.key) % new_buckets.size();
if constexpr(STABLE)
TRY(new_buckets[bucket_index].push_back(entry));
else
TRY(new_buckets[bucket_index].push_back(BAN::move(entry)));
}
}
}
@ -242,20 +244,27 @@ namespace BAN
return {};
}
template<typename Key, typename T, typename HASH>
LinkedList<typename HashMap<Key, T, HASH>::Entry>& HashMap<Key, T, HASH>::get_bucket(const Key& key)
template<typename Key, typename T, typename HASH, bool STABLE>
LinkedList<typename HashMap<Key, T, HASH, STABLE>::Entry>& HashMap<Key, T, HASH, STABLE>::get_bucket(const Key& key)
{
ASSERT(!m_buckets.empty());
auto index = HASH()(key) % m_buckets.size();
return m_buckets[index];
}
template<typename Key, typename T, typename HASH>
const LinkedList<typename HashMap<Key, T, HASH>::Entry>& HashMap<Key, T, HASH>::get_bucket(const Key& key) const
template<typename Key, typename T, typename HASH, bool STABLE>
const LinkedList<typename HashMap<Key, T, HASH, STABLE>::Entry>& HashMap<Key, T, HASH, STABLE>::get_bucket(const Key& key) const
{
ASSERT(!m_buckets.empty());
auto index = HASH()(key) % m_buckets.size();
return m_buckets[index];
}
// Unstable hash map moves values between container during rebucketing.
// This means that if insertion to map fails, elements could be in invalid state
// and that container is no longer usable. This is better if either way you are
// going to stop using the hash map after insertion fails.
template<typename Key, typename T, typename HASH = BAN::hash<Key>>
using HashMapUnstable = HashMap<Key, T, HASH, false>;
}

View File

@ -17,7 +17,8 @@ int memcmp(const void* s1, const void* s2, size_t n)
return 0;
}
void* memcpy(void* dstp, const void* srcp, size_t n)
__attribute__((optimize("-O0")))
void* memcpy(void* __restrict__ dstp, const void* __restrict__ srcp, size_t n)
{
unsigned char* dst = static_cast<unsigned char*>(dstp);
const unsigned char* src = static_cast<const unsigned char*>(srcp);
@ -45,6 +46,7 @@ void* memmove(void* destp, const void* srcp, size_t n)
return destp;
}
__attribute__((optimize("-O0")))
void* memset(void* s, int c, size_t n)
{
unsigned char* p = static_cast<unsigned char*>(s);
@ -144,6 +146,7 @@ char* strndup(const char* str, size_t size)
}
#endif
__attribute__((optimize("-O0")))
size_t strlen(const char* str)
{
size_t len = 0;

View File

@ -183,7 +183,7 @@ int execve(const char* pathname, char* const argv[], char* const envp[])
int execvp(const char* file, char* const argv[])
{
char buffer[1024];
const char* pathname = file;
const char* pathname = NULL;
// do path resolution if file doesn't contain /
if (strchr(file, '/') == nullptr)
@ -218,6 +218,16 @@ int execvp(const char* file, char* const argv[])
cur++;
}
}
else
{
pathname = file;
}
if (!pathname)
{
errno = ENOENT;
return -1;
}
return execve(pathname, argv, environ);
}

View File

@ -17,6 +17,8 @@ set(AOC2023_PROJECTS
day12
day13
day14
day15
day16
)
set(BANAN_AOC2023_BIN ${BANAN_BIN}/aoc2023)

View File

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.26)
project(aoc2023_day15 CXX)
set(SOURCES
main.cpp
)
add_executable(aoc2023_day15 ${SOURCES})
target_compile_options(aoc2023_day15 PUBLIC -O2 -g)
target_link_libraries(aoc2023_day15 PUBLIC libc ban)
add_dependencies(aoc2023_day15 libc-install ban-install)
add_custom_target(aoc2023_day15-install
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/aoc2023_day15 ${BANAN_AOC2023_BIN}/day15
DEPENDS aoc2023_day15
DEPENDS aoc2023_always
)
add_dependencies(aoc2023 aoc2023_day15)
add_dependencies(aoc2023-install aoc2023_day15-install)

View File

@ -0,0 +1,150 @@
#include <BAN/Array.h>
#include <BAN/LinkedList.h>
#include <BAN/String.h>
#include <BAN/StringView.h>
#include <BAN/Vector.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
i64 parse_i64(BAN::StringView string)
{
i64 result = 0;
for (char c : string)
result = (result * 10) + (c - '0');
return result;
}
BAN::Vector<BAN::StringView> parse_instructions(FILE* fp)
{
static BAN::String line;
char buffer[128];
while (fgets(buffer, sizeof(buffer), fp))
{
BAN::StringView buffer_sv(buffer);
MUST(line.append(buffer_sv));
if (buffer_sv.back() == '\n')
{
line.pop_back();
break;
}
}
return MUST(line.sv().split(','));
}
i64 calculate_hash(BAN::StringView string)
{
i64 result = 0;
for (char c : string)
result = ((result + c) * 17) % 256;
return result;
}
i64 puzzle1(FILE* fp)
{
auto instructions = parse_instructions(fp);
i64 result = 0;
for (auto insn : instructions)
result += calculate_hash(insn);
return result;
}
i64 puzzle2(FILE* fp)
{
struct Entry
{
BAN::String label;
i64 focal_length;
};
using Box = BAN::LinkedList<Entry>;
auto instructions = parse_instructions(fp);
BAN::Array<Box, 256> boxes;
for (auto insn : instructions)
{
if (insn.back() == '-')
{
auto label = insn.substring(0, insn.size() - 1);
i64 hash = calculate_hash(label);
for (auto it = boxes[hash].begin(); it != boxes[hash].end(); it++)
{
if (it->label == label)
{
boxes[hash].remove(it);
break;
}
}
}
else
{
auto temp = MUST(insn.split('='));
auto label = temp[0];
auto focal_length = parse_i64(temp[1]);
i64 hash = calculate_hash(label);
bool found = false;
for (auto it = boxes[hash].begin(); it != boxes[hash].end(); it++)
{
if (it->label == label)
{
it->focal_length = focal_length;
found = true;
break;
}
}
if (!found)
MUST(boxes[hash].emplace_back(label, focal_length));
}
}
i64 result = 0;
for (size_t i = 0; i < boxes.size(); i++)
{
size_t slot = 0;
for (auto it = boxes[i].begin(); it != boxes[i].end(); it++, slot++)
result += (i + 1) * (slot + 1) * it->focal_length;
}
return result;
}
int main(int argc, char** argv)
{
const char* file_path = "/usr/share/aoc2023/day15_input.txt";
if (argc >= 2)
file_path = argv[1];
FILE* fp = fopen(file_path, "r");
if (fp == nullptr)
{
perror("fopen");
return 1;
}
printf("puzzle1: %" PRId64 "\n", puzzle1(fp));
fseek(fp, 0, SEEK_SET);
printf("puzzle2: %" PRId64 "\n", puzzle2(fp));
fclose(fp);
}

View File

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.26)
project(aoc2023_day16 CXX)
set(SOURCES
main.cpp
)
add_executable(aoc2023_day16 ${SOURCES})
target_compile_options(aoc2023_day16 PUBLIC -O2 -g)
target_link_libraries(aoc2023_day16 PUBLIC libc ban)
add_dependencies(aoc2023_day16 libc-install ban-install)
add_custom_target(aoc2023_day16-install
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/aoc2023_day16 ${BANAN_AOC2023_BIN}/day16
DEPENDS aoc2023_day16
DEPENDS aoc2023_always
)
add_dependencies(aoc2023 aoc2023_day16)
add_dependencies(aoc2023-install aoc2023_day16-install)

View File

@ -0,0 +1,232 @@
#include <BAN/HashSet.h>
#include <BAN/StringView.h>
#include <BAN/Vector.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
enum class Tile { Empty, PositiveMirror, NegativeMirror, HorizontalSplitter, VerticalSplitter };
enum class Direction { North, South, East, West };
using Grid = BAN::Vector<BAN::Vector<Tile>>;
struct Position
{
i64 y;
i64 x;
Direction dir;
bool operator==(const Position& other) const
{
return x == other.x && y == other.y && dir == other.dir;
}
};
struct PositionHash
{
BAN::hash_t operator()(Position position) const
{
return BAN::hash<i64>()((position.y << 32) | position.x) ^ BAN::hash<i8>()(static_cast<i8>(position.dir));
}
};
Grid parse_grid(FILE* fp)
{
Grid grid;
auto char_to_tile =
[](char c)
{
if (c == '.')
return Tile::Empty;
if (c == '/')
return Tile::PositiveMirror;
if (c == '\\')
return Tile::NegativeMirror;
if (c == '-')
return Tile::HorizontalSplitter;
if (c == '|')
return Tile::VerticalSplitter;
ASSERT_NOT_REACHED();
};
char buffer[128];
while (fgets(buffer, sizeof(buffer), fp))
{
BAN::StringView line(buffer);
ASSERT(line.back() == '\n');
line = line.substring(0, line.size() - 1);
if (line.empty())
break;
MUST(grid.emplace_back(line.size()));
for (size_t i = 0; i < line.size(); i++)
grid.back()[i] = char_to_tile(line[i]);
}
return grid;
}
BAN::Vector<Position> get_next_positions(Position position, const Grid& grid)
{
auto tile = grid[position.y][position.x];
BAN::Vector<Position> next_positions;
switch (tile)
{
case Tile::Empty:
switch (position.dir)
{
case Direction::North: MUST(next_positions.emplace_back(position.y - 1, position.x, Direction::North)); break;
case Direction::South: MUST(next_positions.emplace_back(position.y + 1, position.x, Direction::South)); break;
case Direction::West: MUST(next_positions.emplace_back(position.y, position.x - 1, Direction::West)); break;
case Direction::East: MUST(next_positions.emplace_back(position.y, position.x + 1, Direction::East)); break;
}
break;
case Tile::PositiveMirror:
switch (position.dir)
{
case Direction::North: MUST(next_positions.emplace_back(position.y, position.x + 1, Direction::East)); break;
case Direction::South: MUST(next_positions.emplace_back(position.y, position.x - 1, Direction::West)); break;
case Direction::West: MUST(next_positions.emplace_back(position.y + 1, position.x, Direction::South)); break;
case Direction::East: MUST(next_positions.emplace_back(position.y - 1, position.x, Direction::North)); break;
}
break;
case Tile::NegativeMirror:
switch (position.dir)
{
case Direction::North: MUST(next_positions.emplace_back(position.y, position.x - 1, Direction::West)); break;
case Direction::South: MUST(next_positions.emplace_back(position.y, position.x + 1, Direction::East)); break;
case Direction::West: MUST(next_positions.emplace_back(position.y - 1, position.x, Direction::North)); break;
case Direction::East: MUST(next_positions.emplace_back(position.y + 1, position.x, Direction::South)); break;
}
break;
case Tile::HorizontalSplitter:
switch (position.dir)
{
case Direction::North:
case Direction::South:
MUST(next_positions.emplace_back(position.y, position.x - 1, Direction::West));
MUST(next_positions.emplace_back(position.y, position.x + 1, Direction::East));
break;
case Direction::West: MUST(next_positions.emplace_back(position.y, position.x - 1, Direction::West)); break;
case Direction::East: MUST(next_positions.emplace_back(position.y, position.x + 1, Direction::East)); break;
}
break;
case Tile::VerticalSplitter:
switch (position.dir)
{
case Direction::North: MUST(next_positions.emplace_back(position.y - 1, position.x, Direction::North)); break;
case Direction::South: MUST(next_positions.emplace_back(position.y + 1, position.x, Direction::South)); break;
case Direction::West:
case Direction::East:
MUST(next_positions.emplace_back(position.y - 1, position.x, Direction::North));
MUST(next_positions.emplace_back(position.y + 1, position.x, Direction::South));
break;
}
break;
}
for (size_t i = 0; i < next_positions.size();)
{
if (next_positions[i].y < 0 || next_positions[i].y >= (i64)grid.size())
next_positions.remove(i);
else if (next_positions[i].x < 0 || next_positions[i].x >= (i64)grid.front().size())
next_positions.remove(i);
else
i++;
}
return next_positions;
}
i64 count_energized_tiles(const Grid& grid, Position start)
{
BAN::HashSet<Position, PositionHash> visited;
BAN::HashSet<Position, PositionHash> energized;
BAN::Vector<Position> current_positions;
MUST(current_positions.push_back(start));
MUST(visited.insert(current_positions.front()));
while (!current_positions.empty())
{
auto position = current_positions.back();
current_positions.pop_back();
MUST(energized.insert({ position.y, position.x, Direction::North }));
auto next_positions = get_next_positions(position, grid);
for (auto next : next_positions)
{
if (visited.contains(next))
continue;
MUST(visited.insert(next));
MUST(current_positions.push_back(next));
}
}
return energized.size();
}
i64 puzzle1(FILE* fp)
{
auto grid = parse_grid(fp);
return count_energized_tiles(grid, { 0, 0, Direction::East });
}
i64 puzzle2(FILE* fp)
{
auto grid = parse_grid(fp);
i64 max_energized = 0;
for (i64 y = 0; y < (i64)grid.size(); y++)
{
max_energized = BAN::Math::max(max_energized, count_energized_tiles(grid, { y, 0, Direction::East }));
max_energized = BAN::Math::max(max_energized, count_energized_tiles(grid, { y, (i64)grid.front().size() - 1, Direction::West }));
}
for (i64 x = 0; x < (i64)grid.front().size(); x++)
{
max_energized = BAN::Math::max(max_energized, count_energized_tiles(grid, { 0, x, Direction::South }));
max_energized = BAN::Math::max(max_energized, count_energized_tiles(grid, { (i64)grid.size() - 1, x, Direction::North }));
}
return max_energized;
}
int main(int argc, char** argv)
{
const char* file_path = "/usr/share/aoc2023/day16_input.txt";
if (argc >= 2)
file_path = argv[1];
FILE* fp = fopen(file_path, "r");
if (fp == nullptr)
{
perror("fopen");
return 1;
}
printf("puzzle1: %" PRId64 "\n", puzzle1(fp));
fseek(fp, 0, SEEK_SET);
printf("puzzle2: %" PRId64 "\n", puzzle2(fp));
fclose(fp);
}

1
userspace/aoc2023/input/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
day*_input.txt

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
#!/bin/bash
wget --no-cookies --header "Cookie: session=$AOC_SESSION" https://adventofcode.com/2023/day/$1/input -O day$1_input.txt