1 module tupleops; 2 3 import std.stdio; 4 import std.typecons; 5 6 template LongestOverload(alias func) 7 { 8 private import std.traits : Parameters; 9 10 struct S 11 { 12 alias f = func; 13 } 14 15 enum maxIndex = { 16 size_t maxIdx, maxLen; 17 foreach (i, _f; __traits(getOverloads, S, "f")) 18 { 19 auto plen = Parameters!(_f).length; 20 if (plen > maxLen) 21 { 22 maxIdx = i; 23 maxLen = plen; 24 } 25 } 26 return maxIdx; 27 }(); 28 29 static foreach (i, _f; __traits(getOverloads, S, "f")) 30 { 31 static if (i == maxIndex) 32 { 33 alias func = _f; 34 alias Types = Parameters!_f; 35 } 36 } 37 } 38 39 version (unittest) 40 { 41 void f(int x) {} 42 void f(int x, int y) {} 43 void f(int x, int y, double z) {} 44 } 45 46 unittest 47 { 48 import std.meta : AliasSeq; 49 static assert(is(LongestOverload!f.Types == AliasSeq!(int, int, double))); 50 51 alias g = overload!( 52 (int x) {}, 53 (int x, int y) {}, 54 (int x, int y, double z) {} 55 ); 56 static assert(is(LongestOverload!g.Types == AliasSeq!(int, int, double))); 57 } 58 59 60 private auto _mapImpl(alias func, Ts...)(return auto ref Ts ts) 61 { 62 alias T = typeof(ts[0]); 63 enum isLongest = is(Tuple!(LongestOverload!func.Types) == T); 64 65 static if (isTuple!T) 66 { 67 static if (ts.length == 1) 68 { 69 static if (isLongest) 70 return tuple(func(ts[0].expand)); 71 else 72 return tuple(_mapImpl!func(ts[0].expand)); 73 } 74 else 75 { 76 static if (isLongest) 77 return tuple(func(ts[0].expand), 78 _mapImpl!func(ts[1 .. $]).expand); 79 else 80 return tuple(_mapImpl!func(ts[0].expand), 81 _mapImpl!func(ts[1 .. $]).expand); 82 } 83 } 84 else 85 { 86 static if (ts.length == 1) 87 return tuple(func(ts[0])); 88 else 89 return tuple(func(ts[0]), 90 _mapImpl!func(ts[1 .. $]).expand); 91 } 92 } 93 94 /// simple map over tuples 95 auto map(alias func, Ts...)(return auto ref Ts ts) 96 { 97 return _mapImpl!func(ts)[0]; 98 } 99 100 /// 101 unittest 102 { 103 enum t0 = tuple(1, 2, 3); 104 static assert(map!(x => x)(t0) == t0); 105 enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7)); 106 static assert(map!(x => x)(t) == t); 107 108 assert(map!(x => 2 * x)(t0) == tuple(2, 4, 6)); 109 static assert(map!(x => 2 * x)(t) == 110 tuple(2, 4, tuple(6, tuple(tuple(8, 10), 12), 14))); 111 112 static assert(map!((int x, int y) => x + y)(tuple(1, 2)) == 3); 113 static assert(map!((int x, int y) => x + y)( 114 tuple(tuple(1, 2), 115 tuple(tuple(3, 4), tuple(5, 6)))) 116 == tuple(3, tuple(7, 11))); 117 } 118 119 120 /// equivalent to boost::hana::overload 121 template overload(funcs ...) 122 { 123 static foreach (f; funcs) alias overload = f; 124 } 125 126 /// 127 unittest 128 { 129 import std.conv : to; 130 alias f = overload!( 131 (int i) => i.to!string, 132 (int i, int j) => i + j, 133 (double d) => d * 2); 134 135 static assert(f(1, -2) == -1); 136 static assert(f(1.0) == 2.0); 137 static assert(f(1) == "1"); 138 139 enum t = tuple(1.0, 2, tuple(3.0, tuple(tuple(4, 5.0), 6), 7.0)); 140 static assert(map!f(t) == tuple(2.0, "2", tuple(6.0, tuple(tuple("4", 10.0), "6"), 14.0))); 141 142 // apply longest argument overload 143 enum t1 = tuple(1.0, 2, tuple(3, 4)); 144 static assert(map!f(t1) == tuple(2.0, "2", 7)); 145 146 enum t2 = tuple(1.0, 2, tuple(3.0, tuple(tuple(4, 5), 6), 7.0)); 147 static assert(map!f(t2) == tuple(2.0, "2", tuple(6.0, tuple(9, "6"), 14.0))); 148 } 149 150 151 /// higher order function for mapping via depth-fisrt search 152 auto depthFirstFlatMap(alias func, Ts ...)(return auto ref Ts ts) 153 { 154 static if (isTuple!(typeof(ts[0]))) 155 { 156 static if (ts.length == 1) 157 { 158 return depthFirstFlatMap!func(ts[0].expand); 159 } 160 else 161 { 162 return tuple(depthFirstFlatMap!func(ts[0].expand).expand, 163 depthFirstFlatMap!func(ts[1..$]).expand); 164 } 165 } 166 else 167 { 168 static if (ts.length == 1) 169 { 170 return tuple(func(ts[0])); 171 } 172 else 173 { 174 return tuple(func(ts[0]), 175 depthFirstFlatMap!func(ts[1..$]).expand); 176 } 177 } 178 } 179 180 /// depth-first map iterates by left-to-right search. 181 unittest 182 { 183 enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7)); 184 static assert(depthFirstFlatMap!(x => x)(t) == tuple(1, 2, 3, 4, 5, 6, 7)); 185 } 186 187 auto _foldLeftImpl(alias func, Accumulator, Ts ...)(Accumulator acc, Ts ts) 188 { 189 static if (ts.length == 1) 190 { 191 return func(acc, ts[0]); 192 } 193 else 194 { 195 return _foldLeftImpl!func(func(acc, ts[0]), ts[1 .. $]); 196 } 197 } 198 199 /// simple fold left over tuples 200 auto foldLeft(alias func, Accumulator, Ts ...)(Accumulator acc, Ts ts) 201 { 202 static if (ts.length == 1 && isTuple!(Ts[0])) 203 { 204 return _foldLeftImpl!func(acc, ts[0].expand); 205 } 206 else 207 { 208 return _foldLeftImpl!func(acc, ts); 209 } 210 } 211 212 /// depth-first map iterates by left-to-right search. 213 unittest 214 { 215 import std.conv : to; 216 217 enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7)); 218 static assert(foldLeft!((a, b) => a ~ b.to!string ~ ", ")("", t) == 219 "1, 2, Tuple!(int, Tuple!(Tuple!(int, int), int), int)(3, Tuple!(Tuple!(int, int), int)(Tuple!(int, int)(4, 5), 6), 7), "); 220 } 221 222 /// static map over tuples 223 auto ctMap(alias func, Ts...)(Ts ts) 224 { 225 return foldLeft!( 226 (a, b) 227 { 228 return tuple(a.expand, func!b); 229 })(tuple(), ts); 230 } 231 232 version (unittest) { 233 enum strof(alias x) = typeof(x).stringof; 234 } 235 236 /// 237 unittest 238 { 239 enum a = 1; 240 enum b = "b"; 241 // function should be global 242 // enum strof(alias x) = typeof(x).stringof; 243 static assert(ctMap!strof(a, b) == tuple("int", "string")); 244 } 245 246 /// static filter using template arg 247 auto ctFilter(alias func, Ts ...)(Ts ts) { 248 return foldLeft!( 249 (a, b) 250 { 251 static if (func!(b)) 252 { 253 return tuple(a.expand, b); 254 } 255 else 256 { 257 return a; 258 } 259 })(tuple(), ts); 260 } 261 262 version (unittest) { 263 enum pred(alias b) = isTuple!(typeof(b)); 264 } 265 266 /// static filter example 267 unittest 268 { 269 enum t = tuple(1, 2, tuple(3), tuple(4), 5, tuple(6)); 270 // pred should be global 271 // private enum pred(alias b) = isTuple!(typeof(b)); 272 static assert(t.ctFilter!pred == tuple(tuple(3), tuple(4), tuple(6))); 273 } 274 275 version (unittest) { 276 struct T { 277 // int a; 278 // double b; 279 static void f() {} 280 static int g(int i) { return i; } 281 } 282 283 // alias member(name ...) = __traits(getMember, T, name[0]); 284 import std.traits : isSomeFunction; 285 enum isMemberFunction(alias name) = isSomeFunction!(__traits(getMember, T, name)); 286 } 287 288 // TODO find nice way to handle AliasSeq 289 unittest 290 { 291 import std.meta; 292 enum names = tuple(__traits(allMembers, T)); 293 names.ctMap!isSomeFunction; 294 alias funcs = AliasSeq!(T.f, T.g); 295 // enum memberFunctions = names.ctFilter!isMemberFunction; 296 // memberFunctions.writeln; 297 } 298 299 /// higher order function for reduction from left via depth-fisrt search 300 auto depthFirstFoldLeft(alias func, Accumulator, Ts ...)(Accumulator acc, auto ref Ts ts) 301 { 302 static if (isTuple!(typeof(ts[0]))) 303 { 304 static if (ts.length == 1) 305 { 306 return depthFirstFoldLeft!func(acc, ts[0].expand); 307 } 308 else 309 { 310 return depthFirstFoldLeft!func(depthFirstFoldLeft!func(acc, ts[0].expand), 311 ts[1..$]); 312 } 313 } 314 else 315 { 316 static if (ts.length == 1) 317 { 318 return func(acc, ts[0]); 319 } 320 else 321 { 322 return depthFirstFoldLeft!func(func(acc, ts[0]), ts[1..$]); 323 } 324 } 325 } 326 327 /// depth-first fold left 328 unittest 329 { 330 enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7)); 331 import std.stdio; 332 import std.conv : to; 333 static assert(depthFirstFoldLeft!((a, b) => (a ~ b.to!string))("", t) == "1234567"); 334 // TODO(karita) replace depthFirstFlatMap with depthFirstFoldLeft 335 static assert(depthFirstFoldLeft!((a, b) => tuple(a.expand, b))(tuple(), t) == tuple(1, 2, 3, 4, 5, 6, 7)); 336 } 337 338 339 /// higher order function for mapping via breadth-fisrt search 340 auto breadthFirstFlatMap(alias func, Ts ...)(return auto ref Ts ts) 341 { 342 static if (isTuple!(typeof(ts[0]))) 343 { 344 static if (ts.length == 1) 345 { 346 return breadthFirstFlatMap!func(ts[0].expand); 347 } 348 else 349 { 350 return breadthFirstFlatMap!func(ts[1..$], ts[0].expand); 351 } 352 } 353 else 354 { 355 static if (ts.length == 1) 356 { 357 return tuple(func(ts[0])); 358 } 359 else 360 { 361 return tuple(func(ts[0]), 362 breadthFirstFlatMap!func(ts[1..$]).expand); 363 } 364 } 365 } 366 367 /// breadth-first map iterates by top-to-bottom search. 368 unittest 369 { 370 enum t = tuple(1, 371 tuple( 372 tuple( 373 4, 374 tuple( 375 7)), 376 2), 377 tuple( 378 3, 379 tuple( 380 5, 381 6))); 382 static assert(breadthFirstFlatMap!(x => x)(t) == tuple(1, 2, 3, 4, 5, 6, 7)); 383 } 384 385 /// higher order function for fold left via breadth-fisrt search 386 auto breadthFirstFoldLeft(alias func, Accumulator, Ts ...)(Accumulator acc, auto ref Ts ts) 387 { 388 static if (isTuple!(typeof(ts[0]))) 389 { 390 static if (ts.length == 1) 391 { 392 return breadthFirstFoldLeft!func(acc, ts[0].expand); 393 } 394 else 395 { 396 return breadthFirstFoldLeft!func(acc, ts[1..$], ts[0].expand); 397 } 398 } 399 else 400 { 401 static if (ts.length == 1) 402 { 403 return func(acc, ts[0]); 404 } 405 else 406 { 407 return breadthFirstFoldLeft!func(func(acc, ts[0]), ts[1..$]); 408 } 409 } 410 } 411 412 /// breadth-first map iterates by top-to-bottom search. 413 unittest 414 { 415 enum t = tuple(1, 416 tuple( 417 tuple( 418 4, 419 tuple( 420 7)), 421 2), 422 tuple( 423 3, 424 tuple( 425 5, 426 6))); 427 static assert(breadthFirstFoldLeft!((a, b) => tuple(a.expand, b))(tuple(), t) == tuple(1, 2, 3, 4, 5, 6, 7)); 428 } 429 430 /// flatten nested tuple into 1-d tuple with copies of elements 431 auto flatten(alias map = depthFirstFlatMap, Ts ...)(return auto ref Ts ts) 432 { 433 return map!((return auto ref x) => x)(ts); 434 } 435 436 /// 437 unittest 438 { 439 enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7)); 440 static assert(t.flatten == tuple(1, 2, 3, 4, 5, 6, 7)); 441 } 442 443 /// flatten nested tuple into 1-d tuple with pointers of elements 444 auto ptrs(alias map = depthFirstFlatMap, Ts ...)(return ref Ts ts) 445 { 446 return map!((return ref x) => &x)(ts); 447 } 448 449 /// 450 unittest 451 { 452 auto t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7)); 453 auto p = t.ptrs; 454 *p[1] = 222; 455 auto d = t.flatten; 456 assert(d == tuple(1, 222, 3, 4, 5, 6, 7)); 457 458 static foreach (i; 0 .. t.length) { 459 assert(*p[i] == d[i]); 460 } 461 } 462 463 /// 464 auto unzip(Z)(Z zipped) 465 { 466 import std.algorithm : map; 467 mixin( 468 { 469 import std.conv : to; 470 alias Tp = typeof(Z.init.front()); 471 auto m = "return tuple("; 472 foreach (i; 0 .. Tp.length) 473 { 474 m ~= "zipped.map!(x => x[" ~ i.to!string ~ "]),"; 475 } 476 return m[0 .. $-1] ~ ");"; 477 }()); 478 } 479 480 /// 481 unittest 482 { 483 import std.algorithm : equal; 484 import std.range : zip; 485 immutable a = [1, 2, 3]; 486 immutable b = ["a", "b", "c"]; 487 immutable c = [0.1, 0.2, 0.3]; 488 489 auto x = tuple(a, b, c); 490 auto z = zip(x.expand); 491 auto u = unzip(zip(a, b, c)); 492 static foreach (i; 0 .. x.length) 493 { 494 assert(u[i].equal(x[i])); 495 } 496 }