1 // SemiTwist Library 2 // Written in the D programming language. 3 4 module semitwist.util.array; 5 6 import std.algorithm; 7 import std.array; 8 import std.compiler; 9 import std.conv; 10 import std.functional; 11 import std.random; 12 import std.range; 13 14 import semitwist.util.all; 15 16 private alias std.algorithm.map map_; 17 18 class MissingKeyException : Exception 19 { 20 string key; 21 this(string key) 22 { 23 this.key = key; 24 super(text("Required key '", key, "' is missing")); 25 } 26 } 27 28 size_t maxLength(T)(T[][] arrays) 29 { 30 size_t maxLen=0; 31 foreach(T[] array; arrays) 32 maxLen = max(maxLen, array.length); 33 return maxLen; 34 } 35 36 size_t indexOfMin(T)(T[] array) 37 { 38 T best = T.max; 39 size_t bestIndex; 40 foreach(size_t i, T elem; array) 41 { 42 if(elem < best) 43 { 44 best = elem; 45 bestIndex = i; 46 } 47 } 48 return bestIndex; 49 } 50 51 size_t indexOfMax(T)(T[] array) 52 { 53 T best = T.min; 54 size_t bestIndex; 55 foreach(size_t i, T elem; array) 56 { 57 if(elem > best) 58 { 59 best = elem; 60 bestIndex = i; 61 } 62 } 63 return bestIndex; 64 } 65 66 //TODO: eliminate name collision with tango.text.Util 67 //TODO: Is this the same as tango.core.Array.findIf()? 68 /+size_t find(T)(T[] collection, bool delegate(T[], size_t) isFound, size_t start=0) 69 { 70 for(size_t i=start; i<collection.length; i++) 71 { 72 if(isFound(collection, i)) 73 return i; 74 } 75 76 return collection.length; 77 } 78 +/ 79 size_t findPrior(T)(T[] collection, bool delegate(T[], size_t) isFound, size_t start=(size_t).max) 80 { 81 if(start == (size_t).max) 82 start = collection.length-1; 83 84 for(size_t i=start; i >= 0; i--) 85 { 86 if(isFound(collection, i)) 87 return i; 88 } 89 90 return collection.length; 91 } 92 93 /// Returns everything in 'from' minus the values in 'except'. 94 // Note: using ref didn't work when params were (const string[] here).dup 95 T allExcept(T)(T from, T except) 96 { 97 T f = from.dup; 98 T e = except.dup; 99 f.sort(); 100 e.sort(); 101 return f.missingFrom(e); 102 } 103 T[] allExcept(T)(T[] from, T except) 104 { 105 return allExcept(from, [except]); 106 } 107 108 /// Ex: toRangedPairs([3,4,5,5,6,6,10,25,26]) == [[3,6], [10,10], [25,26]] 109 /// Only intended for integer types and other ordered types for which "x+1" makes sense. 110 /// Input does not have to be sorted. 111 /// The resulting pairs are sorted and are inclusive on both ends. 112 /// Optionally takes a splitCond predicate so you can customize when the range ends. 113 T[2][] toRangedPairs(alias splitCond = "b > a + 1", T)(T[] arr) 114 { 115 alias binaryFun!(splitCond) splitCondDg; 116 static if(!is(typeof(splitCondDg(T.init, T.init)) == bool)) 117 static assert(false, "Invalid predicate passed to toRangedPairs: "~splitCond); 118 119 if(arr.length == 0) 120 return []; 121 122 if(arr.length == 1) 123 return [ [ arr[0], arr[0] ] ]; 124 125 arr = array(sort(arr)); 126 127 T[2][] ret; 128 auto prevVal = arr[0]; 129 auto startVal = arr[0]; 130 foreach(val; arr[1..$]) 131 { 132 if(splitCondDg(prevVal, val)) 133 { 134 ret ~= [ startVal, prevVal ]; 135 startVal = val; 136 } 137 prevVal = val; 138 } 139 ret ~= [ startVal, arr[$-1] ]; 140 141 return ret; 142 } 143 144 /// If 'haystack' begins with 'needle', remove 'needle' 145 T[] removeLeft(T)(T[] haystack, T[] needle) 146 { 147 if(haystack.startsWith(needle)) 148 haystack = haystack[needle.length .. $]; 149 150 return haystack; 151 } 152 153 /// If 'haystack' ends with 'needle', remove 'needle' 154 T[] removeRight(T)(T[] haystack, T[] needle) 155 { 156 if(haystack.endsWith(needle)) 157 haystack = haystack[0 .. $-needle.length]; 158 159 return haystack; 160 } 161 162 TVal getRequired(TVal, TKey)(TVal[TKey] aa, TKey key) 163 { 164 if(auto valPtr = key in aa) 165 return *valPtr; 166 else 167 throw new MissingKeyException(key); 168 } 169 170 ubyte[] randomBytes(size_t numBytes) 171 { 172 //TODO: Get rid of this ugly branch after dropping support for DMD 2.058 173 static if(vendor == Vendor.digitalMars || version_minor <= 58) 174 { 175 return 176 array( 177 take( 178 map_!( (x) => cast(ubyte)x )( 179 Random(unpredictableSeed) 180 ), 181 numBytes 182 ) 183 ); 184 } 185 else 186 { 187 return 188 Random(unpredictableSeed) 189 .map_!( (x) => cast(ubyte)x )() 190 .take(numBytes) 191 .array(); 192 } 193 } 194 195 mixin(unittestSemiTwistDLib(q{ 196 197 // toRangedPairs 198 mixin(deferEnsure!(q{ [12,3,7,4,10,5,9,12].toRangedPairs() }, q{ _ == [[3,5], [7,7], [9,10], [12,12]] })); 199 mixin(deferEnsure!(q{ [1,20].toRangedPairs() }, q{ _ == [[1,1], [20,20]] })); 200 201 // removeLeft 202 mixin(deferEnsure!(q{ removeLeft("abcde", "ab") }, q{ _ == "cde" })); 203 mixin(deferEnsure!(q{ removeLeft("abcde", "aX") }, q{ _ == "abcde" })); 204 mixin(deferEnsure!(q{ removeLeft("abcde", "Xb") }, q{ _ == "abcde" })); 205 mixin(deferEnsure!(q{ removeLeft("abcde", "XX") }, q{ _ == "abcde" })); 206 mixin(deferEnsure!(q{ removeLeft([1,2,3,4], [1,2]) }, q{ _ == [3,4] })); 207 mixin(deferEnsure!(q{ removeLeft([1,2,3,4], [9,9]) }, q{ _ == [1,2,3,4] })); 208 209 // removeRight 210 mixin(deferEnsure!(q{ removeRight("abcde", "de") }, q{ _ == "abc" })); 211 mixin(deferEnsure!(q{ removeRight("abcde", "Xe") }, q{ _ == "abcde" })); 212 mixin(deferEnsure!(q{ removeRight("abcde", "dX") }, q{ _ == "abcde" })); 213 mixin(deferEnsure!(q{ removeRight("abcde", "XX") }, q{ _ == "abcde" })); 214 mixin(deferEnsure!(q{ removeRight([1,2,3,4], [3,4]) }, q{ _ == [1,2] })); 215 mixin(deferEnsure!(q{ removeRight([1,2,3,4], [9,9]) }, q{ _ == [1,2,3,4] })); 216 217 }));