├── .gitignore ├── scripts └── input │ ├── shuffle.py │ └── delete.py ├── utils.hpp ├── LICENSE ├── README.md ├── network_simplex.hpp ├── md5.hpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # IO 35 | input.txt 36 | output.txt -------------------------------------------------------------------------------- /scripts/input/shuffle.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def return_only_ascii(str): 4 | return ''.join([x for x in str if x.isascii()]) 5 | 6 | #n_start=6282 7 | #n_end=85921 8 | with open("input.txt", encoding="utf8") as f: 9 | lines = f.readlines() 10 | #lines=[return_only_ascii(x) for x in lines] 11 | #tmp=lines[n_start:n_end] 12 | random.shuffle(lines) 13 | #lines[n_start:n_end]=tmp 14 | with open("outfile.txt", "w", encoding="utf8") as f: 15 | f.writelines(lines) -------------------------------------------------------------------------------- /scripts/input/delete.py: -------------------------------------------------------------------------------- 1 | import random 2 | import os 3 | import re 4 | 5 | m = {} 6 | with open("input.txt") as src, open("temp.txt", "w") as dest: 7 | for line in src: 8 | if line[0] == '#': 9 | continue 10 | res = re.findall(r'\(.*?\)', line)[0] 11 | if(res not in m): 12 | m[res] = [] 13 | m[res].append(line) 14 | for key in m: 15 | seen = False 16 | for s in m[key]: 17 | if '%' in s: 18 | seen = True 19 | break 20 | if seen: 21 | m[key] = [] 22 | for key in m: 23 | for s in m[key]: 24 | dest.write(s) 25 | 26 | 27 | os.rename("temp.txt", "output.txt") -------------------------------------------------------------------------------- /utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace utils{ 9 | 10 | std::wstring toWide(std::string s){ 11 | std::wstring_convert> converter; 12 | std::wstring wide = converter.from_bytes(s); 13 | return wide; 14 | } 15 | 16 | 17 | size_t utf8_length(std::string s){ return toWide(s).length(); } 18 | 19 | class timer : std::chrono::high_resolution_clock { 20 | const time_point start_time; 21 | public: 22 | timer(): start_time(now()) {} 23 | rep elapsed_time() const { return std::chrono::duration_cast(now()-start_time).count(); } 24 | }; 25 | 26 | void up(std::string& str){ transform(str.begin(), str.end(), str.begin(), ::toupper); } 27 | 28 | } // namespace utils 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Juan Ignacio Suarez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastTradeMaximizer 2 | A running-time optimized custom version of @chrisokasaki TradeMaximizer. 3 | 4 | - https://github.com/chrisokasaki/TradeMaximizer 5 | - https://boardgamegeek.com/wiki/page/TradeMaximizer 6 | 7 | Chris' original version uses augmenting paths with a modified Dijkstra algorithm to find a perfect matching. This algorithm is said to have a complexity of $O(V * E * log U)$, though performance-wise it runs on par with Edmonds-Karp; Edmonds-Karp does have a closed form running time that's easier to compare to: $O(V * E^2)$. 8 | 9 | This version formulates the min-cost max-flow matching problem as a linear programming problem instead and uses a specialized Simplex algorithm to solve it. This algorithm's theoretical worst case is exponential although in practice it converges to the solution **very** fast with an average complexity of $O(V * E)$ and a good constant. 10 | 11 | The code also comes with implementation details to make it go faster, besides the usage of modern C++ tools itself. For example, item nodes are mapped to $[0,totalItems)$ indices so we can use a Simplex implementation that uses contiguous vector memory spaces. It also implements a better heuristic than "randomly try to find better solutions", which not only finds strictly better solutions, it also does so quite faster. 12 | 13 | # Usage 14 | 15 | FastTradeMaximizer attempts to copy the original's behavior as much as possible, so most of its functionality is the same. That said, it uses a better way to maximize the number of trading users, so the concept of random iterations doesn't exist anymore: it iterates as long as it wants to improve the solution in each step and stops when it can no longer do anything to improve it. 16 | 17 | ## Compile 18 | 19 | g++ main.cpp -o ftm.exe 20 | 21 | You need to compile C++17, if your compiler is not up to date ```-std=c++17``` might be helpful. 22 | 23 | - For some reason installing an updated version of ```g++``` on Windows proved itself not to be as simple as doing one or two google searches. You can follow Microsoft's straightforward installation guide here: https://code.visualstudio.com/docs/cpp/config-mingw#_installing-the-mingww64-toolchain 24 | 25 | - If running the program yields the error "The code execution cannot proceed because libgcc_s_seh-1.dll was not found. Reinstalling the program may fix this problem.", you can try bypassing it by compiling with ```g++ -static -static-libgcc -static-libstdc++ main.cpp -o ftm.exe``` 26 | 27 | ## Run 28 | 29 | ftm.exe < wants.txt 30 | 31 | # Benchmarks 32 | 33 | All testcase files can be found in the ```/testcases``` directory. This is not an objective benchmark because I'm using some official results that were probably ran in a slightly slower computer than mine, it's mostly a showcase of improved solutions + a lot faster. I used the following command lines to run both programs: 34 | 35 | java -jar tm-threaded.jar < input.txt > output.txt 36 | ftm.exe < input.txt > output.txt 37 | 38 | | Testcase | TradeMaximizer Version 1.5c multi-threaded | FastTradeMaximizer Version 0.3 | 39 | |-------------|--------------------------------------------------------------------------|----------------------------------------------------| 40 | | ARG2024May | 421 users in 1 iteration after 259650ms (4min 19sec 650ms) | 427 users after 35941ms (35sec 941ms) | 41 | | US2024Jan | 300 users in 72 iterations after 4341261ms (1hrs 12min 21sec 261ms) | 313 users in 16999ms (16sec 999ms) | 42 | | US2024Feb | 222 users in 72 iterations after 1211105ms (20min 11sec 105ms) | 230 users in 15609ms (15sec 609ms) | 43 | | US2024Apr | 241 users in 72 iterations after 2633036ms (43min 53sec 36ms) | 250 users in 42214ms (42sec 214ms) | 44 | | US2024May | 251 users in 72 iterations after 3685621ms (1hrs 1min 25sec 621ms) | 257 users in 33832ms (33sec 832ms) | 45 | 46 | # Additional comments 47 | 48 | - Item nodes have a $\texttt{supply}\in\mathbb{N}$ of flow, and edges have $\texttt{lower}$ and $\texttt{upper}$ bound flow requirements. By default, senders have $\texttt{supply=1}$, receivers $\texttt{supply=-1}$, and edges $\texttt{lower=0}, \texttt{upper=1}$. This may let you think of a new feature, for example $\texttt{lower=upper=1}$ **forces** an edge to be selected. You could also set $\texttt{supply=0}$ to represent a "passing by" optional node, or $\texttt{supply=2}$ to let a node get matched to up to 2 edges. 49 | 50 | - Edges also have a $\texttt{cost}\in\mathbb{N}$ per unit of flow associated ($\texttt{cost=1}$ by default and $\texttt{cost=INF}$ for edges we don't want to use, like self loops). Conventional flow algorithms will forbid negative values or may require some sort of special initialization, a linear solver doesn't care. 51 | 52 | - Priorities seem a bit useless so they were not implemented, they can be included by tweaking edges' $\texttt{cost}$. Just remember that increasing an edge's cost makes it less prioritary than **all** other edges, not just yours. 53 | 54 | - Random generation and the program itself are completely different from the Java version, so it's impossible to reproduce the same outputs from previous Math Trades given the same $\texttt{SEED}$. However the number of maximum trades should be equal and the tracked metric should be equal or better. 55 | 56 | - Simplex solves a superset of the problem, we don't have any generic graph, it's bipartite. 57 | 58 | - If you're on Linux and your CPU supports it, you might want to experiment building with #pragma flags: ```#pragma GCC target("avx2,bmi,bmi2,popcnt,lzcnt")``` 59 | 60 | - ```std::unordered_map``` can be replaced by ```__gnu_pbds::gp_hash_table``` 61 | -------------------------------------------------------------------------------- /network_simplex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct linked_lists { 11 | int L, N; 12 | std::vector next, prev; 13 | 14 | // L: lists are [0...L), N: integers are [0...N) 15 | explicit linked_lists(int L = 0, int N = 0) { assign(L, N); } 16 | 17 | int rep(int l) const { return l + N; } 18 | int head(int l) const { return next[rep(l)]; } 19 | int tail(int l) const { return prev[rep(l)]; } 20 | bool empty(int l) const { return next[rep(l)] == rep(l); } 21 | 22 | void init(int l, int n) { meet(rep(l), n, rep(l)); } 23 | void clear(int l) { next[rep(l)] = prev[rep(l)] = rep(l); } 24 | 25 | void push_front(int l, int n) { assert(l < L && n < N), meet(rep(l), n, head(l)); } 26 | void push_back(int l, int n) { assert(l < L && n < N), meet(tail(l), n, rep(l)); } 27 | void insert_before(int i, int n) { assert(i < N && n < N), meet(prev[i], n, i); } 28 | void insert_after(int i, int n) { assert(i < N && n < N), meet(i, n, next[i]); } 29 | void erase(int n) { assert(n < N), meet(prev[n], next[n]); } 30 | void pop_front(int l) { assert(l < L && !empty(l)), meet(rep(l), next[head(l)]); } 31 | void pop_back(int l) { assert(l < L && !empty(l)), meet(prev[tail(l)], rep(l)); } 32 | 33 | void splice_front(int l, int b) { 34 | if (l != b && !empty(b)) 35 | meet(tail(b), head(l)), meet(rep(l), head(b)), clear(b); 36 | } 37 | void splice_back(int l, int b) { 38 | if (l != b && !empty(b)) 39 | meet(tail(l), head(b)), meet(tail(b), rep(l)), clear(b); 40 | } 41 | 42 | void clear() { 43 | std::iota(begin(next) + N, end(next), N); 44 | std::iota(begin(prev) + N, end(prev), N); 45 | } 46 | void assign(int L, int N) { 47 | this->L = L, this->N = N; 48 | next.resize(N + L), prev.resize(N + L), clear(); 49 | } 50 | 51 | private: 52 | inline void meet(int u, int v) { next[u] = v, prev[v] = u; } 53 | inline void meet(int u, int v, int w) { meet(u, v), meet(v, w); } 54 | }; 55 | 56 | #define FOR_EACH_IN_LINKED_LIST(i, l, lists) \ 57 | for (int z##i = l, i = lists.head(z##i); i != lists.rep(z##i); i = lists.next[i]) 58 | 59 | #define FOR_EACH_IN_LINKED_LIST_REVERSE(i, l, lists) \ 60 | for (int z##i = l, i = lists.tail(z##i); i != lists.rep(z##i); i = lists.prev[i]) 61 | 62 | template 63 | struct network_simplex { 64 | explicit network_simplex(int V) : V(V), node(V + 1) {} 65 | 66 | void add(int u, int v, Flow lower, Flow upper, Cost cost) { 67 | assert(0 <= u && u < V && 0 <= v && v < V); 68 | edge.push_back({{u, v}, lower, upper, cost}), E++; 69 | } 70 | 71 | void add_supply(int u, Flow supply) { node[u].supply += supply; } 72 | void add_demand(int u, Flow demand) { node[u].supply -= demand; } 73 | 74 | auto get_supply(int u) const { return node[u].supply; } 75 | auto get_potential(int u) const { return node[u].pi; } 76 | auto get_flow(int e) const { return edge[e].flow; } 77 | 78 | auto get_circulation_cost() const { 79 | CostSum sum = 0; 80 | for (int e = 0; e < E; e++) { 81 | sum += edge[e].flow * CostSum(edge[e].cost); 82 | } 83 | return sum; 84 | } 85 | 86 | auto mincost_circulation() { 87 | static constexpr bool INFEASIBLE = false, OPTIMAL = true; 88 | 89 | Flow sum_supply = 0; 90 | for (int u = 0; u < V; u++) { 91 | sum_supply += node[u].supply; 92 | } 93 | if (sum_supply != 0) { 94 | return INFEASIBLE; 95 | } 96 | for (int e = 0; e < E; e++) { 97 | if (edge[e].lower > edge[e].upper) { 98 | return INFEASIBLE; 99 | } 100 | } 101 | 102 | init(); 103 | int in_arc = select_pivot_edge(); 104 | while (in_arc != -1) { 105 | pivot(in_arc); 106 | in_arc = select_pivot_edge(); 107 | } 108 | 109 | for (int e = 0; e < E; e++) { 110 | auto [u, v] = edge[e].node; 111 | edge[e].flow += edge[e].lower; 112 | edge[e].upper += edge[e].lower; 113 | node[u].supply += edge[e].lower; 114 | node[v].supply -= edge[e].lower; 115 | } 116 | for (int e = E; e < E + V; e++) { 117 | if (edge[e].flow != 0) { 118 | edge.resize(E); 119 | return INFEASIBLE; 120 | } 121 | } 122 | edge.resize(E); 123 | return OPTIMAL; 124 | } 125 | 126 | private: 127 | enum ArcState : int8_t { STATE_UPPER = -1, STATE_TREE = 0, STATE_LOWER = 1 }; 128 | 129 | struct Node { 130 | int parent, pred; 131 | Flow supply; 132 | CostSum pi; 133 | }; 134 | struct Edge { 135 | int node[2]; 136 | Flow lower, upper; 137 | Cost cost; 138 | Flow flow = 0; 139 | ArcState state = STATE_LOWER; 140 | }; 141 | int V, E = 0, next_arc = 0, block_size = 0; 142 | std::vector node; 143 | std::vector edge; 144 | linked_lists children; 145 | std::vector bfs; // scratchpad for pi bfs and upwards walk 146 | 147 | auto reduced_cost(int e) const { 148 | auto [u, v] = edge[e].node; 149 | return edge[e].cost + node[u].pi - node[v].pi; 150 | } 151 | void init() { 152 | Cost slack_cost = 0; 153 | 154 | for (int e = 0; e < E; e++) { 155 | auto [u, v] = edge[e].node; 156 | edge[e].flow = 0; 157 | edge[e].state = STATE_LOWER; 158 | edge[e].upper -= edge[e].lower; 159 | node[u].supply -= edge[e].lower; 160 | node[v].supply += edge[e].lower; 161 | slack_cost += edge[e].cost < 0 ? -edge[e].cost : edge[e].cost; 162 | } 163 | 164 | edge.resize(E + V); 165 | bfs.resize(V + 1); 166 | children.assign(V + 1, V + 1); 167 | next_arc = 0; 168 | 169 | // Remove non-zero lower bounds 170 | int root = V; 171 | node[root] = {-1, -1, 0, 0}; 172 | 173 | for (int u = 0, e = E; u < V; u++, e++) { 174 | node[u].parent = root, node[u].pred = e; 175 | children.push_back(root, u); 176 | auto supply = node[u].supply; 177 | if (supply >= 0) { 178 | node[u].pi = -slack_cost; 179 | edge[e] = {{u, root}, 0, supply, slack_cost, supply, STATE_TREE}; 180 | } else { 181 | node[u].pi = slack_cost; 182 | edge[e] = {{root, u}, 0, -supply, slack_cost, -supply, STATE_TREE}; 183 | } 184 | } 185 | 186 | block_size = std::max(int(std::ceil(std::sqrt(V + 1))), std::min(10, V + 1)); 187 | } 188 | 189 | int select_pivot_edge() { // lemon-like block search 190 | int in_arc = -1; 191 | int count = block_size, seen_edges = E + V; 192 | Cost minimum = 0; 193 | for (int e = next_arc; seen_edges-- > 0; e = e + 1 == E + V ? 0 : e + 1) { 194 | if (minimum > edge[e].state * reduced_cost(e)) { 195 | minimum = edge[e].state * reduced_cost(e); 196 | in_arc = e; 197 | } 198 | if (--count == 0 && minimum < 0) { 199 | next_arc = e + 1 == E + V ? 0 : e + 1; 200 | return in_arc; 201 | } else if (count == 0) { 202 | count = block_size; 203 | } 204 | } 205 | return in_arc; 206 | } 207 | 208 | void pivot(int in_arc) { 209 | // Find join node 210 | auto [u_in, v_in] = edge[in_arc].node; 211 | int a = u_in, b = v_in; 212 | while (a != b) { 213 | a = node[a].parent == -1 ? v_in : node[a].parent; 214 | b = node[b].parent == -1 ? u_in : node[b].parent; 215 | } 216 | int join = a; 217 | 218 | // we add flow to source->target 219 | int source = edge[in_arc].state == STATE_LOWER ? u_in : v_in; 220 | int target = edge[in_arc].state == STATE_LOWER ? v_in : u_in; 221 | 222 | enum OutArcSide { SAME_EDGE, FIRST_SIDE, SECOND_SIDE }; 223 | Flow flow_delta = edge[in_arc].upper; 224 | OutArcSide side = SAME_EDGE; 225 | int u_out = -1; 226 | 227 | // Go up the cycle from source to the join node 228 | for (int u = source; u != join; u = node[u].parent) { 229 | int e = node[u].pred; 230 | bool edge_down = u == edge[e].node[1]; 231 | Flow d = edge_down ? edge[e].upper - edge[e].flow : edge[e].flow; 232 | if (flow_delta > d) { 233 | flow_delta = d; 234 | u_out = u; 235 | side = FIRST_SIDE; 236 | } 237 | } 238 | 239 | // Go up the cycle from target to the join node 240 | for (int u = target; u != join; u = node[u].parent) { 241 | int e = node[u].pred; 242 | bool edge_up = u == edge[e].node[0]; 243 | Flow d = edge_up ? edge[e].upper - edge[e].flow : edge[e].flow; 244 | if (flow_delta >= d) { 245 | flow_delta = d; 246 | u_out = u; 247 | side = SECOND_SIDE; 248 | } 249 | } 250 | 251 | // Put u_in on the same side as u_out 252 | u_in = side == FIRST_SIDE ? source : target; 253 | v_in = side == FIRST_SIDE ? target : source; 254 | 255 | // Augment along the cycle 256 | if (flow_delta) { 257 | auto delta = edge[in_arc].state * flow_delta; 258 | edge[in_arc].flow += delta; 259 | for (int u = edge[in_arc].node[0]; u != join; u = node[u].parent) { 260 | int e = node[u].pred; 261 | edge[e].flow += (u == edge[e].node[0] ? -1 : +1) * delta; 262 | } 263 | for (int u = edge[in_arc].node[1]; u != join; u = node[u].parent) { 264 | int e = node[u].pred; 265 | edge[e].flow += (u == edge[e].node[0] ? +1 : -1) * delta; 266 | } 267 | } 268 | 269 | if (side == SAME_EDGE) { 270 | edge[in_arc].state = ArcState(-edge[in_arc].state); 271 | return; 272 | } 273 | 274 | int out_arc = node[u_out].pred; 275 | edge[in_arc].state = STATE_TREE; 276 | edge[out_arc].state = edge[out_arc].flow ? STATE_UPPER : STATE_LOWER; 277 | 278 | int i = 0, S = 0; 279 | for (int u = u_in; u != u_out; u = node[u].parent) { 280 | bfs[S++] = u; 281 | } 282 | for (i = S - 1; i >= 0; i--) { 283 | int u = bfs[i], p = node[u].parent; 284 | children.erase(p); 285 | children.push_back(u, p); 286 | node[p].parent = u; 287 | node[p].pred = node[u].pred; 288 | } 289 | children.erase(u_in); 290 | children.push_back(v_in, u_in); 291 | node[u_in].parent = v_in; 292 | node[u_in].pred = in_arc; 293 | 294 | CostSum current_pi = reduced_cost(in_arc); 295 | CostSum pi_delta = (u_in == edge[in_arc].node[0] ? -1 : +1) * current_pi; 296 | assert(pi_delta != 0); 297 | 298 | bfs[0] = u_in; 299 | for (i = 0, S = 1; i < S; i++) { 300 | int u = bfs[i]; 301 | node[u].pi += pi_delta; 302 | FOR_EACH_IN_LINKED_LIST (v, u, children) { bfs[S++] = v; } 303 | } 304 | } 305 | }; 306 | -------------------------------------------------------------------------------- /md5.hpp: -------------------------------------------------------------------------------- 1 | /* MD5 2 | converted to C++ class by Frank Thilo (thilo@unix-ag.org) 3 | for bzflag (http://www.bzflag.org) 4 | 5 | based on: 6 | 7 | md5.h and md5.c 8 | reference implemantion of RFC 1321 9 | 10 | Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All 11 | rights reserved. 12 | 13 | License to copy and use this software is granted provided that it 14 | is identified as the "RSA Data Security, Inc. MD5 Message-Digest 15 | Algorithm" in all material mentioning or referencing this software 16 | or this function. 17 | 18 | License is also granted to make and use derivative works provided 19 | that such works are identified as "derived from the RSA Data 20 | Security, Inc. MD5 Message-Digest Algorithm" in all material 21 | mentioning or referencing the derived work. 22 | 23 | RSA Data Security, Inc. makes no representations concerning either 24 | the merchantability of this software or the suitability of this 25 | software for any particular purpose. It is provided "as is" 26 | without express or implied warranty of any kind. 27 | 28 | These notices must be retained in any copies of any part of this 29 | documentation and/or software. 30 | 31 | */ 32 | 33 | /* interface header */ 34 | 35 | #ifndef BZF_MD5_H 36 | #define BZF_MD5_H 37 | 38 | #include 39 | #include 40 | 41 | 42 | // a small class for calculating MD5 hashes of strings or byte arrays 43 | // it is not meant to be fast or secure 44 | // 45 | // usage: 1) feed it blocks of uchars with update() 46 | // 2) finalize() 47 | // 3) get hexdigest() string 48 | // or 49 | // MD5(std::string).hexdigest() 50 | // 51 | // assumes that char is 8 bit and int is 32 bit 52 | class MD5 53 | { 54 | public: 55 | typedef unsigned int size_type; // must be 32bit 56 | 57 | MD5(); 58 | MD5(const std::string& text); 59 | void update(const unsigned char *buf, size_type length); 60 | void update(const char *buf, size_type length); 61 | MD5& finalize(); 62 | std::string hexdigest() const; 63 | friend std::ostream& operator<<(std::ostream&, MD5 md5); 64 | 65 | private: 66 | void init(); 67 | typedef unsigned char uint1; // 8bit 68 | typedef unsigned int uint4; // 32bit 69 | enum {blocksize = 64}; // VC6 won't eat a const static int here 70 | 71 | void transform(const uint1 block[blocksize]); 72 | static void decode(uint4 output[], const uint1 input[], size_type len); 73 | static void encode(uint1 output[], const uint4 input[], size_type len); 74 | 75 | bool finalized; 76 | uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk 77 | uint4 count[2]; // 64bit counter for number of bits (lo, hi) 78 | uint4 state[4]; // digest so far 79 | uint1 digest[16]; // the result 80 | 81 | // low level logic operations 82 | static inline uint4 F(uint4 x, uint4 y, uint4 z); 83 | static inline uint4 G(uint4 x, uint4 y, uint4 z); 84 | static inline uint4 H(uint4 x, uint4 y, uint4 z); 85 | static inline uint4 I(uint4 x, uint4 y, uint4 z); 86 | static inline uint4 rotate_left(uint4 x, int n); 87 | static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); 88 | static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); 89 | static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); 90 | static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); 91 | }; 92 | 93 | std::string md5(const std::string str); 94 | 95 | #endif 96 | 97 | /* system implementation headers */ 98 | #include 99 | 100 | 101 | // Constants for MD5Transform routine. 102 | #define S11 7 103 | #define S12 12 104 | #define S13 17 105 | #define S14 22 106 | #define S21 5 107 | #define S22 9 108 | #define S23 14 109 | #define S24 20 110 | #define S31 4 111 | #define S32 11 112 | #define S33 16 113 | #define S34 23 114 | #define S41 6 115 | #define S42 10 116 | #define S43 15 117 | #define S44 21 118 | 119 | /////////////////////////////////////////////// 120 | 121 | // F, G, H and I are basic MD5 functions. 122 | inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) { 123 | return x&y | ~x&z; 124 | } 125 | 126 | inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) { 127 | return x&z | y&~z; 128 | } 129 | 130 | inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) { 131 | return x^y^z; 132 | } 133 | 134 | inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) { 135 | return y ^ (x | ~z); 136 | } 137 | 138 | // rotate_left rotates x left n bits. 139 | inline MD5::uint4 MD5::rotate_left(uint4 x, int n) { 140 | return (x << n) | (x >> (32-n)); 141 | } 142 | 143 | // FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. 144 | // Rotation is separate from addition to prevent recomputation. 145 | inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { 146 | a = rotate_left(a+ F(b,c,d) + x + ac, s) + b; 147 | } 148 | 149 | inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { 150 | a = rotate_left(a + G(b,c,d) + x + ac, s) + b; 151 | } 152 | 153 | inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { 154 | a = rotate_left(a + H(b,c,d) + x + ac, s) + b; 155 | } 156 | 157 | inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { 158 | a = rotate_left(a + I(b,c,d) + x + ac, s) + b; 159 | } 160 | 161 | ////////////////////////////////////////////// 162 | 163 | // default ctor, just initailize 164 | MD5::MD5() 165 | { 166 | init(); 167 | } 168 | 169 | ////////////////////////////////////////////// 170 | 171 | // nifty shortcut ctor, compute MD5 for string and finalize it right away 172 | MD5::MD5(const std::string &text) 173 | { 174 | init(); 175 | update(text.c_str(), text.length()); 176 | finalize(); 177 | } 178 | 179 | ////////////////////////////// 180 | 181 | void MD5::init() 182 | { 183 | finalized=false; 184 | 185 | count[0] = 0; 186 | count[1] = 0; 187 | 188 | // load magic initialization constants. 189 | state[0] = 0x67452301; 190 | state[1] = 0xefcdab89; 191 | state[2] = 0x98badcfe; 192 | state[3] = 0x10325476; 193 | } 194 | 195 | ////////////////////////////// 196 | 197 | // decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4. 198 | void MD5::decode(uint4 output[], const uint1 input[], size_type len) 199 | { 200 | for (unsigned int i = 0, j = 0; j < len; i++, j += 4) 201 | output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) | 202 | (((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24); 203 | } 204 | 205 | ////////////////////////////// 206 | 207 | // encodes input (uint4) into output (unsigned char). Assumes len is 208 | // a multiple of 4. 209 | void MD5::encode(uint1 output[], const uint4 input[], size_type len) 210 | { 211 | for (size_type i = 0, j = 0; j < len; i++, j += 4) { 212 | output[j] = input[i] & 0xff; 213 | output[j+1] = (input[i] >> 8) & 0xff; 214 | output[j+2] = (input[i] >> 16) & 0xff; 215 | output[j+3] = (input[i] >> 24) & 0xff; 216 | } 217 | } 218 | 219 | ////////////////////////////// 220 | 221 | // apply MD5 algo on a block 222 | void MD5::transform(const uint1 block[blocksize]) 223 | { 224 | uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; 225 | decode (x, block, blocksize); 226 | 227 | /* Round 1 */ 228 | FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ 229 | FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ 230 | FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ 231 | FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ 232 | FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ 233 | FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ 234 | FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ 235 | FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ 236 | FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ 237 | FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ 238 | FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ 239 | FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ 240 | FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ 241 | FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ 242 | FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ 243 | FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ 244 | 245 | /* Round 2 */ 246 | GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ 247 | GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ 248 | GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ 249 | GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ 250 | GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ 251 | GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ 252 | GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ 253 | GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ 254 | GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ 255 | GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ 256 | GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ 257 | GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ 258 | GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ 259 | GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ 260 | GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ 261 | GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ 262 | 263 | /* Round 3 */ 264 | HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ 265 | HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ 266 | HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ 267 | HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ 268 | HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ 269 | HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ 270 | HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ 271 | HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ 272 | HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ 273 | HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ 274 | HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ 275 | HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ 276 | HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ 277 | HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ 278 | HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ 279 | HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ 280 | 281 | /* Round 4 */ 282 | II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ 283 | II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ 284 | II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ 285 | II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ 286 | II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ 287 | II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ 288 | II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ 289 | II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ 290 | II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ 291 | II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ 292 | II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ 293 | II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ 294 | II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ 295 | II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ 296 | II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ 297 | II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ 298 | 299 | state[0] += a; 300 | state[1] += b; 301 | state[2] += c; 302 | state[3] += d; 303 | 304 | // Zeroize sensitive information. 305 | memset(x, 0, sizeof x); 306 | } 307 | 308 | ////////////////////////////// 309 | 310 | // MD5 block update operation. Continues an MD5 message-digest 311 | // operation, processing another message block 312 | void MD5::update(const unsigned char input[], size_type length) 313 | { 314 | // compute number of bytes mod 64 315 | size_type index = count[0] / 8 % blocksize; 316 | 317 | // Update number of bits 318 | if ((count[0] += (length << 3)) < (length << 3)) 319 | count[1]++; 320 | count[1] += (length >> 29); 321 | 322 | // number of bytes we need to fill in buffer 323 | size_type firstpart = 64 - index; 324 | 325 | size_type i; 326 | 327 | // transform as many times as possible. 328 | if (length >= firstpart) 329 | { 330 | // fill buffer first, transform 331 | memcpy(&buffer[index], input, firstpart); 332 | transform(buffer); 333 | 334 | // transform chunks of blocksize (64 bytes) 335 | for (i = firstpart; i + blocksize <= length; i += blocksize) 336 | transform(&input[i]); 337 | 338 | index = 0; 339 | } 340 | else 341 | i = 0; 342 | 343 | // buffer remaining input 344 | memcpy(&buffer[index], &input[i], length-i); 345 | } 346 | 347 | ////////////////////////////// 348 | 349 | // for convenience provide a verson with signed char 350 | void MD5::update(const char input[], size_type length) 351 | { 352 | update((const unsigned char*)input, length); 353 | } 354 | 355 | ////////////////////////////// 356 | 357 | // MD5 finalization. Ends an MD5 message-digest operation, writing the 358 | // the message digest and zeroizing the context. 359 | MD5& MD5::finalize() 360 | { 361 | static unsigned char padding[64] = { 362 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 363 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 364 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 365 | }; 366 | 367 | if (!finalized) { 368 | // Save number of bits 369 | unsigned char bits[8]; 370 | encode(bits, count, 8); 371 | 372 | // pad out to 56 mod 64. 373 | size_type index = count[0] / 8 % 64; 374 | size_type padLen = (index < 56) ? (56 - index) : (120 - index); 375 | update(padding, padLen); 376 | 377 | // Append length (before padding) 378 | update(bits, 8); 379 | 380 | // Store state in digest 381 | encode(digest, state, 16); 382 | 383 | // Zeroize sensitive information. 384 | memset(buffer, 0, sizeof buffer); 385 | memset(count, 0, sizeof count); 386 | 387 | finalized=true; 388 | } 389 | 390 | return *this; 391 | } 392 | 393 | ////////////////////////////// 394 | 395 | // return hex representation of digest as string 396 | std::string MD5::hexdigest() const 397 | { 398 | if (!finalized) 399 | return ""; 400 | 401 | char buf[33]; 402 | for (int i=0; i<16; i++) 403 | sprintf(buf+i*2, "%02x", digest[i]); 404 | buf[32]=0; 405 | 406 | return std::string(buf); 407 | } 408 | 409 | ////////////////////////////// 410 | 411 | std::ostream& operator<<(std::ostream& out, MD5 md5) 412 | { 413 | return out << md5.hexdigest(); 414 | } 415 | 416 | ////////////////////////////// 417 | 418 | std::string md5(const std::string str) 419 | { 420 | MD5 md5 = MD5(str); 421 | 422 | return md5.hexdigest(); 423 | } 424 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #pragma GCC optimize("O3,unroll-loops") 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "network_simplex.hpp" 16 | #include "utils.hpp" 17 | #include "md5.hpp" 18 | 19 | using namespace std; 20 | using ll = long long; 21 | 22 | static class Stats { 23 | public: 24 | int totalRealItems = 0, tradedItems = 0; 25 | ll sumSquares = 0, trackedMetric = 0; 26 | size_t formattingWidth = 0; 27 | 28 | string commandLine, inputChecksum, resultsChecksum; 29 | vector options, customOutput; 30 | struct { int optimizedRealitems, deletedOrphans; } shrinked; 31 | 32 | utils::timer Timer; 33 | chrono::time_point startTime; 34 | const string version = "0.4"; 35 | } Metadata; 36 | 37 | static class Config { 38 | public: 39 | enum METRIC_TYPE { USERS_TRADING = 0 } METRIC; 40 | bool ALLOW_DUMMIES , REQUIRE_COLONS, REQUIRE_USERNAMES, REQUIRE_OFFICIAL_NAMES, 41 | SHOW_MISSING, SHOW_WANTS, SHOW_ELAPSED_TIME, 42 | HIDE_LOOPS, HIDE_SUMMARY, HIDE_NONTRADES, HIDE_ERRORS, HIDE_REPEATS, HIDE_STATS, 43 | SORT_BY_ITEM, CASE_SENSITIVE, VERBOSE; 44 | 45 | ll SMALL_STEP = 0, BIG_STEP = 0, ITERATIONS = 1, SEED, NONTRADE_COST = 1e12, SHRINK = 0; 46 | } Settings; 47 | 48 | // Real items and dummies are uniquely mapped to an integer from 0 to N, being N the total number. 49 | // When separating into "Sender" and "Receiver" nodes, an index I in [0,N) represents the Ith item's "Sender", 50 | // and index I + N represent the Ith item's "Receiver". So you can add or substract N to match some item index to its pair. 51 | struct Specimen { 52 | int index; 53 | string tag, username; 54 | vector wishlist; 55 | bool dummy; 56 | 57 | string show() const { 58 | if(Settings.SORT_BY_ITEM) return this->tag + " " + "(" + this->username + ")"; 59 | else return "(" + this->username + ")" + " " + this->tag; 60 | } 61 | }; 62 | 63 | 64 | unordered_map Items; // Maps tags to the corresponding item 65 | unordered_map Tags; // Maps indices to tags, basically to represent a "bidirectional map" 66 | 67 | // See Chris Okasaki explanation: https://boardgamegeek.com/thread/1601921/math-trade-theory-classifying-edges 68 | // sccShrinkOptimization() implements Kosaraju to find each SCC (strongly conected component) and then 69 | // erase every outgoing edge from this component to a different one (particularly, not the same component). 70 | // After that it also erases nodes that have been left without edges, "orphans". 71 | void sccShrinkOptimization(){ 72 | vector> adj(Items.size()), adj_rev(Items.size()); 73 | for(const auto& [key, s] : Items){ 74 | for(auto w : s.wishlist){ 75 | adj[s.index].push_back(w); 76 | adj_rev[w].push_back(s.index); 77 | } 78 | } 79 | vector order; 80 | set component; 81 | vector used; 82 | function dfs1 = [&](int v){ used[v] = true; for(auto& u : adj[v]) if(not used[u]) dfs1(u); order.push_back(v); }; 83 | function dfs2 = [&](int v){ used[v] = true; component.insert(v); for(auto& u : adj_rev[v]) if(not used[u]) dfs2(u); }; 84 | used.assign(Items.size(), false); 85 | for(int i = 0; i < Items.size(); i++) if(not used[i]) dfs1(i); 86 | used.assign(Items.size(), false); 87 | reverse(order.begin(), order.end()); 88 | 89 | int deletedRealItemsCount = 0, optimizedEdges = 0; 90 | for (auto v : order) if (not used[v]) { 91 | dfs2(v); 92 | 93 | for(auto x : component){ 94 | if(component.size() == 1){ 95 | assert(Tags[x] != ""); 96 | deletedRealItemsCount += Tags[x][0] != '%'; 97 | Items.erase(Tags[x]); 98 | } else { 99 | Specimen& s = Items[Tags[x]]; 100 | 101 | vector temp; 102 | for(auto w : s.wishlist){ 103 | if(component.count(w)){ // Only keep edges within the SCC 104 | temp.push_back(w); 105 | } else { 106 | optimizedEdges++; 107 | } 108 | } 109 | s.wishlist = temp; 110 | } 111 | 112 | } 113 | component.clear(); 114 | } 115 | 116 | set deletedOrphans; 117 | for(auto it = Items.begin(); it != Items.end(); ){ // Delete oprhans (nodes left without outgoing edges) 118 | if((it->second).wishlist.empty()){ 119 | deletedOrphans.insert((it->second).index); 120 | it = Items.erase(it); 121 | } 122 | else it++; 123 | } 124 | 125 | Metadata.shrinked.optimizedRealitems = Metadata.totalRealItems - deletedRealItemsCount; 126 | Metadata.shrinked.deletedOrphans = deletedOrphans.size(); 127 | 128 | // Re-index nodes 129 | int total = 0; 130 | unordered_map mapping; 131 | for(auto &[tag, item] : Items){ 132 | if(not mapping.count(item.index)){ 133 | mapping[item.index] = mapping.size(); 134 | 135 | for(auto &w : item.wishlist){ 136 | if(not mapping.count(w) and not deletedOrphans.count(w)){ 137 | mapping[w] = mapping.size(); 138 | } 139 | } 140 | } 141 | } 142 | 143 | unordered_map newTags; 144 | for(auto &[tag, item] : Items){ 145 | item.index = mapping[item.index]; 146 | newTags[item.index] = tag; 147 | 148 | vector temp; 149 | for(auto &w : item.wishlist){ 150 | if(mapping.count(w)){ 151 | assert(mapping[w] < Items.size()); 152 | temp.push_back(mapping[w]); 153 | } 154 | } 155 | item.wishlist = temp; 156 | } 157 | Tags = newTags; 158 | } 159 | 160 | 161 | // solve() runs the whole sauce of solving the math trade. 162 | vector> bestGroups; 163 | unordered_map favoredCosts; // A cost reduction for nodes' outgoing edges to favor non-trading users 164 | unordered_map nontradedUserCount; 165 | unordered_map userItemCount; 166 | bool solve(int iteration){ 167 | network_simplex ns(2 * Items.size()); 168 | 169 | // Simplex supply / demand 170 | for (int v = 0; v < Items.size(); v++){ 171 | ns.add_supply(v, 1); 172 | ns.add_supply(v + Items.size(), -1); 173 | } 174 | 175 | vector> Edges; 176 | 177 | for(const auto &[tag, s] : Items){ // Build edges from wishlists, write over copies, not concurrent data 178 | for(const auto &sendTo : s.wishlist){ 179 | assert(s.index != sendTo); 180 | Edges.push_back({s.index, sendTo + Items.size()}); 181 | 182 | ll cost; 183 | if(s.dummy) cost = Settings.NONTRADE_COST; 184 | else cost = favoredCosts.count(s.index) ? 1 - favoredCosts[s.index] : 1; 185 | 186 | ns.add(s.index, sendTo + Items.size(), 0, 1, cost); 187 | } 188 | } 189 | 190 | for (int v = 0; v < Items.size(); v++){ // Self-matching loop idea 191 | Edges.push_back({v, v + Items.size()}); 192 | ns.add(v, v + Items.size(), 0, 1, Settings.NONTRADE_COST); 193 | } 194 | 195 | if (ns.mincost_circulation() == 0) { // Run simplex 196 | cout << "Ill-formed graph -- Input error / Critical bug\n"; 197 | assert(false); 198 | } 199 | 200 | map solution; // Solution in the abstracted space of Senders and Receivers 201 | for (int e = 0; e < Edges.size(); e++) { 202 | if(ns.get_flow(e)){ 203 | assert(solution.count(Edges[e].first) == 0); 204 | solution[Edges[e].first] = Edges[e].second; 205 | assert(Tags[Edges[e].first].size() > 0); 206 | } 207 | } 208 | 209 | map clean; // Cleaned up solution with indices back to [0,N) 210 | for(auto [key, val] : solution){ 211 | assert(Tags.count(key) > 0); 212 | if(not Items[Tags[key]].dummy){ // Actual item to be sent 213 | int trueVal = val; 214 | 215 | while(Items[Tags[trueVal - Items.size()]].dummy){ // Skips trades between dummies 216 | trueVal = solution[trueVal - Items.size()]; 217 | } 218 | 219 | assert(clean.count(key) == 0); 220 | if(key != trueVal - Items.size()){ // Self loop implies a non traded item 221 | clean[key] = trueVal - Items.size(); 222 | } 223 | } 224 | } 225 | 226 | vector> groups; // Trading chains, or "groups" 227 | map visit; 228 | for(auto [key, val] : clean){ 229 | if(not visit[key]){ 230 | visit[key] = true; 231 | groups.push_back({key}); 232 | int u = clean[key]; 233 | while(u != key){ 234 | visit[u] = true; 235 | groups.back().push_back(u); 236 | u = clean[u]; 237 | } 238 | } 239 | } 240 | 241 | unordered_set TradingUsers; 242 | for(const auto &g : groups){ 243 | for(const auto &e : g){ 244 | const Specimen& _left = Items[Tags[e]]; 245 | TradingUsers.insert(_left.username); 246 | } 247 | } 248 | 249 | bool improvedSolution = false; 250 | set FavoringRound; 251 | 252 | if(Settings.METRIC == Config::USERS_TRADING){ 253 | cerr << "iteration #" << iteration << " found " << TradingUsers.size() << " users trading." << endl; 254 | if(Metadata.trackedMetric < TradingUsers.size()){ 255 | Metadata.trackedMetric = TradingUsers.size(); 256 | bestGroups = groups; 257 | } 258 | 259 | for(const auto &[key, s] : Items) { 260 | if(s.dummy) continue; 261 | if(TradingUsers.count(s.username)) continue; 262 | if(FavoringRound.count(s.username)) continue; 263 | if(userItemCount[s.username] == nontradedUserCount[s.username]) continue; 264 | 265 | improvedSolution = true; 266 | if(not favoredCosts.count(s.index)){ 267 | FavoringRound.insert(s.username); 268 | favoredCosts[s.index] = 1; 269 | nontradedUserCount[s.username]++; 270 | } 271 | } 272 | 273 | } 274 | 275 | return improvedSolution; 276 | } 277 | 278 | void formatOutput(ostream& out){ 279 | out << "FastTradeMaximizer Version " << Metadata.version << '\n'; 280 | time_t startTimeT = chrono::system_clock::to_time_t(Metadata.startTime); 281 | out << "... run started: " << ctime(&startTimeT); 282 | out << "... command line: " << Metadata.commandLine << '\n'; 283 | for(const auto& cO : Metadata.customOutput) cout << cO << '\n'; 284 | out << "Options: "; for(const auto &o : Metadata.options) out << o << ' '; 285 | out << "\n\n"; 286 | out << "Input Checksum: " << Metadata.inputChecksum << '\n'; 287 | out << "Weeded down number of items: " << Metadata.shrinked.optimizedRealitems << " (" << Metadata.shrinked.deletedOrphans << " orphans)"; 288 | out << "\n\n"; 289 | out << "TRADE LOOPS (" << Metadata.tradedItems << " total trades):\n\n"; 290 | 291 | sort(bestGroups.begin(), bestGroups.end(), [](const vector& a, const vector& b){ return a.size() > b.size(); }); // Format in group-size decreasing order 292 | 293 | Metadata.resultsChecksum = md5(""); 294 | vector itemSummary; 295 | for(const auto &g : bestGroups){ 296 | for(int i = 0; i < g.size(); i++){ 297 | // Generate trade loops 298 | const Specimen& current = Items[Tags[g[i]]]; 299 | const Specimen& sendTo = Items[Tags[g[(i+1)%g.size()]]]; 300 | const Specimen& receiveFrom = Items[Tags[g[(i-1+g.size())%g.size()]]]; 301 | 302 | Metadata.resultsChecksum = md5(Metadata.resultsChecksum + sendTo.show() + current.show()); 303 | out << std::left << setfill(' ') << setw(Metadata.formattingWidth) << sendTo.show() << " receives " << current.show() << '\n'; 304 | 305 | // Prepare item summaries 306 | stringstream buffer; 307 | buffer << std::left << setfill(' ') << setw(Metadata.formattingWidth) << current.show() << " receives " 308 | << std::left << setfill(' ') << setw(Metadata.formattingWidth) << receiveFrom.show() << "and sends to " 309 | << sendTo.show(); 310 | itemSummary.push_back(buffer.str()); 311 | } 312 | out << '\n'; 313 | } 314 | 315 | out << "ITEM SUMMARY (" << Metadata.tradedItems << " total trades):\n\n"; 316 | sort(itemSummary.begin(), itemSummary.end()); 317 | for(const auto &s : itemSummary) out << s << '\n'; 318 | 319 | out << "\n\n"; 320 | out << "Results Checksum: " << Metadata.resultsChecksum << "\n\n"; 321 | out << "Num trades = " << Metadata.tradedItems << " of " << Metadata.totalRealItems << " items (" << Metadata.tradedItems * 100.0 / Metadata.totalRealItems * 1.0 << "%)\n"; 322 | out << "Best metric = " << Metadata.trackedMetric << '\n'; 323 | out << "Total cost = " << Metadata.tradedItems << " (avg 1.00)\n"; // There is no priority implemented 324 | out << "Num groups = " << bestGroups.size() << '\n'; 325 | out << "Group sizes = "; for(const auto& g : bestGroups) out << g.size() << ' '; out << '\n'; 326 | out << "Sum squares = " << Metadata.sumSquares << '\n'; 327 | if(Settings.SHOW_ELAPSED_TIME) out << "Elapsed time = " << Metadata.Timer.elapsed_time() << "ms" << '\n'; 328 | } 329 | 330 | int main(int argc, char** argv) { 331 | Metadata.startTime = chrono::system_clock::now(); 332 | Metadata.commandLine = argv[0]; 333 | 334 | string line; 335 | while (getline(cin, line)) { 336 | if(line.size() == 0) continue; 337 | if(line[0] == '#'){ 338 | if(line[1] == '+'){ Metadata.customOutput.push_back(line); continue; } // Custom output 339 | if(line[1] != '!') continue; // Comment 340 | 341 | // Option 342 | Metadata.inputChecksum = md5(Metadata.inputChecksum + line); 343 | istringstream iss(line); 344 | string option; 345 | iss >> option; // Discard initial "#!" stream tokens 346 | 347 | while(iss >> option){ 348 | bool validOption = true; 349 | 350 | if(option == "ALLOW-DUMMIES") Settings.ALLOW_DUMMIES = true; 351 | else if(option == "REQUIRE-COLONS") Settings.REQUIRE_COLONS = true; 352 | else if(option == "REQUIRE-USERNAMES") Settings.REQUIRE_USERNAMES = true; 353 | else if(option == "REQUIRE-OFFICIAL-NAMES") Settings.REQUIRE_OFFICIAL_NAMES = true; 354 | else if(option == "SHOW-MISSING") Settings.SHOW_MISSING = true; 355 | else if(option == "SHOW-WANTS") Settings.SHOW_WANTS = true; 356 | else if(option == "SHOW-ELAPSED-TIME") Settings.SHOW_ELAPSED_TIME = true; 357 | else if(option == "HIDE-LOOPS") Settings.HIDE_LOOPS = true; 358 | else if(option == "HIDE-SUMMARY") Settings.HIDE_SUMMARY = true; 359 | else if(option == "HIDE-NONTRADES") Settings.HIDE_NONTRADES = true; 360 | else if(option == "HIDE-ERRORS") Settings.HIDE_ERRORS = true; 361 | else if(option == "HIDE-REPEATS") Settings.HIDE_REPEATS = true; 362 | else if(option == "HIDE-STATS") Settings.HIDE_STATS = true; 363 | else if(option == "SORT-BY_ITEM") Settings.SORT_BY_ITEM = true; 364 | else if(option == "CASE-SENSITIVE") Settings.CASE_SENSITIVE = true; 365 | else if(option == "VERBOSE") Settings.VERBOSE = true; 366 | else if(option.find('=') != string::npos and 0 < option.find('=') and option.find('=') < option.size() - 1){ 367 | // Must be "Option=Value" 368 | string name = option.substr(0, option.find('=')); 369 | string value = option.substr(option.find('=') + 1); 370 | 371 | if(name == "SMALL-STEP") Settings.SMALL_STEP = stoll(value); 372 | else if(name == "BIG-STEP") Settings.BIG_STEP = stoll(value); 373 | else if(name == "ITERATIONS") Settings.ITERATIONS = stoll(value); 374 | else if(name == "SEED") Settings.SEED = stoll(value); 375 | else if(name == "SHRINK") Settings.SHRINK = stoll(value); 376 | else if(name == "METRIC") Settings.METRIC = Config::USERS_TRADING; // There is no other METRIC 377 | else validOption = false; 378 | } else validOption = false; 379 | 380 | if(not validOption) { 381 | cout << "Unknown option \"" << option << "\".\n"; 382 | assert(false); 383 | } 384 | 385 | Metadata.inputChecksum = md5(Metadata.inputChecksum + option); 386 | Metadata.options.push_back(option); 387 | } 388 | } 389 | else if(line == "!BEGIN-OFFICIAL-NAMES"){ 390 | while (getline(cin, line) and line != "!END-OFFICIAL-NAMES") { 391 | Metadata.inputChecksum = md5(Metadata.inputChecksum + line); 392 | istringstream iss(line); 393 | string tag; 394 | iss >> tag; if(not Settings.CASE_SENSITIVE) utils::up(tag); 395 | 396 | assert(Items.count(tag) == 0); // Repeated tag in official names 397 | 398 | int elems = Items.size(); 399 | Items[tag] = Specimen{.index = elems, .tag = tag, .dummy = false}; 400 | Tags[elems] = tag; 401 | Metadata.totalRealItems++; 402 | } 403 | } else { 404 | // Wishlists 405 | Metadata.inputChecksum = md5(Metadata.inputChecksum + line); 406 | assert(line[0] == '('); // Garbage line 407 | 408 | if(Settings.REQUIRE_OFFICIAL_NAMES) assert(Items.size() > 0); // Cannot wishlist without listing official names 409 | istringstream iss(line); 410 | string temp, username; 411 | 412 | getline(iss, temp, '('); 413 | getline(iss, username, ')'); 414 | assert(temp.size() == 0 and username.size() > 0); 415 | 416 | string tag; 417 | iss >> tag; 418 | 419 | if(Settings.REQUIRE_COLONS){ 420 | if(tag.back() == ':'){ 421 | tag.pop_back(); 422 | } else { 423 | getline(iss, temp, ':'); 424 | /* beautify */ 425 | } 426 | } 427 | 428 | if(not Settings.CASE_SENSITIVE){ utils::up(tag); utils::up(username); } 429 | if(tag[0] == '%') tag += username; 430 | if(not Items.count(tag)){ 431 | if(Settings.REQUIRE_OFFICIAL_NAMES) assert(tag[0] == '%'); // Must be a dummy not seen before 432 | int elems = Items.size(); 433 | Items[tag] = Specimen{.index = elems, .tag = tag, .username = username, .dummy = tag[0] == '%'}; 434 | Tags[elems] = tag; 435 | Metadata.totalRealItems += tag[0] != '%'; 436 | } 437 | 438 | if(Items[tag].username.empty()){ 439 | Items[tag].username = username; 440 | } 441 | else assert (Items[tag].username == username); 442 | 443 | while(iss >> temp){ 444 | if(not Settings.CASE_SENSITIVE) utils::up(temp); 445 | if(temp[0] == '%') temp += username; 446 | if(not Items.count(temp)){ 447 | if(Settings.REQUIRE_OFFICIAL_NAMES) assert(temp[0] == '%'); // Must be a dummy not seen before 448 | int elems = Items.size(); 449 | Items[temp] = Specimen{.index = elems, .tag = temp, .dummy = temp[0] == '%'}; 450 | Tags[elems] = temp; 451 | Metadata.totalRealItems += temp[0] != '%'; 452 | } 453 | 454 | Items[temp].wishlist.push_back(Items[tag].index); 455 | } 456 | } 457 | } 458 | 459 | sccShrinkOptimization(); 460 | 461 | for(const auto &[key, s] : Items){ 462 | if(not s.dummy) userItemCount[s.username]++; 463 | } 464 | 465 | for(int i = 0; solve(i); i++); 466 | 467 | // Prepare metadata 468 | for(const auto &v : bestGroups){ 469 | Metadata.tradedItems += v.size(); 470 | Metadata.sumSquares += v.size() * v.size(); 471 | for(const auto &e : v){ 472 | const string& tag = Tags[e]; 473 | Metadata.formattingWidth = max(Metadata.formattingWidth, utils::utf8_length(Items[tag].show()) + 1); 474 | } 475 | } 476 | 477 | // Output result 478 | formatOutput(cout); 479 | 480 | return 0; 481 | } 482 | --------------------------------------------------------------------------------