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 }));