1 /** 2 Create a new range concatenating input range/value with any additional ranges and/or values. 3 */ 4 module ddash.algorithm.concat; 5 6 /// 7 unittest { 8 import std.range: iota; 9 10 // Concat stuff 11 assert([1, 2, 3].concat(4, [5], [6, 7], 8).equal(1.iota(9))); 12 13 // Concat single element 14 assert([1].concat(2).equal([1, 2])); 15 16 // Implicitly convertible doubles with ints 17 assert([1.0].concat([2, 3]).equal([1.0, 2.0, 3.0])); 18 19 // Concat nothing to single value 20 assert(1.concat().equal([1])); 21 22 // Concat nothing to range 23 assert([1].concat().equal([1])); 24 25 // Concat values to another value 26 assert(1.concat(2, 3).equal([1, 2, 3])); 27 28 // Concat ranges or values to another value 29 assert(1.concat(2, [3, 4]).equal([1, 2, 3, 4])); 30 31 // Concat strings 32 assert("yo".concat("dles").equal("yodles")); 33 34 // Concat stuff to string 35 assert("abc".concat(1, 2, 3).equal("abc123")); 36 } 37 38 39 import ddash.common; 40 41 /** 42 Concats everything together using best effort. 43 44 It will concat as long as there is a common type between all sets of inputs. When an input 45 is a range it will use `ElementType` as it's type. 46 47 If the first input is a string, all subsequent inputs are converted to a string as well 48 49 Params: 50 values = set of values that share a common type. 51 52 Returns: 53 A range of common type or a string if the first value is a string 54 55 Benchmarks: 56 Bottom line, concat (which uses a static foreach) is faster than eager and 57 recursive approach both with and without .array. 58 59 If you are dealing only with pure strings, however, then appender is your 60 friend 61 62 --- 63 Benchmarking concat against eager, recursive, and standard lib 64 the (array) versions involve a call to .array 65 concat single arguments: 66 concat: 1 hnsec 67 concatRecurse: 5 ms, 921 μs, and 6 hnsecs 68 concatEager: 5 ms, 417 μs, and 5 hnsecs 69 concat (array): 1 ms and 372 μs 70 concatRecurse (array): 7 ms, 587 μs, and 2 hnsecs 71 concatEager (array): 5 ms, 968 μs, and 2 hnsecs 72 concat multiple ranges together 73 concat: 1 ms, 491 μs, and 3 hnsecs 74 concatRecurse: 2 ms, 195 μs, and 3 hnsecs 75 concatEager: 7 ms, 787 μs, and 2 hnsecs 76 concat (array): 3 ms, 174 μs, and 2 hnsecs 77 concatRecurse (array): 2 ms, 816 μs, and 7 hnsecs 78 concatEager (array): 6 ms, 838 μs, and 1 hnsec 79 concat single args and multiple ranges: 80 concat: 1 ms, 858 μs, and 1 hnsec 81 concatRecurse: 3 ms, 479 μs, and 2 hnsecs 82 concatEager: 7 ms, 374 μs, and 9 hnsecs 83 concat (array): 3 ms, 358 μs, and 5 hnsecs 84 concatRecurse (array): 5 ms, 558 μs, and 1 hnsec 85 concatEager (array): 6 ms, 997 μs, and 3 hnsecs 86 concat strings and chars: 87 concat: 0 hnsecs 88 concatRecurse: 1 ms, 368 μs, and 8 hnsecs 89 concat (array): 4 ms, 238 μs, and 3 hnsecs 90 concatRecurse (array): 5 ms, 77 μs, and 9 hnsecs 91 concat strings vs appender and join 92 concat: 0 hnsecs 93 join: 6 ms, 930 μs, and 4 hnsecs 94 appender: 3 ms, 186 μs, and 1 hnsec 95 concat (array): 4 ms, 917 μs, and 6 hnsecs 96 join (array): 9 ms, 243 μs, and 7 hnsecs 97 appender (array): 3 ms, 57 μs, and 8 hnsecs 98 --- 99 100 Since: 101 0.0.1 102 */ 103 auto concat(Values...)(Values values) if (from!"bolts.traits".areCombinable!Values) { 104 import std.range: isInputRange, chain, only; 105 import std.traits: isNarrowString, isSomeChar; 106 import std.conv: to; 107 108 static if (!Values.length) { // If none we're done 109 return; 110 } else static if (Values.length == 1) { // If one we return it as a range 111 static if (isInputRange!(Values[0])) { 112 return values[0]; 113 } else { 114 return only(values[0]); 115 } 116 } else { 117 alias Head = Values[0]; 118 alias Rest = Values[1..$]; 119 // If head is a string start with a string, if it's a range start a chain, if it's a value create a range 120 static if (isNarrowString!(Head)) { 121 auto link0 = values[0]; 122 } else { 123 static if (isInputRange!(Head)) { 124 auto link0 = chain(values[0]); 125 } else { 126 auto link0 = only(values[0]); 127 } 128 } 129 130 // Declare a variable called `linkX` equal to the previous link chained with an input range 131 static string link(int i, string range) { 132 return "auto link" ~ i.to!string ~ " = link" ~ (i - 1).to!string 133 ~ ".chain(" ~ range ~ ");"; 134 } 135 136 static foreach (I; 1 .. Values.length) { 137 static if (isNarrowString!Head) { 138 static if (isNarrowString!(Values[I])) { 139 mixin(link(I, q{ values[I] })); 140 } else static if (isInputRange!(Values[I])) { 141 // If you don't rename you get a conflict if std.array is included 142 // becuase that has .join as well (And we include it in the unittest version) 143 // 144 // Interestingly, "import ddash.algorithm: myJoin = join" does not work either 145 import ddash.algorithm: stringify; 146 mixin(link(I, q{ values[I].stringify })); 147 } else static if (isSomeChar!(Values[I])) { 148 mixin(link(I, q{ only(values[I]) })); 149 } else { 150 mixin(link(I, q{ values[I].to!string })); 151 } 152 } else { 153 static if (isInputRange!(Values[I])) { 154 mixin(link(I, q{ values[I] })); 155 } else { 156 mixin(link(I, q{ only(values[I]) })); 157 } 158 } 159 } 160 mixin("return link" ~ (Values.length - 1).to!string ~ ";"); 161 } 162 } 163 164 unittest { 165 import std.array; 166 167 int i = 1; 168 double d = 2.2; 169 char c = 'c'; 170 string s = "oo"; 171 172 auto a = concat(i, d, c, [i, i]).array; 173 assert(a == [i, d, c, i, i]); 174 static assert(is(typeof(a) == double[])); 175 176 import std.algorithm: filter; 177 178 assert(concat(i, i, i).equal([i, i, i])); 179 assert([i, i].concat([i, i]).equal([i, i, i, i])); 180 assert([i, i].filter!"true".concat([i, i], i, i).equal([i, i, i, i, i, i])); 181 assert([i, i].concat(d, c, i).equal([i, i, d, c, i])); 182 assert(d.concat(i, c, i).equal([d, i, c, i])); 183 assert(c.concat(d, i).equal([c, d, i])); 184 assert([c, c].concat([d, d], 2).equal("cc2.22.22")); 185 assert(s.concat([c, c], c).equal("ooccc")); 186 assert(s.concat(s, [s]).equal("oooooo")); 187 } 188 189 unittest { 190 import std.algorithm: filter; 191 import std.range: only; 192 // Make sure if it's a single range the same type is returned 193 auto a = [1, 2]; 194 auto b = [1].filter!"true"; 195 static assert(is(typeof(concat(a)) == typeof(a))); 196 static assert(is(typeof(concat(b)) == typeof(b))); 197 static assert(is(typeof(concat(1)) == typeof(only(1)))); 198 }