protohackers

My solutions to the protohackers.com challenges.
git clone git://henryandlizzy.uk/protohackers
Log | Files | Refs

commit 6ae40f4243775d67866a474debcd8f9726eb2a64
Author: Henry Wilson <henry@henryandlizzy.uk>
Date:   Mon, 28 Aug 2023 13:51:36 +0000

Complete tasks 0-4

Diffstat:
A.gitignore | 2++
Afd.hpp | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainet.hpp | 47+++++++++++++++++++++++++++++++++++++++++++++++
Amakefile | 17+++++++++++++++++
Aprotohack-0.cpp | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprotohack-1.cpp | 399+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprotohack-2.cpp | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprotohack-3.cpp | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprotohack-4.cpp | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 948 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +protohack-[0-9] +protohack-[0-9][0-9] diff --git a/fd.hpp b/fd.hpp @@ -0,0 +1,75 @@ +#pragma once +#include <unistd.h> + +#include <string> + +struct descriptor +{ + descriptor() + : fd(-1) + {} + + explicit descriptor(int d) + : fd(d) + {} + + ~descriptor() + { + close(); + } + + descriptor(descriptor&& old) + : fd(old.fd) + { + old.fd = -1; + } + + descriptor& operator =(descriptor&& old) + { + if (this != &old) + { + close(); + fd = old.fd; + old.fd = -1; + } + return *this; + } + + descriptor(descriptor const&) = delete; + descriptor& operator =(descriptor const&) = delete; + + void close() + { + if (*this) + ::close(fd); + fd = -1; + } + + explicit operator bool() const + { + return fd != -1; + } + + operator int() const + { + return fd; + } + +private: + int fd; +}; + +int write(descriptor& d, std::string_view s) +{ + return ::write(d, s.data(), s.size()); +} + +std::string read(descriptor& d) +{ + char buf[256]; + ssize_t n = ::read(d, buf, sizeof buf); + if (n > 0) + return {buf, (size_t)n}; + else + return {}; +} diff --git a/inet.hpp b/inet.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "fd.hpp" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <err.h> + +#include <iostream> + +namespace inet { + +descriptor listen(int type) +{ + struct sockaddr_in addr {sizeof addr, AF_INET, htons(1337), INADDR_ANY}; + int reuseaddr = 1; + + descriptor incoming{::socket(AF_INET, type, 0)}; + if (!incoming) + err(1, "socket"); + + if (::setsockopt(incoming, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof reuseaddr)) + err(1, "setsockopt"); + if (::bind(incoming, (sockaddr const*)&addr, sizeof addr)) + err(1, "bind"); + if (::listen(incoming, 10)) + err(1, "listen"); + + return incoming; +} + +descriptor accept(descriptor& incoming) +{ + struct sockaddr_in addr; + socklen_t addr_size = sizeof addr; + descriptor conn{::accept(incoming, (sockaddr*)&addr, &addr_size)}; + + if (!conn) + err(1, "accept"); + + std::cout << "Connection " << conn << " from " << inet_ntoa(addr.sin_addr) << std::endl; + return conn; +} + +} diff --git a/makefile b/makefile @@ -0,0 +1,17 @@ +CXXFLAGS := -std=c++20 -fcolor-diagnostics + +ALL := protohack-0 \ + protohack-1 \ + protohack-2 \ + protohack-3 \ + protohack-4 \ + +all: $(ALL) + +protohack-1: protohack-1.cpp + ${LINK.cc} -o protohack-1 protohack-1.cpp -lcrypto + +clean: + rm -f $(ALL) + +.PHONY: all clean diff --git a/protohack-0.cpp b/protohack-0.cpp @@ -0,0 +1,62 @@ +#include "inet.hpp" + +#include <poll.h> + +#include <iostream> +#include <sstream> +#include <span> +#include <map> +#include <vector> + +struct client +{ + descriptor socket; + std::string buffer; +}; + +int main() +{ + std::map<int, client> clients; + std::vector<pollfd> awaitables; + auto incoming = inet::listen(SOCK_STREAM); + + for (;;) + { + awaitables.clear(); + awaitables.push_back(pollfd{incoming, POLLIN, 0}); + + for (auto const& c : clients) + awaitables.push_back(pollfd{c.second.socket, POLLIN, 0}); + + if (::poll(awaitables.data(), awaitables.size(), INFTIM) == -1) + err(1, "poll"); + + if (awaitables.front().revents & POLLIN) // incoming + { + auto conn = inet::accept(incoming); + clients[conn] = {std::move(conn), {}}; + } + + for (auto& ev : std::span{awaitables}.subspan(1)) + { + if (~ev.revents & POLLIN) + continue; + + char buf[0x1000]; + int n = ::read(ev.fd, buf, sizeof buf); + if (not n) + { + std::cout << "* " << ev.fd << " disconnected\n"; + clients.erase(ev.fd); + continue; + } + if (n < 0 or n > sizeof buf) + err(1, "read"); + + std::cout << "* " << ev.fd << ": " << n << " bytes\n"; + ::write(1, buf, n); + //if (n != ::write(ev.fd, buf, n)) + // err(1, "write"); + } + } +} diff --git a/protohack-1.cpp b/protohack-1.cpp @@ -0,0 +1,399 @@ +#include "inet.hpp" +#include "fd.hpp" + +#include <poll.h> + +#include <iostream> +#include <cmath> +#include <map> +#include <vector> +#include <optional> +#include <openssl/bn.h> + +using namespace std; + +struct bignum +{ + bignum() + : p(BN_new(), BN_free) + { + BN_init(*this); + } + + bignum(BIGNUM* bn) + : p(bn, BN_free) + {} + + bignum(unsigned long w) + : bignum() + { + BN_set_word(*this, w); + } + + bignum(bignum&&) = default; + bignum& operator=(bignum&&) = default; + + bignum(bignum const& old) + : bignum{BN_dup(old)} + {} + bignum& operator=(bignum const& old) + { + if (p) + BN_copy(*this, old); + else + p.reset(BN_dup(old)); + + return *this; + } + + char const* to_str() const + { + return BN_bn2dec(*this); + } + + bool is_prime() const + { + return BN_is_prime_fasttest_ex(*this, INT_MAX, nullptr, 1, nullptr); + } + + bignum operator +(bignum const& rhs) + { + bignum r; + BN_add(r, *this, rhs); + return r; + } + bignum operator -(bignum const& rhs) + { + bignum r; + BN_sub(r, *this, rhs); + return r; + } + +private: + unique_ptr<BIGNUM, void(*)(BIGNUM*)> p; + + operator BIGNUM const*() const + { + return p.get(); + } + operator BIGNUM*() + { + return p.get(); + } +}; + +ostream& operator <<(ostream& out, bignum const& rhs) +{ + return out << rhs.to_str(); +} + +struct json_object +{ + std::map<string_view, string_view> nv_pairs; + std::map<string_view, bignum> num_pairs; +}; + +template <typename T> +struct parse_t +{ + T val; + string_view remaining; +}; + +std::optional<string_view> parse_char(string_view in, char c) +{ + if (in.empty()) + return {}; + if (in.front() != c) + return {}; + return in.substr(1); +} + +std::optional<string_view> parse_str(string_view in, string_view str) +{ + if (not in.starts_with(str)) + return {}; + return in.substr(str.size()); +} + +string_view parse_whitespace(string_view in) +{ + auto end = in.find_first_not_of(" \t\r\n"); + return end != string::npos ? in.substr(end) : ""sv; +} + +std::optional<parse_t<bool>> parse_bool(string_view in) +{ + if (auto next = parse_str(in, "true")) + return parse_t<bool>{true, *next}; + if (auto next = parse_str(in, "false")) + return parse_t<bool>{false, *next}; + return {}; +} + +size_t find_end_quote(string_view str) +{ + size_t start = 0; + for (;;) + { + auto pos = str.find_first_of("\"\\", start); + if ((pos == string::npos) or (str[pos] == '"')) + return pos; + start = pos + 2; + if (start > str.size()) + return string::npos; + } +} + +std::optional<parse_t<string_view>> parse_string(string_view in) +{ + if (auto next = parse_char(in, '"')) + in = *next; + else + return {}; + + if (in.empty()) + return {}; + + auto end = find_end_quote(in); + auto str = in.substr(0, end); + in = end != string::npos ? in.substr(end) : ""sv; + + if (auto next = parse_char(in, '"')) + in = *next; + else + return {}; + + return parse_t<string_view>{str, in}; +} + +std::optional<parse_t<bignum>> parse_bignum(string_view in) +{ + BIGNUM* p = nullptr; + auto end = in.find_first_not_of("-0123456789"); + string str{in.substr(0, end)}; + int r = BN_dec2bn(&p, str.c_str()); + if (r == 0) + return {}; + if (r < 0) + __builtin_trap(); + + in = end != string::npos ? in.substr(end) : ""sv; + + if (auto next = parse_char(in, '.')) + { + in = *next; + } + else + return parse_t<bignum>{bignum{p}, in}; + + end = in.find_first_not_of("0123456789"); + in = end != string::npos ? in.substr(end) : ""sv; + return parse_t<bignum>{bignum{0UL}, in}; +} + +std::optional<parse_t<json_object>> parse_object(string_view in) +{ + json_object o; + + if (auto next = parse_char(in, '{')) + in = *next; + else + return {}; + + in = parse_whitespace(in); + + if (auto next = parse_char(in, '}')) + return parse_t<json_object>{std::move(o), *next}; + + for (;;) + { + auto name = parse_string(in); + if (not name) + return {}; + + in = parse_whitespace(name->remaining); + + if (auto next = parse_char(in, ':')) + in = parse_whitespace(*next); + else + return {}; + + if (auto value = parse_string(in)) + { + auto& val = o.nv_pairs[name->val]; + val = value->val; + in = value->remaining; + } + else if (auto value = parse_bignum(in)) + { + auto& val = o.num_pairs[name->val]; + val = value->val; + in = value->remaining; + } + else if (auto value = parse_bool(in)) + { + //auto& val = o.bool_pairs[name->val]; + //val = value->val; + in = value->remaining; + } + else if (auto value = parse_object(in)) + { + //auto& val = o.obj_pairs[name->val]; + //val = value->val; + in = value->remaining; + } + else + return {}; + + if (auto next = parse_char(in, ',')) + in = parse_whitespace(*next); + else + break; + } + if (auto next = parse_char(in, '}')) + return parse_t<json_object>{std::move(o), *next}; + else + return {}; +} + +ostream& operator <<(ostream& out, json_object const& rhs) +{ + out << '{'; + for (auto const& nv : rhs.nv_pairs) + out << '"' << nv.first << "\": \"" << nv.second << "\","; + for (auto const& nv : rhs.num_pairs) + out << '"' << nv.first << "\": " << nv.second << ','; + return out << '}'; +} + +ostream& operator <<(ostream& out, std::optional<parse_t<json_object>> const& rhs) +{ + if (not rhs) + return out << "<failed parse>"; + return out << rhs->val << " <" << rhs->remaining << ">"; +} + +struct client +{ + descriptor d; + std::string buf; + + bool do_read() + { + string data = read(d); + if (data.empty()) + return false; + + buf.append(std::move(data)); + return true; + } + optional<string> getline() + { + if (buf.empty()) + return {}; + auto n = buf.find('\n'); + if (n == string::npos) + return {}; + string line = buf.substr(0, n); + buf.erase(0, n+1); + return line; + } +}; + +std::optional<bignum> parse_json_request(string_view line) +{ + auto obj = parse_object(line); + if (not obj) + { + cout << "request is invalid JSON\n"; + return {}; + } + //std::cout << obj << std::flush; + + auto srch = obj->val.nv_pairs.find("method"); + if ((srch == obj->val.nv_pairs.end()) or (srch->second != "isPrime")) + { + cout << "'method' != isPrime or is missing\n"; + return {}; + } + auto srch2 = obj->val.num_pairs.find("number"); + if (srch2 == obj->val.num_pairs.end()) + { + cout << "'number' is not number type or is missing\n"; + return {}; + } + return srch2->second; +} + +bool client_ready(client& c) +{ + if (not c.do_read()) + { + cout << "* " << c.d << " disconnected\n"; + return false; + } + + while (auto line = c.getline()) + { + if (auto number = parse_json_request(*line)) + { + bool prime = number->is_prime(); + std::cout << *number << ": " << prime << std::endl; + + if (prime) + write(c.d, "{\"method\":\"isPrime\",\"prime\":true}\n"sv); + else + write(c.d, "{\"method\":\"isPrime\",\"prime\":false}\n"sv); + } + else + { + write(c.d, "xxx\n"sv); + return false; + } + } + return true; +} + +int main() +{ + std::map<int, client> clients; + auto incoming = inet::listen(SOCK_STREAM); + + std::cout << std::boolalpha << "Awaiting clients\n"; + + for (;;) + { + std::vector<pollfd> awaitables; + awaitables.push_back({incoming, POLLIN, 0}); + for (auto const& c : clients) + awaitables.push_back({c.second.d, POLLIN, 0}); + + ::poll(awaitables.data(), awaitables.size(), INFTIM); + + for (auto& ev : awaitables) + { + if (~ev.revents & POLLIN) + continue; + + if (ev.fd == incoming) + { + auto conn = inet::accept(incoming); + + auto& x = clients[conn]; + x.d = move(conn); + continue; + } + + auto it = clients.find(ev.fd); + if (it == clients.end()) + { + cerr << "Unknown fd " << ev.fd << endl; + return 1; + } + + if (not client_ready(it->second)) + clients.erase(it); + } + } +} diff --git a/protohack-2.cpp b/protohack-2.cpp @@ -0,0 +1,139 @@ +#include "inet.hpp" + +#include <poll.h> + +#include <algorithm> +#include <iostream> +#include <map> +#include <vector> + +using namespace std; + +struct listings : map<int32_t, int32_t> +{ + int32_t average(int32_t min, int32_t max) + { + if (empty()) + return 0; + if (min > max) + return 0; + + auto it = lower_bound(min); + + if (it == end()) + return 0; + + int64_t accum = 0; + int32_t n = 0; + + while (it != end() and it->first <= max) + { + ++n; + accum += it++->second; + } + if (n == 0) + return 0; + + return accum / n; + } +}; + +struct client +{ + descriptor d; + listings l; + std::vector<char> buf; +}; + +std::map<int, client> clients; + +uint32_t decode(void const* in) +{ + auto p = (unsigned char const*)in; + return p[3] | p[2] << 8U | p[1] << 16U | p[0] << 24U; +} + +void encode(uint32_t n, void* out) +{ + unsigned char* p = (unsigned char*)out; + p[0] = (n & 0xFF000000U) >> 24U; + p[1] = (n & 0x00FF0000U) >> 16U; + p[2] = (n & 0x0000FF00U) >> 8U; + p[3] = (n & 0x000000FFU); +} + +int main() +{ + auto incoming = inet::listen(SOCK_STREAM); + + for (;;) + { + std::vector<pollfd> awaitables; + awaitables.push_back(pollfd{incoming, POLLIN, 0}); + for (auto const& c : clients) + awaitables.push_back(pollfd{c.second.d, POLLIN, 0}); + + ::poll(awaitables.data(), awaitables.size(), INFTIM); + + for (auto& ev : awaitables) + { + if (~ev.revents & POLLIN) + continue; + + if (ev.fd == incoming) + { + auto conn = inet::accept(incoming); + clients[conn].d = std::move(conn); + continue; + } + + if (auto it = clients.find(ev.fd); it != clients.end()) + { + char buf; + int rc = ::read(ev.fd, &buf, sizeof buf); + if (not rc) + { + std::cout << "* " << ev.fd << " disconnected\n"; + clients.erase(it); + continue; + } + if (rc < 0) + err(1, "read"); + + auto& buffer = it->second.buf; + buffer.push_back(buf); + if (buffer.size() < 9) + continue; + + auto& l = it->second.l; + + int32_t a = decode(buffer.data() + 1); + int32_t b = decode(buffer.data() + 5); + char cmd = buffer.front(); + buffer.erase(buffer.begin(), buffer.begin() + 9); + switch (cmd) + { + case 'I': + if (l.count(a)) + return 1; + l[a] = b; + continue; + + case 'Q': + a = l.average(a, b); + char txbuf[4]; + encode(a, txbuf); + ::write(ev.fd, txbuf, 4); + continue; + } + + std::cout << "* " << ev.fd << " bad command" << cmd << "\n"; + clients.erase(it); + continue; + } + + std::cerr << "Unknown fd " << ev.fd << std::endl; + return 1; + } + } +} diff --git a/protohack-3.cpp b/protohack-3.cpp @@ -0,0 +1,149 @@ +#include "inet.hpp" + +#include <poll.h> + +#include <iostream> +#include <sstream> +#include <map> +#include <vector> + +#include "fd.hpp" + +using namespace std; + +struct client +{ + descriptor d; + std::string name, buf; + stringstream stream; + + bool do_read() + { + string data = read(d); + if (data.empty()) + return false; + + buf.append(data); + return true; + } + string getline() + { + auto s = buf.find_first_not_of('\n'); + auto n = buf.find('\n', s); + if (n == string::npos) + return {}; + + string line = buf.substr(s, n); + buf.erase(0, s+n+1); + return line; + } +}; + +std::map<int, client> unnamed, clients; + +void broadcast(string msg, int exclude) +{ + msg.push_back('\n'); + std::cout << msg; + for (auto& c : clients) + if (c.second.d != exclude) + write(c.second.d, msg); +} + +int main() +{ + auto incoming = inet::listen(SOCK_STREAM); + + for (;;) + { + std::vector<pollfd> awaitables; + awaitables.push_back({incoming, POLLIN, 0}); + for (auto const& c : unnamed) + awaitables.push_back({c.second.d, POLLIN, 0}); + for (auto const& c : clients) + awaitables.push_back({c.second.d, POLLIN, 0}); + + ::poll(awaitables.data(), awaitables.size(), INFTIM); + + for (auto& ev : awaitables) + { + if (~ev.revents & POLLIN) + continue; + + if (ev.fd == incoming) + { + auto conn = inet::accept(incoming); + + write(conn, "Welcome to H&L-chat, what is your name?\n"); + auto& x = unnamed[conn]; + x.d = move(conn); + continue; + } + + auto it = unnamed.find(ev.fd); + if (it != unnamed.end()) + { + auto& c = it->second; + // c.stream << read(c.d); + if (not c.do_read()) + { + unnamed.erase(it); + continue; + } + string line = c.getline(); + if (line.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz") != string::npos) + { + unnamed.erase(it); + continue; + } + // getline(c.stream, line); + { + ostringstream msg; + msg << "* " << line << " joined."; + broadcast(msg.str(), c.d); + c.name = line; + } + { + ostringstream msg; + msg << "* " << clients.size() << " users in chat:"; + for (auto const& o : clients) + msg << ' ' << o.second.name; + msg << '\n'; + write(c.d, msg.str()); + } + clients.insert(unnamed.extract(it)); + continue; + } + + it = clients.find(ev.fd); + if (it != clients.end()) + { + auto& c = it->second; + // string line = read(c.d); + // if (line.empty()) + if (not c.do_read()) + { + ostringstream msg; + msg << "* " << c.name << " has left the room"; + broadcast(msg.str(), c.d); + clients.erase(it); + continue; + } + + // c.stream << line; + + // getline(c.stream, line); + string line = c.getline(); + if (line.empty()) + continue; + ostringstream msg; + msg << "[" << c.name << "] " << line; + broadcast(msg.str(), c.d); + continue; + } + + cerr << "Unknown fd " << ev.fd << endl; + return 1; + } + } +} diff --git a/protohack-4.cpp b/protohack-4.cpp @@ -0,0 +1,58 @@ +#include <poll.h> + +#include <iostream> +#include <sstream> +#include <map> +#include <vector> + +#include "inet.hpp" + +using namespace std; + +std::map<string, string> database = {{"version", "m3henry-protohack-4"}}; + +string recv_udp(descriptor& d, sockaddr_in& a) +{ + char buf[1024]; + socklen_t addr_size = sizeof a; + + ssize_t n = ::recvfrom(d, buf, sizeof buf, 0, (sockaddr*)&a, &addr_size); + if (n > 0) + return {buf, (size_t)n}; + else + return {}; +} + +int send_udp(descriptor& d, sockaddr_in const& a, string const& s) +{ + return ::sendto(d, s.data(), s.size(), 0, (sockaddr const*)&a, sizeof a); +} + +int main() +{ + auto incoming = inet::listen(SOCK_DGRAM); + + for (;;) + { + struct sockaddr_in addr; + socklen_t addr_size = sizeof addr; + char buf[1024]; + + auto len = ::recvfrom(incoming, buf, sizeof buf, 0, (sockaddr*)&addr, &addr_size); + if (len < 0) + return 1; + string msg{buf, (size_t)len}; + string ip = inet_ntoa(addr.sin_addr); + cout << ip << ":" << addr.sin_port << " '" << msg << "'\n"; + + auto split = msg.find('='); + if (split == string::npos) + { + send_udp(incoming, addr, msg + '=' + database[msg]); + continue; + } + auto key = msg.substr(0, split); + if (key != "version") + database[key] = msg.substr(split+1); + } +}