Last active
December 30, 2017 18:13
-
-
Save mmstick/08c97c0ffc7a4131df3849a67f795dec to your computer and use it in GitHub Desktop.
Advent of Code 2017 (C Edition)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <errno.h> | |
#include <string.h> | |
/// A fat pointer which points to byte array of text. | |
typedef struct str { | |
uint8_t *data; | |
size_t len; | |
} str; | |
/// An owned type for managing allocations and rellocations of an inner `str`. | |
typedef struct String { | |
str view; | |
size_t capacity; | |
} String; | |
/// Allocate a new `String` with a set capacity | |
String string_with_capacity(const size_t capacity) { | |
return (String) { (str) { malloc(capacity), 0 }, capacity }; | |
} | |
/// Obtain a `str*` from the given `String`. | |
str *get_str(String *self) { | |
return &self->view; | |
} | |
/// Converts a byte array into a `str` without checking if it is UTF-8. | |
str from_utf8_unchecked(uint8_t *bytes, const size_t size) { | |
return (str) { bytes, size }; | |
} | |
/// Mutably borrows the `String`, and appends the supplied string onto it. | |
/// If a reallocation is needed, a reallocation will occur. | |
void push_str(String *self, const str *append) { | |
if (self->capacity - self->view.len < append->len + 1) { | |
self->capacity *= 2; | |
while (self->capacity - self->view.len < append->len + 1) { | |
self->capacity *= 2; | |
} | |
self->view.data = realloc(self->view.data, self->capacity); | |
if (NULL == self->view.data) { | |
fprintf(stderr, "string reallocation failed\n"); | |
exit(1); | |
} | |
} | |
memcpy(self->view.data + self->view.len, append->data, append->len); | |
self->view.len += append->len; | |
*(self->view.data + self->view.len) = '\0'; | |
} | |
// Trims the input string | |
str trim(const str *input) { | |
size_t id = 0; | |
for (; id < input->len; id++) { | |
if (input->data[id] > 32) break; | |
} | |
str output = (str) { input->data + id, input->len - id }; | |
for (id = 0; id < input->len; id++) { | |
if (input->data[input->len - 1 - id] > 32) break; | |
} | |
output.len -= id; | |
return output; | |
} | |
// NOTE: Result sum type implementations via tagged unions. | |
/// Effectively a `Result<String, ()>` | |
typedef struct TaggedString { | |
uint8_t _tag; | |
String string; | |
} TaggedString; | |
typedef struct IoError { | |
int errorno; | |
char *description; | |
} IoError; | |
/// Effectively a `Result<(), io::Error>` | |
typedef struct TaggedIoError { | |
uint8_t _tag; | |
IoError error; | |
} TaggedIoError; | |
/// Effectively a `Result<usize, ()>` | |
typedef struct TaggedUsize { | |
uint8_t _tag; | |
size_t value; | |
} TaggedUsize; | |
/// Emulates a `Result<String, io::Error>` | |
typedef union { | |
TaggedString ok; | |
TaggedIoError err; | |
} StringResult; | |
typedef union { | |
TaggedUsize ok; | |
TaggedIoError err; | |
} UsizeIoErrorResult; | |
/// A convenience method for `FILE` which simply reads the entire contents of | |
/// the file into a `String` supplied a buffer. | |
UsizeIoErrorResult read_to_end(FILE *file, String *buffer) { | |
uint8_t *temp_buffer[8192]; | |
size_t total_read = 0; | |
while (1) { | |
const size_t read = fread(temp_buffer, 1, 8192, file); | |
if (read == 0) { break; } | |
total_read += read; | |
const str append = from_utf8_unchecked((uint8_t*) temp_buffer, read); | |
push_str(buffer, &append); | |
} | |
return (errno != 0) | |
? (UsizeIoErrorResult) (TaggedIoError) { 1, errno, "error reading input file" } | |
: (UsizeIoErrorResult) (TaggedUsize) { 0, total_read }; | |
} | |
uint8_t is_numeric(uint8_t input) { | |
return input > 47 && input < 58; | |
} | |
/// Opens the supplied input file, reading it into a string. | |
StringResult get_input(char *path) { | |
FILE *file = fopen(path, "r"); | |
String buffer = string_with_capacity(2048); | |
UsizeIoErrorResult result = read_to_end(file, &buffer); | |
return (result.ok._tag == 0) | |
? (StringResult) (TaggedString) { 0, buffer } | |
: (StringResult) result.err; | |
} | |
size_t get_sum_from(const str *input) { | |
uint8_t last_char = input->data[0] - 48; | |
size_t sum = 0; | |
for (size_t pos = 1; pos < input->len; pos++) { | |
uint8_t current = input->data[pos]; | |
if (!is_numeric(current)) continue; | |
current -= 48; | |
if (last_char == current) sum += current; | |
last_char = current; | |
} | |
sum += (last_char == input->data[0] - 48) ? last_char : 0; | |
return sum; | |
} | |
size_t get_pos(const size_t length, const size_t current, size_t forward) { | |
forward = current + forward; | |
return (length <= forward) | |
? forward - length | |
: forward; | |
} | |
size_t get_sum_from2(const str *input) { | |
const size_t forward = input->len / 2; | |
size_t sum = 0; | |
for (size_t pos = 0; pos < input->len; pos++) { | |
uint8_t current = input->data[pos]; | |
uint8_t future = input->data[get_pos(input->len, pos, forward)]; | |
if (current == future && is_numeric(current) && is_numeric(future)) { | |
sum += current - 48; | |
} | |
} | |
uint8_t last_char = input->data[input->len-1] - 48; | |
sum += (last_char == input->data[forward] - 48) ? last_char : 0; | |
return sum; | |
} | |
typedef struct UnitTest { | |
uint8_t value; | |
uint8_t expected; | |
} UnitTest; | |
int tests() { | |
const size_t NTESTS = 6; | |
UnitTest tests[NTESTS]; | |
tests[0] = (UnitTest) { get_sum_from(&(str) { (uint8_t*) "91212129", 8 }), 9 }; | |
tests[1] = (UnitTest) { get_sum_from(&(str) { (uint8_t*) "1111", 4}), 4 }; | |
tests[2] = (UnitTest) { get_sum_from2(&(str) { (uint8_t*) "12131415", 8 }), 4 }; | |
tests[3] = (UnitTest) { get_sum_from2(&(str) { (uint8_t*) "123123", 6 }), 12 }; | |
tests[4] = (UnitTest) { get_sum_from2(&(str) { (uint8_t*) "123425", 6 }), 4 }; | |
tests[5] = (UnitTest) { get_sum_from2(&(str) { (uint8_t*) "1221", 4 }), 0 }; | |
uint8_t failed = 0; | |
for (size_t test = 0; test < NTESTS; test++) { | |
UnitTest current_test = tests[test]; | |
if (current_test.value != current_test.expected) { | |
fprintf( | |
stderr, | |
"test %lu failed: expected %u, found %u\n", | |
test + 1, | |
current_test.expected, | |
current_test.value | |
); | |
failed = 1; | |
} else { | |
fprintf(stderr, "test %lu passed\n", test + 1); | |
} | |
} | |
failed | |
? fprintf(stderr, "Some tests failed\n") | |
: fprintf(stderr, "All tests succeeded\n"); | |
return failed; | |
} | |
int _main() { | |
StringResult result = get_input("../input"); | |
if (1 == result.ok._tag) { | |
IoError err = result.err.error; | |
fprintf(stderr, "program error: %s: %s", strerror(err.errorno), err.description); | |
return 1; | |
} | |
const str view = trim(get_str(&result.ok.string)); | |
const size_t sum1 = get_sum_from(&view); | |
printf("The result for the first part is %lu\n", sum1); | |
const size_t sum2 = get_sum_from2(&view); | |
printf("The result for the second part is %lu\n", sum2); | |
return 0; | |
} | |
int main() { | |
#ifdef DEBUG | |
return tests(); | |
#else | |
return _main(); | |
#endif | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::fs::File; | |
use std::process::exit; | |
use std::io::{self, Read}; | |
use std::path::Path; | |
fn get_input<P: AsRef<Path>>(path: P) -> io::Result<String> { | |
let mut file = File::open(path)?; | |
let mut buffer = String::with_capacity(2048); | |
file.read_to_string(&mut buffer)?; | |
Ok(buffer) | |
} | |
fn is_numeric(byte: u8) -> bool { | |
byte > 47 && byte < 58 | |
} | |
fn get_sum_from(input: &str) -> usize { | |
let input = input.as_bytes(); | |
let mut last_char = input[0] - 48; | |
let mut sum: usize = 0; | |
for pos in 1..input.len() { | |
let mut current = input[pos]; | |
if !is_numeric(current) { continue } | |
current -= 48; | |
sum += if last_char == current { current as usize } else { 0 }; | |
last_char = current; | |
} | |
if last_char == input[0] - 48 { sum + last_char as usize } else { sum } | |
} | |
fn get_pos(length: usize, current: usize, forward: usize) -> usize { | |
let forward = current + forward; | |
if length <= forward { forward - length } else { forward } | |
} | |
fn get_sum_from2(input: &str) -> usize { | |
let input = input.as_bytes(); | |
let forward = input.len() / 2; | |
let mut sum = 0; | |
for pos in 0..input.len() { | |
let mut current = input[pos]; | |
let mut future = input[get_pos(input.len(), pos, forward)]; | |
if current == future && is_numeric(current) && is_numeric(future) { | |
sum += (current - 48) as usize; | |
} | |
} | |
sum | |
} | |
fn main() { | |
let input: String = match get_input("../input") { | |
Ok(input) => input, | |
Err(why) => { | |
eprintln!("program error: {}", why); | |
exit(1); | |
} | |
}; | |
let input = input.trim(); | |
let sum1 = get_sum_from(input); | |
let sum2 = get_sum_from2(input); | |
println!("The result for the first part is {}", sum1); | |
println!("The result for the second part is {}", sum2); | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn first_part() { | |
assert_eq!(9, get_sum_from("91212129")); | |
assert_eq!(4, get_sum_from("1111")); | |
} | |
#[test] | |
fn second_part() { | |
assert_eq!(4, get_sum_from2("12131415")); | |
assert_eq!(12, get_sum_from2("123123")); | |
assert_eq!(4, get_sum_from2("123425")); | |
assert_eq!(0, get_sum_from2("1221")); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment