1 /** 2 Some additional alogorithm functions. 3 4 Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de> 5 License: Subject to the terms of the MIT license, as written in the 6 included LICENSE file. 7 Authors: Arne Ludwig <arne.ludwig@posteo.de> 8 */ 9 module dalicious.algorithm.comparison; 10 11 import std.algorithm : 12 copy, 13 countUntil, 14 min, 15 OpenRight, 16 swapAt, 17 uniq; 18 import std.conv : to; 19 import std.functional : binaryFun, unaryFun; 20 import std.traits : 21 CommonType, 22 isCallable, 23 isDynamicArray, 24 ReturnType, 25 rvalueOf; 26 import std.typecons : Flag, No, Yes; 27 import std.range : enumerate; 28 import std.range.primitives; 29 30 31 /** 32 Order `a` and `b` lexicographically by applying each `fun` to them. For 33 unary functions compares `fun(a) < fun(b)`. 34 */ 35 template orderLexicographically(fun...) 36 { 37 bool _orderLexicographically(T)(T a, T b) 38 { 39 static foreach (i, getFieldValue; fun) 40 {{ 41 auto aValue = unaryFun!getFieldValue(a); 42 auto bValue = unaryFun!getFieldValue(b); 43 44 if (aValue != bValue) 45 return aValue < bValue; 46 }} 47 48 return false; 49 } 50 51 static if (is(fun[0])) 52 alias orderLexicographically = _orderLexicographically!(fun[0]); 53 else 54 alias orderLexicographically = _orderLexicographically; 55 } 56 57 58 /** 59 Compare `a` and `b` lexicographically by applying each `fun` to them. For 60 unary functions compares `fun(a) < fun(b)`. 61 */ 62 int cmpLexicographically(T, fun...)(T a, T b) 63 { 64 static foreach (i, alias getFieldValue; fun) 65 { 66 { 67 auto aValue = unaryFun!getFieldValue(a); 68 auto bValue = unaryFun!getFieldValue(b); 69 70 if (aValue < bValue) 71 { 72 return -1; 73 } 74 else if (aValue > bValue) 75 { 76 return 1; 77 } 78 } 79 } 80 81 return 0; 82 } 83 84 85 /// Returns one of a collection of expressions based on the value of the 86 /// switch expression. 87 template staticPredSwitch(T...) 88 { 89 auto staticPredSwitch(E)(E switchExpression) pure nothrow 90 { 91 static assert (T.length > 0, "missing choices"); 92 enum hasDefaultClause = T.length % 2 == 1; 93 94 static foreach (i; 0 .. T.length - 1) 95 { 96 static if (i % 2 == 0) 97 { 98 if (switchExpression == T[i]) 99 { 100 static if (isCallable!(T[i + 1])) 101 return T[i + 1](); 102 else 103 return T[i + 1]; 104 } 105 } 106 } 107 108 static if (hasDefaultClause) 109 { 110 static if (isCallable!(T[$ - 1])) 111 { 112 static if (is(ReturnType!(T[$ - 1]) == void)) 113 { 114 T[$ - 1](); 115 assert(0); 116 } 117 else 118 return T[$ - 1](); 119 } 120 else 121 { 122 return T[$ - 1]; 123 } 124 } 125 else 126 { 127 assert(0, "none of the clauses matched in " ~ __FUNCTION__); 128 } 129 } 130 } 131 132 /// 133 unittest 134 { 135 alias numberName = staticPredSwitch!( 136 1, "one", 137 2, "two", 138 3, "three", 139 "many", 140 ); 141 142 static assert("one" == numberName(1)); 143 static assert("two" == numberName(2)); 144 static assert("three" == numberName(3)); 145 static assert("many" == numberName(4)); 146 } 147 148 149 /** 150 Checks if both ranges are permutations of each other. 151 152 pred must be an equivalence relation, e.i. for all inputs: 153 154 // reflexive 155 pred(a, a) == true 156 // symmetric 157 pred(a, b) == pred(b, a) 158 // transitive 159 !(pred(a, b) && pred(b, c)) || pred(a, c) 160 161 Params: 162 pred = an optional parameter to change how equality is defined 163 r1 = A finite input range 164 r2 = A finite random access range 165 index = An index into r2 such that r2 permuted by index is a prefix of r1. 166 Returns: 167 `true` if all of the elements in `r1` appear the same number of times in `r2`. 168 Otherwise, returns `false`. 169 */ 170 bool isPermutation(alias pred = "a == b", Flag!"nearlySorted" nearlySorted = Yes.nearlySorted, R1, R2)(R1 r1, R2 r2) 171 if ( 172 isInputRange!R1 && !isInfinite!R1 && 173 isRandomAccessRange!R2 && !isInfinite!R2 && 174 is(CommonType!(ElementType!R1, ElementType!R2)) 175 ) 176 { 177 static if (hasLength!R1) 178 if (r1.length != r2.length) 179 return false; 180 181 auto index = new size_t[r2.length]; 182 183 return isPermutation!(pred, nearlySorted)(r1, r2, index); 184 } 185 186 /// ditto 187 bool isPermutation(alias pred = "a == b", Flag!"nearlySorted" nearlySorted = Yes.nearlySorted, R1, R2)(R1 r1, R2 r2, size_t[] index) 188 if ( 189 isInputRange!R1 && !isInfinite!R1 && 190 isRandomAccessRange!R2 && !isInfinite!R2 && 191 is(CommonType!(ElementType!R1, ElementType!R2)) 192 ) 193 in (r2.length <= index.length, "index is too small") 194 { 195 static if (hasLength!R1) 196 if (r1.length != r2.length) 197 return false; 198 199 return isPermutation!(pred, nearlySorted)(r1, r2, index); 200 } 201 202 /// ditto 203 bool isPermutation(alias pred = "a == b", Flag!"nearlySorted" nearlySorted = Yes.nearlySorted, R1, R2)(R1 r1, R2 r2, ref size_t[] index) 204 if ( 205 isInputRange!R1 && !isInfinite!R1 && 206 isRandomAccessRange!R2 && !isInfinite!R2 && 207 is(CommonType!(ElementType!R1, ElementType!R2)) 208 ) 209 in (r2.length <= index.length, "index is too small") 210 { 211 static if (hasLength!R1) 212 if (r1.length != r2.length) 213 { 214 index = []; 215 return false; 216 } 217 218 alias _pred = binaryFun!pred; 219 const length2 = r2.length; 220 index = index[0 .. length2]; 221 222 foreach (idx, ref permIndex; index) 223 permIndex = idx; 224 225 // determine longest common prefix 226 size_t prefixLength = longestCommonPrefix!_pred(r1, r2[]); 227 228 // determine permutation for the rest 229 size_t i = prefixLength; 230 foreach (e1; r1) 231 { 232 auto j = i; 233 234 while (j < length2 && !_pred(e1, r2[index[j]])) 235 ++j; 236 assert(j == length2 || _pred(e1, r2[index[j]])); 237 238 if (j == length2) 239 { 240 index = index[0 .. i]; 241 return false; 242 } 243 else 244 { 245 static if (nearlySorted) 246 { 247 if (j > i) 248 { 249 auto tmp = index[j]; 250 // shift index[i .. j] by one index 251 foreach_reverse (k; i .. j) 252 index[k + 1] = index[k]; 253 index[i] = tmp; 254 } 255 } 256 else 257 { 258 index.swapAt(i, j); 259 } 260 } 261 262 ++i; 263 } 264 265 return true; 266 } 267 268 /// 269 unittest 270 { 271 enum r1 = [1, 2, 3, 4, 5, 6]; 272 enum r2 = [1, 3, 2, 4, 5, 6]; 273 274 assert(isPermutation(r1, r2)); 275 276 auto index = new size_t[r2.length]; 277 assert(isPermutation(r1, r2, index)); 278 assert(index == [0, 2, 1, 3, 4, 5]); 279 } 280 281 unittest 282 { 283 import std.array : array; 284 import std.random; 285 import std.range : iota; 286 287 debug (exhaustiveUnittests) 288 { 289 enum size_t n = 1024; 290 enum size_t m = 4096; 291 } 292 else 293 { 294 enum size_t n = 64; 295 enum size_t m = 32; 296 } 297 const size_t[] sorted = iota(n).array; 298 size_t[] randomlyShuffled = iota(n).array; 299 auto index = new size_t[n]; 300 301 foreach (_; 0 .. m) 302 { 303 randomShuffle(randomlyShuffled); 304 305 assert(isPermutation!"a == b"(sorted, randomlyShuffled, index)); 306 } 307 } 308 309 debug (benchmark) unittest 310 { 311 import std.algorithm : equal; 312 import std.array : array; 313 import std.datetime.stopwatch; 314 import std.random; 315 import std.range : iota; 316 import std.stdio : writefln; 317 318 enum size_t n = 64; 319 const size_t[] sorted = iota(n).array; 320 const size_t[] shortSwap = sorted[0 .. n/2] ~ [sorted[n/2 + 1], sorted[n/2]] ~ sorted[n/2 + 2 .. $]; 321 const size_t[] longSwap = sorted[1 .. $] ~ sorted[0 .. 1]; 322 const size_t[] randomlyShuffled = iota(n).array.randomShuffle; 323 auto index = new size_t[n]; 324 alias eq = (size_t a, size_t b) => a == b; 325 auto res = benchmark!( 326 { cast(void) equal!eq(sorted, sorted); }, 327 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, sorted); }, 328 { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, sorted); }, 329 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, sorted, index); }, 330 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, shortSwap[]); }, 331 { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, shortSwap[]); }, 332 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, shortSwap[], index); }, 333 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, longSwap[]); }, 334 { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, longSwap[]); }, 335 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, longSwap[], index); }, 336 { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, randomlyShuffled[]); }, 337 { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, randomlyShuffled[]); }, 338 { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, randomlyShuffled[], index); }, 339 )(10_000); 340 341 writefln!"-- isPermutation!(eq, nearlySorted)(sorted, r2, index):"(); 342 writefln!" equal(sorted, sorted): %s"(res[0]); 343 writefln!" r2 index nearlySorted took"(); 344 writefln!" sorted no yes %s"(res[1]); 345 writefln!" sorted no no %s"(res[2]); 346 writefln!" sorted yes yes %s"(res[3]); 347 writefln!" shortSwap no yes %s"(res[4]); 348 writefln!" shortSwap no no %s"(res[5]); 349 writefln!" shortSwap yes yes %s"(res[6]); 350 writefln!" longSwap no yes %s"(res[7]); 351 writefln!" longSwap no no %s"(res[8]); 352 writefln!" longSwap yes yes %s"(res[9]); 353 writefln!" randomlyShuffled no yes %s"(res[10]); 354 writefln!" randomlyShuffled no no %s"(res[11]); 355 writefln!" randomlyShuffled yes no %s"(res[12]); 356 } 357 358 359 private size_t longestCommonPrefix(alias pred, R1, R2)(ref R1 r1, R2 r2) 360 { 361 size_t prefixLength; 362 363 static if (isRandomAccessRange!R1 && hasSlicing!R1) 364 { 365 const length = min(r1.length, r2.length); 366 while (prefixLength < length && pred(r1[prefixLength], r2[prefixLength])) 367 ++prefixLength; 368 369 r1 = r1[prefixLength .. $]; 370 } 371 else 372 { 373 while (!(r1.empty || r2.empty) && pred(r1.front, r2.front)) 374 { 375 r1.popFront(); 376 r2.popFront(); 377 ++prefixLength; 378 } 379 } 380 381 return prefixLength; 382 } 383 384 debug (benchmark) unittest 385 { 386 import std.array : array; 387 import std.datetime.stopwatch; 388 import std.random; 389 import std.range : iota; 390 import std.stdio : writefln; 391 392 struct ForwardIota 393 { 394 size_t n; 395 size_t i; 396 397 void popFront() pure nothrow @safe @nogc 398 { 399 assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof); 400 401 ++i; 402 } 403 404 405 @property auto front() pure nothrow @safe @nogc 406 { 407 assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof); 408 409 return i; 410 } 411 412 413 @property bool empty() const pure nothrow @safe @nogc 414 { 415 return i >= n; 416 } 417 418 419 @property ForwardIota save() const pure nothrow @safe @nogc 420 { 421 return ForwardIota(n, i); 422 } 423 } 424 425 enum size_t n = 64; 426 const size_t[] sortedRA = iota(n).array; 427 auto sortedFR = ForwardIota(n); 428 const size_t[] permuted = iota(n).array.randomShuffle; 429 alias eq = (size_t a, size_t b) => a == b; 430 auto res = benchmark!( 431 { 432 auto r1 = sortedRA[]; 433 cast(void) longestCommonPrefix!eq(r1, permuted[]); 434 }, 435 { 436 auto r1 = sortedFR.save; 437 cast(void) longestCommonPrefix!eq(r1, permuted[]); 438 }, 439 )(10_000); 440 441 442 writefln!"random-access interface: %s"(res[0]); 443 writefln!" range interface: %s"(res[1]); 444 }