From bd81d402beedf9af1d8a6796ebf414a60f82cb04 Mon Sep 17 00:00:00 2001 From: Knyffen Date: Thu, 21 Mar 2024 13:49:58 +0100 Subject: [PATCH] Init from KnyfSky --- CPP.cpp.snip | 24 ++ CPP/TODO/foo.cpp | 105 +++++++ CPP/graph_theory/floyd_warshall.cpp.snip | 37 +++ CPP/graph_theory/graph.cpp.snip | 267 ++++++++++++++++ CPP/graph_theory/test/test | Bin 0 -> 101816 bytes CPP/graph_theory/test/test.cpp | 372 +++++++++++++++++++++++ CPP/hpc/dmalloc_2d.c.snip | 25 ++ CPP/hpc/include_cblas.c.snip | 6 + CPP/range_queries/SegmentTree.cpp.snip | 142 +++++++++ CPP/utility/prime_sieve.cpp.snip | 12 + HTML.html.snip | 8 + Markdown.md.snip | 18 ++ install_configfiles.sh | 4 + julia_OR.jl.snip | 29 ++ latex.tex.snip | 17 ++ rust/unit_test.rs.snip | 9 + 16 files changed, 1075 insertions(+) create mode 100644 CPP.cpp.snip create mode 100644 CPP/TODO/foo.cpp create mode 100644 CPP/graph_theory/floyd_warshall.cpp.snip create mode 100644 CPP/graph_theory/graph.cpp.snip create mode 100644 CPP/graph_theory/test/test create mode 100644 CPP/graph_theory/test/test.cpp create mode 100644 CPP/hpc/dmalloc_2d.c.snip create mode 100644 CPP/hpc/include_cblas.c.snip create mode 100644 CPP/range_queries/SegmentTree.cpp.snip create mode 100644 CPP/utility/prime_sieve.cpp.snip create mode 100644 HTML.html.snip create mode 100644 Markdown.md.snip create mode 100644 install_configfiles.sh create mode 100644 julia_OR.jl.snip create mode 100644 latex.tex.snip create mode 100644 rust/unit_test.rs.snip diff --git a/CPP.cpp.snip b/CPP.cpp.snip new file mode 100644 index 0000000..295778d --- /dev/null +++ b/CPP.cpp.snip @@ -0,0 +1,24 @@ +// #define NDEBUG + +#include +// #include +#include +// #include +// #include +#include +// #include +// #include +// #include +// #include +// #include + +using namespace std; + +int main() { + ; + + return EXIT_SUCCESS; +} + +#ifndef NDEBUG +#endif diff --git a/CPP/TODO/foo.cpp b/CPP/TODO/foo.cpp new file mode 100644 index 0000000..4d2b37e --- /dev/null +++ b/CPP/TODO/foo.cpp @@ -0,0 +1,105 @@ +/* #define NDEBUG */ + +#include +/* #include */ +#include +/* #include */ +/* #include */ +#include +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +#include + +using namespace std; + + + +template +class adjacency_list { + public: + size_t N; + std::vector> adj; + + adjacency_list (size_t n) { + N = n; + adj(N, std::vector()); + } + + std::vector operator[](T i) { adj[i]; }; + void add_directed_edge(size_t i, size_t j) { + adj[i].push_back(j); + }; + void add_undirected_edge(size_t i, size_t j) { + adj[i].push_back(j); + adj[j].push_back(i); + }; +}; + +template +std::vector> floyd_warshall(std::vector> adj); + + +int main() { + vector> adj_matrix; + auto foo = floyd_warshall(adj_matrix); + /* floyd_warshall(adj_matrix); */ + ; + + return EXIT_SUCCESS; +} + +#ifndef NDEBUG +#endif + +// NOTE: !!!UNTESTED!!! +// Time complexity: O(n^3) +// Memory complexity: O(n^2) - only for n ~ 10^3 (n ~ 10^4 results in 1524 MB of memory) +// Input: Graph as an adjancency matrix (weight 0 => no edge) +// Output: A distance matrix (distance std::numeric_limits::max() => no path) +#include +#include +#include +#include +template +std::vector> floyd_warshall(std::vector> adj) { + size_t n = adj.size(); + std::vector> distance(n, std::vector(n, 0)); + + // Initialize distance + for (size_t i = 0; i < n; i++) { + for (size_t j = 0; j < n; j++) { + if (i == j) distance[i][j] = 0; + else if (adj[i][j]) distance[i][j] = adj[i][j]; + else distance[i][j] = std::numeric_limits::max(); + } + } + + // Find shortest distances + for (size_t k = 0; k < n; k++) { + for (size_t i = 0; i < n; i++) { + for (size_t j = 0; j < n; j++) { + if (distance[i][k] == std::numeric_limits::max() || distance[k][j] == std::numeric_limits::max()) + continue; + distance[i][j] = std::min(distance[i][j], + distance[i][k] + distance[k][j]); + } + } + } + + return distance; +} + + + +template +class 1D_sum_queries { + vector> array; + public: + 1D_sum_queries(size_t n) { + + } + std::vector operator[](T i) { adj[i]; }; +} diff --git a/CPP/graph_theory/floyd_warshall.cpp.snip b/CPP/graph_theory/floyd_warshall.cpp.snip new file mode 100644 index 0000000..974a3f8 --- /dev/null +++ b/CPP/graph_theory/floyd_warshall.cpp.snip @@ -0,0 +1,37 @@ +// NOTE: Check the input graph for multiple edges between the same vertices! You will want to keep the shortest one +// Time complexity: O(n^3) +// Memory complexity: O(n^2) - only for n ~ 10^3 (n ~ 10^4 results in 1524 MB of memory) +// Input: Graph as an adjancency matrix (weight __LONG_MAX__ => no edge) +// Output: A distance matrix (distance __LONG_MAX__ => no path) +vector> floyd_warshall(vector> adj) { + size_t n = adj.size(); + vector> distance(n, vector(n, 0)); + + // Initialize distance + for (size_t i = 0; i < n; i++) { + for (size_t j = 0; j < n; j++) { + // Original floyd-warshall + // if (i == j) distance[i][j] = 0; + // else if (adj[i][j]) distance[i][j] = adj[i][j]; + // else distance[i][j] = __LONG_MAX__; + + // My version + if (i == j) distance[i][j] = 0; + else distance[i][j] = adj[i][j]; + } + } + + // Find shortest distances + for (size_t k = 0; k < n; k++) { + for (size_t i = 0; i < n; i++) { + for (size_t j = 0; j < n; j++) { + if (distance[i][k] == __LONG_MAX__ || distance[k][j] == __LONG_MAX__) + continue; + distance[i][j] = min(distance[i][j], + distance[i][k] + distance[k][j]); + } + } + } + + return distance; +} diff --git a/CPP/graph_theory/graph.cpp.snip b/CPP/graph_theory/graph.cpp.snip new file mode 100644 index 0000000..3279a98 --- /dev/null +++ b/CPP/graph_theory/graph.cpp.snip @@ -0,0 +1,267 @@ +// #define NDEBUG + +#include +#include +#include +#include +#include +#include + +template class Graph; +template class WeightedGraph; +template class BaseGraph; + +template +class Graph : public BaseGraph { + virtual int construct_edge(int v, T w) { + w; // Remove warning + return v; + } + + virtual int get_edge_vertex(int e) { + return e; + } + + virtual T get_edge_weight(int e) { + e; // Remove warning + return 1; + } + + public: + Graph(size_t N, size_t min_index) : BaseGraph(N, min_index) {}; +}; + +template +class WeightedGraph : public BaseGraph> { + virtual std::pair construct_edge(int v, T w) { + return {w, v}; + } + + virtual int get_edge_vertex(std::pair e) { + return e.second; + } + + virtual T get_edge_weight(std::pair e) { + return e.first; + } + + bool min_cut_max_flow_dfs(std::vector&, std::pair&>>> &edge_pairs, std::vector&, std::pair&>>& path, std::vector& visited, int v, int sink, T threshold) { + if (v == sink) + return EXIT_SUCCESS; + visited[v] = true; + for (auto e : edge_pairs[v]) { + int u = get_edge_vertex(e.first); + int w = get_edge_weight(e.first); + if (!visited[u] && w >= threshold) { + bool res = min_cut_max_flow_dfs(edge_pairs, path, visited, u, sink, threshold); + if (res == EXIT_SUCCESS) { + path.push_back(e); + return EXIT_SUCCESS; + } + } + } + return EXIT_FAILURE; + } + + public: + WeightedGraph(size_t N, size_t min_index) : BaseGraph>(N, min_index) {}; + + Graph get_shortest_path_graph(int from) { + std::vector best_distance = this->dijkstra(from); + Graph G(this->N, this->min_index); + for (size_t i = this->min_index; i < this->N; i++) { + for (auto e : this->adj[i]) { + T w = get_edge_weight(e); + int v = get_edge_vertex(e); + if (best_distance[i] + w == best_distance[v]) + G.add_directed_edge(i, v); + } + } + return G; + } + + WeightedGraph get_weighted_shortest_path_graph(int from) { + std::vector best_distance = this->dijkstra(from); + WeightedGraph G(this->N, this->min_index); + for (size_t i = this->min_index; i < this->N; i++) { + for (auto e : this->adj[i]) { + T w = get_edge_weight(e); + int v = get_edge_vertex(e); + if (best_distance[i] + w == best_distance[v]) + G.add_directed_edge(i, v, w); + } + } + return G; + } + + // Running time: O(m^2n) + T min_cut_max_flow(int source, int sink) { + // TODO: Implement non-destructive version? + // Ford-Fulkerson - "scaling algorithm" version + std::vector&, std::pair&>>> edge_pairs(this->N, std::vector&, std::pair&>>()); + T threshold = 0; + for (size_t v = this->min_index; v < this->N; v++) { + for (auto& e : this->adj[v]) { + if (get_edge_weight(e) == 0) continue; + threshold = threshold > get_edge_weight(e) ? threshold : get_edge_weight(e); // max(threshold, get_edge_weight(e)); + int u = get_edge_vertex(e); + this->adj[u].push_back(construct_edge(v, 0)); + edge_pairs[v].push_back({e, this->adj[u][this->adj[u].size()-1]}); + edge_pairs[u].push_back({this->adj[u][this->adj[u].size()-1], e}); + } + } + + std::vector visited(this->N, false); + std::vector&, std::pair&>> path; + while (true) { + bool res = min_cut_max_flow_dfs(edge_pairs, path, visited, source, sink, threshold); + if (res == EXIT_SUCCESS) { + T path_max_flow = std::numeric_limits::max(); + for (auto e : path) + path_max_flow = path_max_flow < get_edge_weight(e.first) ? path_max_flow : get_edge_weight(e.first); + // min(path_max_flow, get_edge_weight(e.first)); + for (auto e : path) { + e.first = construct_edge(get_edge_vertex(e.first), get_edge_weight(e.first)-threshold); + e.second = construct_edge(get_edge_vertex(e.second), get_edge_weight(e.second)+threshold); + } + } else { + if (threshold == 1) + break; // Done! + threshold /= 2; + } + std::fill(visited.begin(), visited.end(), false); + path.clear(); + } + + T max_flow = 0; + for (auto e : this->adj[sink]) { + max_flow += get_edge_weight(e); + } + + return max_flow; + } +}; + +template +class BaseGraph { + std::vector top_sort; + int contains_cycles = 0; // 0 = unknown, 1 = yes, 2 = no + bool contains_negative_edge_weight = false; + + virtual int get_edge_vertex(EDGE) = 0; + virtual T get_edge_weight(EDGE) = 0; + virtual EDGE construct_edge(int vertex, T w) = 0; + + bool topological_sort_dfs(std::vector& status, int v, std::vector& top_sort) { + if (status[v] == 1) + return EXIT_FAILURE; + if (status[v] == 0) { + status[v] = 1; + for (auto e : adj[v]) { + bool res = topological_sort_dfs(status, get_edge_vertex(e), top_sort); + if (res == EXIT_FAILURE) + return EXIT_FAILURE; + } + status[v] = 2; + top_sort.push_back(v); + } + return EXIT_SUCCESS; + } + + void do_topological_sort() { + std::vector status(N, 0); + std::vector top_sort = std::vector(); + for (size_t i = min_index; i < N; i++) { + bool res = topological_sort_dfs(status, i, top_sort); + if (res == EXIT_FAILURE) { + contains_cycles = 1; + return; + } + } + + contains_cycles = 2; + std::reverse(top_sort.begin(), top_sort.end()); + this->top_sort = top_sort; + } + + public: + size_t N; + size_t min_index; + std::vector> adj; + + BaseGraph(size_t N, size_t min_index) { + N += min_index; + this->N = N; + this->min_index = min_index; + adj = std::vector>(N, std::vector()); + } + + void add_directed_edge(int from, int to, T w = 1) { + adj[from].push_back(construct_edge(to, w)); + contains_cycles = 0; + if (w < 0) + contains_negative_edge_weight = true; + } + + void add_undirected_edge(size_t a, size_t b, T w = 1) { + adj[a].push_back(construct_edge(b, w)); + adj[b].push_back(construct_edge(a, w)); + contains_cycles = 0; + if (w < 0) + contains_negative_edge_weight = true; + } + + std::vector get(int v) { + return adj[v]; + } + + std::vector topological_sort() { + if (contains_cycles != 2) + std::cerr << "Check for cycles first!" << std::endl; + return top_sort; + } + + bool has_cycles() { + if (contains_cycles == 0) + do_topological_sort(); + return contains_cycles == 1; + } + + std::vector count_paths(int from) { + if (contains_cycles != 2) + std::cerr << "Check for cycles first!" << std::endl; + std::vector paths(N, 0); + for (auto v : top_sort) { + T p = 0; + if (v == from) p++; + for (auto e : adj[v]) + p += paths[get_edge_vertex(e)]; + paths[v] = p; + } + return paths; + } + + std::vector dijkstra(int from) { + if (contains_negative_edge_weight) + std::cerr << "Dijkstra only works if the graph doesn't contain any negative edge weights" << std::endl; + std::vector distance(N, std::numeric_limits::max()); + distance[from] = 0; + std::vector processed(N, false); + std::priority_queue> q; + q.push({0, from}); + while (!q.empty()) { + int v = q.top().second; q.pop(); + if (processed[v]) continue; + processed[v] = true; + for (auto e : adj[v]) { + int u = get_edge_vertex(e); + T w = get_edge_weight(e); + if (distance[v] + w < distance[u]) { + distance[u] = distance[v] + w; + q.push({-distance[u], u}); + } + } + } + return distance; + } +}; diff --git a/CPP/graph_theory/test/test b/CPP/graph_theory/test/test new file mode 100644 index 0000000000000000000000000000000000000000..d7e015a2eab2407c0e27ea53ebe65f14e43ae879 GIT binary patch literal 101816 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 RtbXeaT=E@3h2UfD{{Y#q0S*8F literal 0 HcmV?d00001 diff --git a/CPP/graph_theory/test/test.cpp b/CPP/graph_theory/test/test.cpp new file mode 100644 index 0000000..b7d719e --- /dev/null +++ b/CPP/graph_theory/test/test.cpp @@ -0,0 +1,372 @@ +// #define NDEBUG + +#include +// #include +#include +// #include +// #include +#include +// #include +// #include +// #include +// #include +// #include + + +// #define NDEBUG + +#include +#include +#include +#include +#include +#include + +template class Graph; +template class WeightedGraph; +template class BaseGraph; + +template +class Graph : public BaseGraph { + virtual int construct_edge(int v, T w) { + w; // Remove warning + return v; + } + + virtual int get_edge_vertex(int e) { + return e; + } + + virtual T get_edge_weight(int e) { + e; // Remove warning + return 1; + } + + public: + Graph(size_t N, size_t min_index) : BaseGraph(N, min_index) {}; +}; + +template +class WeightedGraph : public BaseGraph> { + virtual std::pair construct_edge(int v, T w) { + return {w, v}; + } + + virtual int get_edge_vertex(std::pair e) { + return e.second; + } + + virtual T get_edge_weight(std::pair e) { + return e.first; + } + + bool min_cut_max_flow_dfs(std::vector&, std::pair&>>> &edge_pairs, std::vector&, std::pair&>>& path, std::vector& visited, int v, int sink, T threshold) { + if (v == sink) + return EXIT_SUCCESS; + visited[v] = true; + for (auto e : edge_pairs[v]) { + int u = get_edge_vertex(e.first); + int w = get_edge_weight(e.first); + if (!visited[u] && w >= threshold) { + bool res = min_cut_max_flow_dfs(edge_pairs, path, visited, u, sink, threshold); + if (res == EXIT_SUCCESS) { + path.push_back(e); + return EXIT_SUCCESS; + } + } + } + return EXIT_FAILURE; + } + + public: + WeightedGraph(size_t N, size_t min_index) : BaseGraph>(N, min_index) {}; + + Graph get_shortest_path_graph(int from) { + std::vector best_distance = this->dijkstra(from); + Graph G(this->N, this->min_index); + for (size_t i = this->min_index; i < this->N; i++) { + for (auto e : this->adj[i]) { + T w = get_edge_weight(e); + int v = get_edge_vertex(e); + if (best_distance[i] + w == best_distance[v]) + G.add_directed_edge(i, v); + } + } + return G; + } + + WeightedGraph get_weighted_shortest_path_graph(int from) { + std::vector best_distance = this->dijkstra(from); + WeightedGraph G(this->N, this->min_index); + for (size_t i = this->min_index; i < this->N; i++) { + for (auto e : this->adj[i]) { + T w = get_edge_weight(e); + int v = get_edge_vertex(e); + if (best_distance[i] + w == best_distance[v]) + G.add_directed_edge(i, v, w); + } + } + return G; + } + + // Running time: O(m^2n) + T min_cut_max_flow(int source, int sink) { + // TODO: Implement non-destructive version? + // Ford-Fulkerson - "scaling algorithm" version + std::vector&, std::pair&>>> edge_pairs(this->N, std::vector&, std::pair&>>()); + T threshold = 0; + for (size_t v = this->min_index; v < this->N; v++) { + for (auto& e : this->adj[v]) { + if (get_edge_weight(e) == 0) continue; + threshold = threshold > get_edge_weight(e) ? threshold : get_edge_weight(e); // max(threshold, get_edge_weight(e)); + int u = get_edge_vertex(e); + this->adj[u].push_back(construct_edge(v, 0)); + edge_pairs[v].push_back({e, this->adj[u][this->adj[u].size()-1]}); + edge_pairs[u].push_back({this->adj[u][this->adj[u].size()-1], e}); + } + } + + std::vector visited(this->N, false); + std::vector&, std::pair&>> path; + while (true) { + bool res = min_cut_max_flow_dfs(edge_pairs, path, visited, source, sink, threshold); + if (res == EXIT_SUCCESS) { + T path_max_flow = std::numeric_limits::max(); + for (auto e : path) + path_max_flow = path_max_flow < get_edge_weight(e.first) ? path_max_flow : get_edge_weight(e.first); + // min(path_max_flow, get_edge_weight(e.first)); + for (auto e : path) { + e.first = construct_edge(get_edge_vertex(e.first), get_edge_weight(e.first)-threshold); + e.second = construct_edge(get_edge_vertex(e.second), get_edge_weight(e.second)+threshold); + } + } else { + if (threshold == 1) + break; // Done! + threshold /= 2; + } + std::fill(visited.begin(), visited.end(), false); + path.clear(); + } + + T max_flow = 0; + for (auto e : this->adj[sink]) { + max_flow += get_edge_weight(e); + } + + return max_flow; + } +}; + +template +class BaseGraph { + std::vector top_sort; + int contains_cycles = 0; // 0 = unknown, 1 = yes, 2 = no + bool contains_negative_edge_weight = false; + + virtual int get_edge_vertex(EDGE) = 0; + virtual T get_edge_weight(EDGE) = 0; + virtual EDGE construct_edge(int vertex, T w) = 0; + + bool topological_sort_dfs(std::vector& status, int v, std::vector& top_sort) { + if (status[v] == 1) + return EXIT_FAILURE; + if (status[v] == 0) { + status[v] = 1; + for (auto e : adj[v]) { + bool res = topological_sort_dfs(status, get_edge_vertex(e), top_sort); + if (res == EXIT_FAILURE) + return EXIT_FAILURE; + } + status[v] = 2; + top_sort.push_back(v); + } + return EXIT_SUCCESS; + } + + void do_topological_sort() { + std::vector status(N, 0); + std::vector top_sort = std::vector(); + for (size_t i = min_index; i < N; i++) { + bool res = topological_sort_dfs(status, i, top_sort); + if (res == EXIT_FAILURE) { + contains_cycles = 1; + return; + } + } + + contains_cycles = 2; + std::reverse(top_sort.begin(), top_sort.end()); + this->top_sort = top_sort; + } + + public: + size_t N; + size_t min_index; + std::vector> adj; + + BaseGraph(size_t N, size_t min_index) { + N += min_index; + this->N = N; + this->min_index = min_index; + adj = std::vector>(N, std::vector()); + } + + void add_directed_edge(int from, int to, T w = 1) { + adj[from].push_back(construct_edge(to, w)); + contains_cycles = 0; + if (w < 0) + contains_negative_edge_weight = true; + } + + void add_undirected_edge(size_t a, size_t b, T w = 1) { + adj[a].push_back(construct_edge(b, w)); + adj[b].push_back(construct_edge(a, w)); + contains_cycles = 0; + if (w < 0) + contains_negative_edge_weight = true; + } + + std::vector get(int v) { + return adj[v]; + } + + std::vector topological_sort() { + if (contains_cycles != 2) + std::cerr << "Check for cycles first!" << std::endl; + return top_sort; + } + + bool has_cycles() { + if (contains_cycles == 0) + do_topological_sort(); + return contains_cycles == 1; + } + + std::vector count_paths(int from) { + if (contains_cycles != 2) + std::cerr << "Check for cycles first!" << std::endl; + std::vector paths(N, 0); + for (auto v : top_sort) { + T p = 0; + if (v == from) p++; + for (auto e : adj[v]) + p += paths[get_edge_vertex(e)]; + paths[v] = p; + } + return paths; + } + + std::vector dijkstra(int from) { + if (contains_negative_edge_weight) + std::cerr << "Dijkstra only works if the graph doesn't contain any negative edge weights" << std::endl; + std::vector distance(N, std::numeric_limits::max()); + distance[from] = 0; + std::vector processed(N, false); + std::priority_queue> q; + q.push({0, from}); + while (!q.empty()) { + int v = q.top().second; q.pop(); + if (processed[v]) continue; + processed[v] = true; + for (auto e : adj[v]) { + int u = get_edge_vertex(e); + T w = get_edge_weight(e); + if (distance[v] + w < distance[u]) { + distance[u] = distance[v] + w; + q.push({-distance[u], u}); + } + } + } + return distance; + } +}; + + + +using namespace std; + +int main() { + // TODO: SEGFAULT + // WeightedGraph G(6, 1); + // G.add_directed_edge(1, 2, 5); + // G.add_directed_edge(1, 4, 4); + // G.add_directed_edge(4, 2, 3); + // G.add_directed_edge(2, 3, 6); + // G.add_directed_edge(4, 5, 1); + // G.add_directed_edge(3, 5, 8); + // G.add_directed_edge(3, 6, 5); + // G.add_directed_edge(5, 6, 2); + + // cout << "foo" << endl; + // cout << G.min_cut_max_flow(1, 6) << endl; + + // // Works! (Probably) + // WeightedGraph G(6, 1); + // G.add_directed_edge(1, 2, 5); + // G.add_directed_edge(4, 1, 4); + // G.add_directed_edge(4, 5, 4); + // G.add_directed_edge(5, 2, 4); + // G.add_directed_edge(5, 3, 4); + // G.add_directed_edge(2, 3, 4); + // G.add_directed_edge(3, 6, 4); + + // cout << "foo" << endl; + // bool cycles = G.has_cycles(); + // cout << "Has cycles: " << cycles << endl; + // vector top = G.topological_sort(); + // for (auto i : top) + // cout << i << " "; + // cout << endl; + + // // Works! (Probably) + // WeightedGraph G(6, 1); + // G.add_directed_edge(1, 2, 5); + // G.add_directed_edge(1, 4, 4); + // G.add_directed_edge(5, 1, 4); + // G.add_directed_edge(4, 5, 4); + // G.add_directed_edge(5, 2, 4); + // G.add_directed_edge(5, 3, 4); + // G.add_directed_edge(2, 3, 4); + // G.add_directed_edge(3, 6, 4); + + // cout << "foo" << endl; + // bool cycles = G.has_cycles(); + // cout << "Has cycles: " << cycles << endl; + + // // Works! (Probably) + // Graph G(6, 1); + // G.add_directed_edge(1, 2); + // G.add_directed_edge(4, 1); + // G.add_directed_edge(4, 5); + // G.add_directed_edge(5, 2); + // G.add_directed_edge(5, 3); + // G.add_directed_edge(2, 3); + // G.add_directed_edge(3, 6); + + // cout << "foo" << endl; + // bool cycles = G.has_cycles(); + // cout << "Has cycles: " << cycles << endl; + // vector top = G.topological_sort(); + // for (auto i : top) + // cout << i << " "; + // cout << endl; + + // Works! (Probably) + WeightedGraph G(6, 1); + G.add_directed_edge(1, 2, 5); + G.add_directed_edge(4, 1, 4); + G.add_directed_edge(4, 5, 4); + G.add_directed_edge(5, 2, 4); + G.add_directed_edge(5, 3, 4); + G.add_directed_edge(2, 3, 4); + G.add_directed_edge(3, 6, 4); + + cout << "foo" << endl; + bool cycles = G.has_cycles(); + cout << "Has cycles: " << cycles << endl; + vector dist = G.dijkstra(1); + for (int i = 1; i <= 6; i++) + cout << i << " " << dist[i] << endl; + + return EXIT_SUCCESS; +} diff --git a/CPP/hpc/dmalloc_2d.c.snip b/CPP/hpc/dmalloc_2d.c.snip new file mode 100644 index 0000000..71027e0 --- /dev/null +++ b/CPP/hpc/dmalloc_2d.c.snip @@ -0,0 +1,25 @@ +#include + +double** dmalloc_2d(int m, int n); +void dfree_2d(double **A); + +// allocate a double-precision m x n matrix +double** dmalloc_2d(int m, int n) { + if (m <= 0 || n <= 0) return NULL; + double **A = malloc(m * sizeof(double *)); + if (A == NULL) return NULL; + A[0] = malloc(m*n*sizeof(double)); + if (A[0] == NULL) { + free(A); + return NULL; + } + for (int i = 1; i < m; i++) + A[i] = A[0] + i * n; + return A; +} + +// de-allocating memory, allocated with dmalloc_2d +void dfree_2d(double **A) { + free(A[0]); + free(A); +} diff --git a/CPP/hpc/include_cblas.c.snip b/CPP/hpc/include_cblas.c.snip new file mode 100644 index 0000000..1b3612a --- /dev/null +++ b/CPP/hpc/include_cblas.c.snip @@ -0,0 +1,6 @@ +#include +#if defined(__MACH__) && defined(__APPLE__) +#include +#else +#include +#endif diff --git a/CPP/range_queries/SegmentTree.cpp.snip b/CPP/range_queries/SegmentTree.cpp.snip new file mode 100644 index 0000000..960d27a --- /dev/null +++ b/CPP/range_queries/SegmentTree.cpp.snip @@ -0,0 +1,142 @@ +#define NDEBUG + +#include +#include +#include +#include + +template class SegmentTree; +template class SumSegmentTree; +template class MinSegmentTree; +template class MaxSegmentTree; +template class DifferenceSegmentTree; + +template +class SumSegmentTree : public SegmentTree { + virtual T cmp (T a, T b) override { + return a + b; + } + + public: + SumSegmentTree(size_t N) : SegmentTree(N, 0) {}; +}; + +template +class MinSegmentTree : public SegmentTree { + virtual T cmp (T a, T b) override { + return (a < b) ? a : b; + } + + public: + MinSegmentTree(size_t N) : SegmentTree(N, std::numeric_limits::max()) {}; +}; + +template +class MaxSegmentTree : public SegmentTree { + virtual T cmp (T a, T b) override { + return (a > b) ? a : b; + } + + public: + MaxSegmentTree(size_t N) : SegmentTree(N, std::numeric_limits::min()) {}; +}; + +template +class DifferenceSegmentTree : public SegmentTree { + virtual T cmp (T a, T b) override { + return a+b; + } + + public: + virtual void add(size_t i, T val) override { + this->set(i, this->get(i) + val); + if (i != this->N-1) + this->set(i+1, this->get(i+1) - val); + } + + void add_range(size_t i, size_t j, T val) { + this->set(i, this->get(i) + val); + if (j+1 != this->N) + this->set(j+1, this->get(j+1)-val); + } + + DifferenceSegmentTree(size_t N) : SegmentTree(N, 0) {}; +}; + +template +class SegmentTree { + const T unit; + virtual T cmp (T a, T b) = 0; + std::vector arr; + + public: + size_t N; + + void print() { + size_t newline = 2; + for (size_t i = 1; i < 2*N; i++) { + std::cout << arr[i] << " "; + if (i == newline-1) { + std::cout << std::endl; + newline *= 2; + } + } + } + + SegmentTree(size_t N, T unit) : unit(unit) { + this->N = N; + // Initialize the array to the size of the smallest power of two greater than N + size_t exp = 0; + while (N != 0) { + N = N >> 1; + exp++; + } + if ((size_t)(1 << (exp-1)) == this->N) + arr = std::vector(this->N*2, unit); + else + arr = std::vector(1 << (exp+1), unit); + } + + T get(size_t i) { +#ifndef NDEBUG + if (i >= N) + std::cerr << "Tried accessing index " << i << " out of " << N-1 << " (remember, 0-indexing)" << std::endl; +#endif + return arr[i+N]; + } + /* T operator[](size_t i) { get(i); }; */ + + virtual void set(size_t i, T val) { + update_field(i, val); + } + + void update_field(size_t i, T val) { +#ifndef NDEBUG + if (i >= N) + std::cerr << "Tried updating index " << i << " out of " << N-1 << " (remember, 0-indexing)" << std::endl; +#endif + i += N; // Put the index at the leaf nodes/original array + arr[i] = val; + for (i /= 2; i >= 1; i /= 2) + arr[i] = cmp(arr[i*2], arr[i*2+1]); + } + + virtual void add(size_t i, T val) { + set(i, val + get(i)); + } + + T query(size_t a, size_t b) { +#ifndef NDEBUG + if (a > N || b > N || a > b) + std::cerr << "Tried querying the range: [" << a << ", " << b << "]" << " of max index " << N-1 << std::endl; +#endif + a += N; b += N; + size_t val = unit; + while (a <= b) { + if (a % 2 == 1) val = cmp(val, arr[a++]); + if (b % 2 == 0) val = cmp(val, arr[b--]); + a /= 2; b /= 2; + } + return val; + } +}; diff --git a/CPP/utility/prime_sieve.cpp.snip b/CPP/utility/prime_sieve.cpp.snip new file mode 100644 index 0000000..ca65eee --- /dev/null +++ b/CPP/utility/prime_sieve.cpp.snip @@ -0,0 +1,12 @@ +vector prime_sieve(int n) { + vector is_prime(n+1, true); + is_prime[0] = is_prime[1] = false; + vector primes; + for (int i = 2; i <= n; i++) { + if (!is_prime[i]) continue; + primes.push_back(i); + for (int j = i; j <= n; j += i) + is_prime[j] = false; + } + return primes; +} diff --git a/HTML.html.snip b/HTML.html.snip new file mode 100644 index 0000000..a6d05e2 --- /dev/null +++ b/HTML.html.snip @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Markdown.md.snip b/Markdown.md.snip new file mode 100644 index 0000000..bd2a481 --- /dev/null +++ b/Markdown.md.snip @@ -0,0 +1,18 @@ + + +
+ + + + + +# Table of Contents + + + + + + +
diff --git a/install_configfiles.sh b/install_configfiles.sh new file mode 100644 index 0000000..8e04bef --- /dev/null +++ b/install_configfiles.sh @@ -0,0 +1,4 @@ +#!/bin/bash +mkdir -p ~/.config/nvim; +rm -df ~/.config/nvim/snippets; +ln -s ~/Nextcloud/configfiles/vim/snippets ~/.config/nvim/snippets; diff --git a/julia_OR.jl.snip b/julia_OR.jl.snip new file mode 100644 index 0000000..32826ee --- /dev/null +++ b/julia_OR.jl.snip @@ -0,0 +1,29 @@ +using JuMP, GLPK + +profit = [5, 3, 2, 7, 4] +weight = [2, 8, 4, 2, 5] +capacity = 10 + +model = Model(GLPK.Optimizer) + +n = 5 +@variable(model, x[1:n], Bin) # Binary +@variable(model, y[1:n], Int) # Integer +@variable(model, z[1:n]) # Continuous + +@objective(model, Max, sum(profit[i]*x[i] for i=1:n)) +@constraint(model, sum(weight[i]*x[i] for i=1:n) <= capacity) +@constraint(model, [i=1:n], x[i] <= 1) # unnecessary, just to include this syntax in the example + +JuMP.optimize!(model) + +if termination_status(model) == MOI.OPTIMAL + println("Objective is: ", JuMP.objective_value(model)) + print("Solution is:") + for i=1:n + print(" ", JuMP.value(x[i])) + end + println() +else + println("Optimise was not successful. Return code ", termination_status(model)) +end diff --git a/latex.tex.snip b/latex.tex.snip new file mode 100644 index 0000000..42d6127 --- /dev/null +++ b/latex.tex.snip @@ -0,0 +1,17 @@ +\documentclass[11pt]{article} +\usepackage{../../../Texpreambles/JRPreamble} +\DTMsavedate{mydate}{2022-9-5}% chktex 8 + +\usepackage{listings} + +\begin{document} +\selectlanguage{british} + +\begin{center} + \Huge My Course: Assignment 1 \\ + \Large Jonas Ryssel \\ + \Large s184009 \\ + \Large \DTMusedate{mydate} +\end{center} + +\end{document} diff --git a/rust/unit_test.rs.snip b/rust/unit_test.rs.snip new file mode 100644 index 0000000..e21624a --- /dev/null +++ b/rust/unit_test.rs.snip @@ -0,0 +1,9 @@ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn my_test() { + assert_eq!(add(1, 2), 3); + } +}