nss: upgrade to release 3.73
[LibreOffice.git] / wizards / source / scriptforge / SF_Array.xba
blobe219a792e134637c1ada907484fb6bd8b40171ae
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
3 <script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Array" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
4 REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
5 REM === Full documentation is available on https://help.libreoffice.org/ ===
6 REM =======================================================================================================================
8 Option Compatible
9 Option Explicit
11 &apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
12 &apos;&apos;&apos; SF_Array
13 &apos;&apos;&apos; ========
14 &apos;&apos;&apos; Singleton class implementing the &quot;ScriptForge.Array&quot; service
15 &apos;&apos;&apos; Implemented as a usual Basic module
16 &apos;&apos;&apos; Only 1D or 2D arrays are considered. Arrays with more than 2 dimensions are rejected
17 &apos;&apos;&apos; With the noticeable exception of the CountDims method (&gt;2 dims allowed)
18 &apos;&apos;&apos; The first argument of almost every method is the array to consider
19 &apos;&apos;&apos; It is always passed by reference and left unchanged
20 &apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
22 REM ================================================================== EXCEPTIONS
24 Const ARRAYSEQUENCEERROR = &quot;ARRAYSEQUENCEERROR&quot; &apos; Incoherent arguments
25 Const ARRAYINSERTERROR = &quot;ARRAYINSERTERROR&quot; &apos; Matrix and vector have incompatible sizes
26 Const ARRAYINDEX1ERROR = &quot;ARRAYINDEX1ERROR&quot; &apos; Given index does not fit in array bounds
27 Const ARRAYINDEX2ERROR = &quot;ARRAYINDEX2ERROR&quot; &apos; Given indexes do not fit in array bounds
28 Const CSVPARSINGERROR = &quot;CSVPARSINGERROR&quot; &apos; Parsing error detected while parsing a csv file
29 Const CSVOVERFLOWWARNING = &quot;CSVOVERFLOWWARNING&quot; &apos; Array becoming too big, import process of csv file is interrupted
31 REM ============================================================ MODULE CONSTANTS
33 Const MAXREPR = 50 &apos; Maximum length to represent an array in the console
35 REM ===================================================== CONSTRUCTOR/DESTRUCTOR
37 REM -----------------------------------------------------------------------------
38 Public Function Dispose() As Variant
39 Set Dispose = Nothing
40 End Function &apos; ScriptForge.SF_Array Explicit destructor
42 REM ================================================================== PROPERTIES
44 REM -----------------------------------------------------------------------------
45 Property Get ObjectType As String
46 &apos;&apos;&apos; Only to enable object representation
47 ObjectType = &quot;SF_Array&quot;
48 End Property &apos; ScriptForge.SF_Array.ObjectType
50 REM -----------------------------------------------------------------------------
51 Property Get ServiceName As String
52 &apos;&apos;&apos; Internal use
53 ServiceName = &quot;ScriptForge.Array&quot;
54 End Property &apos; ScriptForge.SF_Array.ServiceName
56 REM ============================================================== PUBLIC METHODS
58 REM -----------------------------------------------------------------------------
59 Public Function Append(Optional ByRef Array_1D As Variant _
60 , ParamArray pvArgs() As Variant _
61 ) As Variant
62 &apos;&apos;&apos; Append at the end of the input array the items listed as arguments
63 &apos;&apos;&apos; Arguments are appended blindly
64 &apos;&apos;&apos; each of them might be a scalar of any type or a subarray
65 &apos;&apos;&apos; Args
66 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
67 &apos;&apos;&apos; pvArgs: a list of items to append to Array_1D
68 &apos;&apos;&apos; Return:
69 &apos;&apos;&apos; the new extended array. Its LBound is identical to that of Array_1D
70 &apos;&apos;&apos; Examples:
71 &apos;&apos;&apos; SF_Array.Append(Array(1, 2, 3), 4, 5) returns (1, 2, 3, 4, 5)
73 Dim vAppend As Variant &apos; Return value
74 Dim lNbArgs As Long &apos; Number of elements to append
75 Dim lMax As Long &apos; UBound of input array
76 Dim i As Long
77 Const cstThisSub = &quot;Array.Append&quot;
78 Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
80 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
81 vAppend = Array()
83 Check:
84 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
85 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
86 End If
88 Try:
89 lMax = UBound(Array_1D)
90 lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
91 If lMax &lt; LBound(Array_1D) Then &apos; Initial array is empty
92 If lNbArgs &gt; 0 Then
93 ReDim vAppend(0 To lNbArgs - 1)
94 End If
95 Else
96 vAppend() = Array_1D()
97 If lNbArgs &gt; 0 Then
98 ReDim Preserve vAppend(LBound(Array_1D) To lMax + lNbArgs)
99 End If
100 End If
101 For i = 1 To lNbArgs
102 vAppend(lMax + i) = pvArgs(i - 1)
103 Next i
105 Finally:
106 Append = vAppend()
107 SF_Utils._ExitFunction(cstThisSub)
108 Exit Function
109 Catch:
110 GoTo Finally
111 End Function &apos; ScriptForge.SF_Array.Append
113 REM -----------------------------------------------------------------------------
114 Public Function AppendColumn(Optional ByRef Array_2D As Variant _
115 , Optional ByRef Column As Variant _
116 ) As Variant
117 &apos;&apos;&apos; AppendColumn appends to the right side of a 2D array a new Column
118 &apos;&apos;&apos; Args
119 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
120 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st Column of the resulting 2D array
121 &apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
122 &apos;&apos;&apos; Returns:
123 &apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
124 &apos;&apos;&apos; Exceptions:
125 &apos;&apos;&apos; ARRAYINSERTERROR
126 &apos;&apos;&apos; Examples:
127 &apos;&apos;&apos; SF_Array.AppendColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 4), (2, 5), (3, 6))
128 &apos;&apos;&apos; x = SF_Array.AppendColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
130 Dim vAppendColumn As Variant &apos; Return value
131 Dim iDims As Integer &apos; Dimensions of Array_2D
132 Dim lMin1 As Long &apos; LBound1 of input array
133 Dim lMax1 As Long &apos; UBound1 of input array
134 Dim lMin2 As Long &apos; LBound2 of input array
135 Dim lMax2 As Long &apos; UBound2 of input array
136 Dim lMin As Long &apos; LBound of Column array
137 Dim lMax As Long &apos; UBound of Column array
138 Dim i As Long
139 Dim j As Long
140 Const cstThisSub = &quot;Array.AppendColumn&quot;
141 Const cstSubArgs = &quot;Array_2D, Column&quot;
143 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
144 vAppendColumn = Array()
146 Check:
147 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
148 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
149 If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
150 End If
151 iDims = SF_Array.CountDims(Array_2D)
152 If iDims &gt; 2 Then
153 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
154 End If
156 Try:
157 lMin = LBound(Column)
158 lMax = UBound(Column)
160 &apos; Compute future dimensions of output array
161 Select Case iDims
162 Case 0 : lMin1 = lMin : lMax1 = lMax
163 lMin2 = 0 : lMax2 = -1
164 Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
165 lMin2 = 0 : lMax2 = 0
166 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
167 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
168 End Select
169 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
170 ReDim vAppendColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
172 &apos; Copy input array to output array
173 For i = lMin1 To lMax1
174 For j = lMin2 To lMax2
175 If iDims = 2 Then vAppendColumn(i, j) = Array_2D(i, j) Else vAppendColumn(i, j) = Array_2D(i)
176 Next j
177 Next i
178 &apos; Copy new Column
179 For i = lMin1 To lMax1
180 vAppendColumn(i, lMax2 + 1) = Column(i)
181 Next i
183 Finally:
184 AppendColumn = vAppendColumn()
185 SF_Utils._ExitFunction(cstThisSub)
186 Exit Function
187 Catch:
188 GoTo Finally
189 CatchColumn:
190 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
191 GoTo Finally
192 End Function &apos; ScriptForge.SF_Array.AppendColumn
194 REM -----------------------------------------------------------------------------
195 Public Function AppendRow(Optional ByRef Array_2D As Variant _
196 , Optional ByRef Row As Variant _
197 ) As Variant
198 &apos;&apos;&apos; AppendRow appends below a 2D array a new row
199 &apos;&apos;&apos; Args
200 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
201 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the 1st row of the resulting 2D array
202 &apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
203 &apos;&apos;&apos; Returns:
204 &apos;&apos;&apos; the new extended array. Its LBounds are identical to that of Array_2D
205 &apos;&apos;&apos; Exceptions:
206 &apos;&apos;&apos; ARRAYINSERTERROR
207 &apos;&apos;&apos; Examples:
208 &apos;&apos;&apos; SF_Array.AppendRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((1, 2, 3), (4, 5, 6))
209 &apos;&apos;&apos; x = SF_Array.AppendRow(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
211 Dim vAppendRow As Variant &apos; Return value
212 Dim iDims As Integer &apos; Dimensions of Array_2D
213 Dim lMin1 As Long &apos; LBound1 of input array
214 Dim lMax1 As Long &apos; UBound1 of input array
215 Dim lMin2 As Long &apos; LBound2 of input array
216 Dim lMax2 As Long &apos; UBound2 of input array
217 Dim lMin As Long &apos; LBound of row array
218 Dim lMax As Long &apos; UBound of row array
219 Dim i As Long
220 Dim j As Long
221 Const cstThisSub = &quot;Array.AppendRow&quot;
222 Const cstSubArgs = &quot;Array_2D, Row&quot;
224 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
225 vAppendRow = Array()
227 Check:
228 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
229 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
230 If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
231 End If
232 iDims = SF_Array.CountDims(Array_2D)
233 If iDims &gt; 2 Then
234 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
235 End If
237 Try:
238 lMin = LBound(Row)
239 lMax = UBound(Row)
241 &apos; Compute future dimensions of output array
242 Select Case iDims
243 Case 0 : lMin1 = 0 : lMax1 = -1
244 lMin2 = lMin : lMax2 = lMax
245 Case 1 : lMin1 = 0 : lMax1 = 0
246 lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
247 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
248 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
249 End Select
250 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
251 ReDim vAppendRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
253 &apos; Copy input array to output array
254 For i = lMin1 To lMax1
255 For j = lMin2 To lMax2
256 If iDims = 2 Then vAppendRow(i, j) = Array_2D(i, j) Else vAppendRow(i, j) = Array_2D(j)
257 Next j
258 Next i
259 &apos; Copy new row
260 For j = lMin2 To lMax2
261 vAppendRow(lMax1 + 1, j) = Row(j)
262 Next j
264 Finally:
265 AppendRow = vAppendRow()
266 SF_Utils._ExitFunction(cstThisSub)
267 Exit Function
268 Catch:
269 GoTo Finally
270 CatchRow:
271 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
272 GoTo Finally
273 End Function &apos; ScriptForge.SF_Array.AppendRow
275 REM -----------------------------------------------------------------------------
276 Public Function Contains(Optional ByRef Array_1D As Variant _
277 , Optional ByVal ToFind As Variant _
278 , Optional ByVal CaseSensitive As Variant _
279 , Optional ByVal SortOrder As Variant _
280 ) As Boolean
281 &apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date
282 &apos;&apos;&apos; The comparison between strings can be done case-sensitive or not
283 &apos;&apos;&apos; If the array is sorted then
284 &apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
285 &apos;&apos;&apos; Empty and Null items are forbidden
286 &apos;&apos;&apos; a binary search is done
287 &apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
288 &apos;&apos;&apos; Args:
289 &apos;&apos;&apos; Array_1D: the array to scan
290 &apos;&apos;&apos; ToFind: a number, a date or a string to find
291 &apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
292 &apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
293 &apos;&apos;&apos; Return: True when found
294 &apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
295 &apos;&apos;&apos; Examples:
296 &apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns True
297 &apos;&apos;&apos; SF_Array.Contains(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns False
299 Dim bContains As Boolean &apos; Return value
300 Dim iToFindType As Integer &apos; VarType of ToFind
301 Const cstThisSub = &quot;Array.Contains&quot;
302 Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
304 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
306 bContains = False
308 Check:
309 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
310 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
311 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
312 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
313 If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
314 iToFindType = SF_Utils._VarTypeExt(ToFind)
315 If SortOrder &lt;&gt; &quot;&quot; Then
316 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, iToFindType) Then GoTo Finally
317 Else
318 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
319 End If
320 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
321 End If
323 Try:
324 bContains = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)(0)
326 Finally:
327 Contains = bContains
328 SF_Utils._ExitFunction(cstThisSub)
329 Exit Function
330 Catch:
331 GoTo Finally
332 End Function &apos; ScriptForge.SF_Array.Contains
334 REM -----------------------------------------------------------------------------
335 Public Function ConvertToDictionary(Optional ByRef Array_2D As Variant) As Variant
336 &apos;&apos;&apos; Store the content of a 2-columns array into a dictionary
337 &apos;&apos;&apos; Key found in 1st column, Item found in 2nd
338 &apos;&apos;&apos; Args:
339 &apos;&apos;&apos; Array_2D: 1st column must contain exclusively non zero-length strings
340 &apos;&apos;&apos; 1st column may not be sorted
341 &apos;&apos;&apos; Returns:
342 &apos;&apos;&apos; a ScriptForge dictionary object
343 &apos;&apos;&apos; Examples:
344 &apos;&apos;&apos;
346 Dim oDict As Variant &apos; Return value
347 Dim i As Long
348 Const cstThisSub = &quot;Dictionary.ConvertToArray&quot;
349 Const cstSubArgs = &quot;Array_2D&quot;
351 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
353 Check:
354 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
355 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2, V_STRING, True) Then GoTo Finally
356 End If
358 Try:
359 Set oDict = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
360 For i = LBound(Array_2D, 1) To UBound(Array_2D, 1)
361 oDict.Add(Array_2D(i, 0), Array_2D(i, 1))
362 Next i
364 ConvertToDictionary = oDict
366 Finally:
367 SF_Utils._ExitFunction(cstThisSub)
368 Exit Function
369 Catch:
370 GoTo Finally
371 End Function &apos; ScriptForge.SF_Array.ConvertToDictionary
373 REM -----------------------------------------------------------------------------
374 Public Function CountDims(Optional ByRef Array_ND As Variant) As Integer
375 &apos;&apos;&apos; Count the number of dimensions of an array - may be &gt; 2
376 &apos;&apos;&apos; Args:
377 &apos;&apos;&apos; Array_ND: the array to be examined
378 &apos;&apos;&apos; Return: the number of dimensions: -1 = not array, 0 = uninitialized array, else &gt;= 1
379 &apos;&apos;&apos; Examples:
380 &apos;&apos;&apos; Dim a(1 To 10, -3 To 12, 5)
381 &apos;&apos;&apos; CountDims(a) returns 3
383 Dim iDims As Integer &apos; Return value
384 Dim lMax As Long &apos; Storage for UBound of each dimension
385 Const cstThisSub = &quot;Array.CountDims&quot;
386 Const cstSubArgs = &quot;Array_ND&quot;
388 Check:
389 iDims = -1
390 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
391 If IsMissing(Array_ND) Then &apos; To have missing exception processed
392 If Not SF_Utils._ValidateArray(Array_ND, &quot;Array_ND&quot;) Then GoTo Finally
393 End If
394 End If
396 Try:
397 On Local Error Goto ErrHandler
398 &apos; Loop, increasing the dimension index (i) until an error occurs.
399 &apos; An error will occur when i exceeds the number of dimensions in the array. Returns i - 1.
400 iDims = 0
401 If Not IsArray(Array_ND) Then
402 Else
404 iDims = iDims + 1
405 lMax = UBound(Array_ND, iDims)
406 Loop Until (Err &lt;&gt; 0)
407 End If
409 ErrHandler:
410 On Local Error GoTo 0
412 iDims = iDims - 1
413 If iDims = 1 Then
414 If LBound(Array_ND, 1) &gt; UBound(Array_ND, 1) Then iDims = 0
415 End If
417 Finally:
418 CountDims = iDims
419 SF_Utils._ExitFunction(cstThisSub)
420 Exit Function
421 End Function &apos; ScriptForge.SF_Array.CountDims
423 REM -----------------------------------------------------------------------------
424 Public Function Difference(Optional ByRef Array1_1D As Variant _
425 , Optional ByRef Array2_1D As Variant _
426 , Optional ByVal CaseSensitive As Variant _
427 ) As Variant
428 &apos;&apos;&apos; Build a set being the Difference of the two input arrays, i.e. items are contained in 1st array and NOT in 2nd
429 &apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
430 &apos;&apos;&apos; Empty and Null items are forbidden
431 &apos;&apos;&apos; The comparison between strings is case sensitive or not
432 &apos;&apos;&apos; Args:
433 &apos;&apos;&apos; Array1_1D: a 1st input array
434 &apos;&apos;&apos; Array2_1D: a 2nd input array
435 &apos;&apos;&apos; CaseSensitive: default = False
436 &apos;&apos;&apos; Returns: a zero-based array containing unique items from the 1st array not present in the 2nd
437 &apos;&apos;&apos; The output array is sorted in ascending order
438 &apos;&apos;&apos; Examples:
439 &apos;&apos;&apos; SF_Array.Difference(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;)
441 Dim vDifference() As Variant &apos; Return value
442 Dim vSorted() As Variant &apos; The 2nd input array after sort
443 Dim iType As Integer &apos; VarType of elements in input arrays
444 Dim lMin1 As Long &apos; LBound of 1st input array
445 Dim lMax1 As Long &apos; UBound of 1st input array
446 Dim lMin2 As Long &apos; LBound of 2nd input array
447 Dim lMax2 As Long &apos; UBound of 2nd input array
448 Dim lSize As Long &apos; Number of Difference items
449 Dim vItem As Variant &apos; One single item in the array
450 Dim i As Long
451 Const cstThisSub = &quot;Array.Difference&quot;
452 Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
454 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
455 vDifference = Array()
457 Check:
458 If IsMissing(CaseSensitive) Then CaseSensitive = False
459 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
460 If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
461 iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
462 If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
463 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
464 End If
466 Try:
467 lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
468 lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
470 &apos; If 1st array is empty, do nothing
471 If lMax1 &lt; lMin1 Then
472 ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
473 vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
474 Else
476 &apos; First sort the 2nd array
477 vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
479 &apos; Resize the output array to the size of the 1st array
480 ReDim vDifference(0 To (lMax1 - lMin1))
481 lSize = -1
483 &apos; Fill vDifference one by one with items present only in 1st set
484 For i = lMin1 To lMax1
485 vItem = Array1_1D(i)
486 If Not SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
487 lSize = lSize + 1
488 vDifference(lSize) = vItem
489 End If
490 Next i
492 &apos; Remove unfilled entries and duplicates
493 If lSize &gt;= 0 Then
494 ReDim Preserve vDifference(0 To lSize)
495 vDifference() = SF_Array.Unique(vDifference, CaseSensitive)
496 Else
497 vDifference = Array()
498 End If
499 End If
501 Finally:
502 Difference = vDifference()
503 SF_Utils._ExitFunction(cstThisSub)
504 Exit Function
505 Catch:
506 GoTo Finally
507 End Function &apos; ScriptForge.SF_Array.Difference
509 REM -----------------------------------------------------------------------------
510 Public Function ExportToTextFile(Optional ByRef Array_1D As Variant _
511 , Optional ByVal FileName As Variant _
512 , Optional ByVal Encoding As Variant _
513 ) As Boolean
514 &apos;&apos;&apos; Write all items of the array sequentially to a text file
515 &apos;&apos;&apos; If the file exists already, it will be overwritten without warning
516 &apos;&apos;&apos; Args:
517 &apos;&apos;&apos; Array_1D: the array to export
518 &apos;&apos;&apos; FileName: the full name (path + file) in SF_FileSystem.FileNaming notation
519 &apos;&apos;&apos; Encoding: The character set that should be used
520 &apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
521 &apos;&apos;&apos; Note that LibreOffice does not implement all existing sets
522 &apos;&apos;&apos; Default = UTF-8
523 &apos;&apos;&apos; Returns:
524 &apos;&apos;&apos; True if successful
525 &apos;&apos;&apos; Examples:
526 &apos;&apos;&apos; SF_Array.ExportToTextFile(Array(&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;D&quot;), &quot;C:\Temp\A short file.txt&quot;)
528 Dim bExport As Boolean &apos; Return value
529 Dim oFile As Object &apos; Output file handler
530 Dim sLine As String &apos; A single line
531 Const cstThisSub = &quot;Array.ExportToTextFile&quot;
532 Const cstSubArgs = &quot;Array_1D, FileName&quot;
534 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
535 bExport = False
537 Check:
538 If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
539 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
540 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, V_STRING, True) Then GoTo Finally
541 If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
542 If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
543 End If
545 Try:
546 Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
547 If Not IsNull(oFile) Then
548 With oFile
549 For Each sLine In Array_1D
550 .WriteLine(sLine)
551 Next sLine
552 .CloseFile()
553 End With
554 End If
556 bExport = True
558 Finally:
559 If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
560 ExportToTextFile = bExport
561 SF_Utils._ExitFunction(cstThisSub)
562 Exit Function
563 Catch:
564 GoTo Finally
565 End Function &apos; ScriptForge.SF_Array.ExportToTextFile
567 REM -----------------------------------------------------------------------------
568 Public Function ExtractColumn(Optional ByRef Array_2D As Variant _
569 , Optional ByVal ColumnIndex As Variant _
570 ) As Variant
571 &apos;&apos;&apos; ExtractColumn extracts from a 2D array a specific column
572 &apos;&apos;&apos; Args
573 &apos;&apos;&apos; Array_2D: the array from which to extract
574 &apos;&apos;&apos; ColumnIndex: the column to extract - must be in the interval [LBound, UBound]
575 &apos;&apos;&apos; Returns:
576 &apos;&apos;&apos; the extracted column. Its LBound and UBound are identical to that of the 1st dimension of Array_2D
577 &apos;&apos;&apos; Exceptions:
578 &apos;&apos;&apos; ARRAYINDEX1ERROR
579 &apos;&apos;&apos; Examples:
580 &apos;&apos;&apos; |1, 2, 3|
581 &apos;&apos;&apos; SF_Array.ExtractColumn( |4, 5, 6|, 2) returns (3, 6, 9)
582 &apos;&apos;&apos; |7, 8, 9|
584 Dim vExtractColumn As Variant &apos; Return value
585 Dim lMin1 As Long &apos; LBound1 of input array
586 Dim lMax1 As Long &apos; UBound1 of input array
587 Dim lMin2 As Long &apos; LBound1 of input array
588 Dim lMax2 As Long &apos; UBound1 of input array
589 Dim i As Long
590 Const cstThisSub = &quot;Array.ExtractColumn&quot;
591 Const cstSubArgs = &quot;Array_2D, ColumnIndex&quot;
593 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
594 vExtractColumn = Array()
596 Check:
597 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
598 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
599 If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
600 End If
602 Try:
603 &apos; Compute future dimensions of output array
604 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
605 If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
606 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
607 ReDim vExtractColumn(lMin1 To lMax1)
609 &apos; Copy Column of input array to output array
610 For i = lMin1 To lMax1
611 vExtractColumn(i) = Array_2D(i, ColumnIndex)
612 Next i
614 Finally:
615 ExtractColumn = vExtractColumn()
616 SF_Utils._ExitFunction(cstThisSub)
617 Exit Function
618 Catch:
619 GoTo Finally
620 CatchIndex:
621 SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;ColumnIndex&quot;, SF_Array._Repr(Array_2D), ColumnIndex)
622 GoTo Finally
623 End Function &apos; ScriptForge.SF_Array.ExtractColumn
625 REM -----------------------------------------------------------------------------
626 Public Function ExtractRow(Optional ByRef Array_2D As Variant _
627 , Optional ByVal RowIndex As Variant _
628 ) As Variant
629 &apos;&apos;&apos; ExtractRow extracts from a 2D array a specific row
630 &apos;&apos;&apos; Args
631 &apos;&apos;&apos; Array_2D: the array from which to extract
632 &apos;&apos;&apos; RowIndex: the row to extract - must be in the interval [LBound, UBound]
633 &apos;&apos;&apos; Returns:
634 &apos;&apos;&apos; the extracted row. Its LBound and UBound are identical to that of the 2nd dimension of Array_2D
635 &apos;&apos;&apos; Exceptions:
636 &apos;&apos;&apos; ARRAYINDEX1ERROR
637 &apos;&apos;&apos; Examples:
638 &apos;&apos;&apos; |1, 2, 3|
639 &apos;&apos;&apos; SF_Array.ExtractRow(|4, 5, 6|, 2) returns (7, 8, 9)
640 &apos;&apos;&apos; |7, 8, 9|
642 Dim vExtractRow As Variant &apos; Return value
643 Dim lMin1 As Long &apos; LBound1 of input array
644 Dim lMax1 As Long &apos; UBound1 of input array
645 Dim lMin2 As Long &apos; LBound1 of input array
646 Dim lMax2 As Long &apos; UBound1 of input array
647 Dim i As Long
648 Const cstThisSub = &quot;Array.ExtractRow&quot;
649 Const cstSubArgs = &quot;Array_2D, RowIndex&quot;
651 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
652 vExtractRow = Array()
654 Check:
655 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
656 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
657 If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
658 End If
660 Try:
661 &apos; Compute future dimensions of output array
662 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
663 If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
664 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
665 ReDim vExtractRow(lMin2 To lMax2)
667 &apos; Copy row of input array to output array
668 For i = lMin2 To lMax2
669 vExtractRow(i) = Array_2D(RowIndex, i)
670 Next i
672 Finally:
673 ExtractRow = vExtractRow()
674 SF_Utils._ExitFunction(cstThisSub)
675 Exit Function
676 Catch:
677 GoTo Finally
678 CatchIndex:
679 SF_Exception.RaiseFatal(ARRAYINDEX1ERROR, &quot;RowIndex&quot;, SF_Array._Repr(Array_2D), RowIndex)
680 GoTo Finally
681 End Function &apos; ScriptForge.SF_Array.ExtractRow
683 REM -----------------------------------------------------------------------------
684 Public Function Flatten(Optional ByRef Array_1D As Variant) As Variant
685 &apos;&apos;&apos; Stack all items and all items in subarrays into one array without subarrays
686 &apos;&apos;&apos; Args
687 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
688 &apos;&apos;&apos; Return:
689 &apos;&apos;&apos; The new flattened array. Its LBound is identical to that of Array_1D
690 &apos;&apos;&apos; If one of the subarrays has a number of dimensions &gt; 1 Then that subarray is left unchanged
691 &apos;&apos;&apos; Examples:
692 &apos;&apos;&apos; SF_Array.Flatten(Array(1, 2, Array(3, 4, 5)) returns (1, 2, 3, 4, 5)
694 Dim vFlatten As Variant &apos; Return value
695 Dim lMin As Long &apos; LBound of input array
696 Dim lMax As Long &apos; UBound of input array
697 Dim lIndex As Long &apos; Index in output array
698 Dim vItem As Variant &apos; Array single item
699 Dim iDims As Integer &apos; Array number of dimensions
700 Dim lEmpty As Long &apos; Number of empty subarrays
701 Dim i As Long
702 Dim j As Long
703 Const cstThisSub = &quot;Array.Flatten&quot;
704 Const cstSubArgs = &quot;Array_1D&quot;
706 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
707 vFlatten = Array()
709 Check:
710 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
711 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
712 End If
714 Try:
715 If UBound(Array_1D) &gt;= LBound(Array_1D) Then
716 lMin = LBound(Array_1D) : lMax = UBound(Array_1D)
717 ReDim vFlatten(lMin To lMax) &apos; Initial minimal sizing
718 lEmpty = 0
719 lIndex = lMin - 1
720 For i = lMin To lMax
721 vItem = Array_1D(i)
722 If IsArray(vItem) Then
723 iDims = SF_Array.CountDims(vItem)
724 Select Case iDims
725 Case 0 &apos; Empty arrays are ignored
726 lEmpty = lEmpty + 1
727 Case 1 &apos; Only 1D subarrays are flattened
728 ReDim Preserve vFlatten(lMin To UBound(vFlatten) + UBound(vItem) - LBound(vItem))
729 For j = LBound(vItem) To UBound(vItem)
730 lIndex = lIndex + 1
731 vFlatten(lIndex) = vItem(j)
732 Next j
733 Case &gt; 1 &apos; Other arrays are left unchanged
734 lIndex = lIndex + 1
735 vFlatten(lIndex) = vItem
736 End Select
737 Else
738 lIndex = lIndex + 1
739 vFlatten(lIndex) = vItem
740 End If
741 Next i
742 End If
743 &apos; Reduce size of output if Array_1D is populated with some empty arrays
744 If lEmpty &gt; 0 Then
745 If lIndex - lEmpty &lt; lMin Then
746 vFlatten = Array()
747 Else
748 ReDim Preserve vFlatten(lMin To UBound(vFlatten) - lEmpty)
749 End If
750 End If
752 Finally:
753 Flatten = vFlatten()
754 SF_Utils._ExitFunction(cstThisSub)
755 Exit Function
756 Catch:
757 GoTo Finally
758 End Function &apos; ScriptForge.SF_Array.Flatten
760 REM -----------------------------------------------------------------------------
761 Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
762 &apos;&apos;&apos; Return the actual value of the given property
763 &apos;&apos;&apos; Args:
764 &apos;&apos;&apos; PropertyName: the name of the property as a string
765 &apos;&apos;&apos; Returns:
766 &apos;&apos;&apos; The actual value of the property
767 &apos;&apos;&apos; Exceptions
768 &apos;&apos;&apos; ARGUMENTERROR The property does not exist
770 Const cstThisSub = &quot;Array.GetProperty&quot;
771 Const cstSubArgs = &quot;PropertyName&quot;
773 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
774 GetProperty = Null
776 Check:
777 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
778 If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
779 End If
781 Try:
782 Select Case UCase(PropertyName)
783 Case Else
784 End Select
786 Finally:
787 SF_Utils._ExitFunction(cstThisSub)
788 Exit Function
789 Catch:
790 GoTo Finally
791 End Function &apos; ScriptForge.SF_Array.GetProperty
793 REM -----------------------------------------------------------------------------
794 Public Function ImportFromCSVFile(Optional ByRef FileName As Variant _
795 , Optional ByVal Delimiter As Variant _
796 , Optional ByVal DateFormat As Variant _
797 ) As Variant
798 &apos;&apos;&apos; Import the data contained in a comma-separated values (CSV) file
799 &apos;&apos;&apos; The comma may be replaced by any character
800 &apos;&apos;&apos; Each line in the file contains a full record
801 &apos;&apos;&apos; Line splitting is not allowed)
802 &apos;&apos;&apos; However sequences like \n, \t, ... are left unchanged. Use SF_String.Unescape() to manage them
803 &apos;&apos;&apos; A special mechanism is implemented to load dates
804 &apos;&apos;&apos; The applicable CSV format is described in https://tools.ietf.org/html/rfc4180
805 &apos;&apos;&apos; Args:
806 &apos;&apos;&apos; FileName: the name of the text file containing the data expressed as given by the current FileNaming
807 &apos;&apos;&apos; property of the SF_FileSystem service. Default = both URL format or native format
808 &apos;&apos;&apos; Delimiter: Default = &quot;,&quot;. Other usual options are &quot;;&quot; and the tab character
809 &apos;&apos;&apos; DateFormat: either YYYY-MM-DD, DD-MM-YYYY or MM-DD-YYYY
810 &apos;&apos;&apos; The dash (-) may be replaced by a dot (.), a slash (/) or a space
811 &apos;&apos;&apos; Other date formats will be ignored
812 &apos;&apos;&apos; If &quot;&quot; (default), dates will be considered as strings
813 &apos;&apos;&apos; Returns:
814 &apos;&apos;&apos; A 2D-array with each row corresponding with a single record read in the file
815 &apos;&apos;&apos; and each column corresponding with a field of the record
816 &apos;&apos;&apos; No check is made about the coherence of the field types across columns
817 &apos;&apos;&apos; A best guess will be made to identify numeric and date types
818 &apos;&apos;&apos; If a line contains less or more fields than the first line in the file,
819 &apos;&apos;&apos; an exception will be raised. Empty lines however are simply ignored
820 &apos;&apos;&apos; If the size of the file exceeds the number of items limit, a warning is raised
821 &apos;&apos;&apos; and the array is truncated
822 &apos;&apos;&apos; Exceptions:
823 &apos;&apos;&apos; CSVPARSINGERROR Given file is not formatted as a csv file
824 &apos;&apos;&apos; CSVOVERFLOWWARNING Maximum number of allowed items exceeded
826 Dim vArray As Variant &apos; Returned array
827 Dim lCol As Long &apos; Index of last column of vArray
828 Dim lRow As Long &apos; Index of current row of vArray
829 Dim lFileSize As Long &apos; Number of records found in the file
830 Dim vCsv As Object &apos; CSV file handler
831 Dim sLine As String &apos; Last read line
832 Dim vLine As Variant &apos; Array of fields of last read line
833 Dim sItem As String &apos; Individual item in the file
834 Dim vItem As Variant &apos; Individual item in the output array
835 Dim iPosition As Integer &apos; Date position in individual item
836 Dim iYear As Integer, iMonth As Integer, iDay As Integer
837 &apos; Date components
838 Dim i As Long
839 Const cstItemsLimit = 250000 &apos; Maximum number of admitted items
840 Const cstThisSub = &quot;Array.ImportFromCSVFile&quot;
841 Const cstSubArgs = &quot;FileName, [Delimiter=&quot;&quot;,&quot;&quot;], [DateFormat=&quot;&quot;&quot;&quot;]&quot;
843 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
844 vArray = Array()
846 Check:
847 If IsMissing(Delimiter) Or IsEmpty(Delimiter) Then Delimiter = &quot;,&quot;
848 If IsMissing(DateFormat) Or IsEmpty(DateFormat) Then DateFormat = &quot;&quot;
849 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
850 If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
851 If Not SF_Utils._Validate(Delimiter, &quot;Delimiter&quot;, V_STRING) Then GoTo Finally
852 If Not SF_Utils._Validate(DateFormat, &quot;DateFormat&quot;, V_STRING) Then GoTo Finally
853 End If
854 If Len(Delimiter) = 0 Then Delimiter = &quot;,&quot;
856 Try:
857 &apos; Counts the lines present in the file to size the final array
858 &apos; Very beneficial for large files, better than multiple ReDims
859 &apos; Small overhead for small files
860 lFileSize = SF_FileSystem._CountTextLines(FileName, False)
861 If lFileSize &lt;= 0 Then GoTo Finally
863 &apos; Reread file line by line
864 Set vCsv = SF_FileSystem.OpenTextFile(FileName, IOMode := SF_FileSystem.ForReading)
865 If IsNull(vCsv) Then GoTo Finally &apos; Open error
866 lRow = -1
867 With vCsv
868 Do While Not .AtEndOfStream
869 sLine = .ReadLine()
870 If Len(sLine) &gt; 0 Then &apos; Ignore empty lines
871 If InStr(sLine, &quot;&quot;&quot;&quot;) &gt; 0 Then vLine = SF_String.SplitNotQuoted(sLine, Delimiter) Else vLine = Split(sLine, Delimiter) &apos; Simple split when relevant
872 lRow = lRow + 1
873 If lRow = 0 Then &apos; Initial sizing of output array
874 lCol = UBound(vLine)
875 ReDim vArray(0 To lFileSize - 1, 0 To lCol)
876 ElseIf UBound(vLine) &lt;&gt; lCol Then
877 GoTo CatchCSVFormat
878 End If
879 &apos; Check type and copy all items of the line
880 For i = 0 To lCol
881 If Left(vLine(i), 1) = &quot;&quot;&quot;&quot; Then sItem = SF_String.Unquote(vLine(i)) Else sItem = vLine(i) &apos; Unquote only when useful
882 &apos; Interpret the individual line item
883 Select Case True
884 Case IsNumeric(sItem)
885 If InStr(sItem, &quot;.&quot;) + InStr(1, sItem, &quot;e&quot;, 1) &gt; 0 Then vItem = Val(sItem) Else vItem = CLng(sItem)
886 Case DateFormat &lt;&gt; &quot;&quot; And Len(sItem) = Len(DateFormat)
887 If SF_String.IsADate(sItem, DateFormat) Then
888 iPosition = InStr(DateFormat, &quot;YYYY&quot;) : iYear = CInt(Mid(sItem, iPosition, 4))
889 iPosition = InStr(DateFormat, &quot;MM&quot;) : iMonth = CInt(Mid(sItem, iPosition, 2))
890 iPosition = InStr(DateFormat, &quot;DD&quot;) : iDay = CInt(Mid(sItem, iPosition, 2))
891 vItem = DateSerial(iYear, iMonth, iDay)
892 Else
893 vItem = sItem
894 End If
895 Case Else : vItem = sItem
896 End Select
897 vArray(lRow, i) = vItem
898 Next i
899 End If
900 &apos; Provision to avoid very large arrays and their sometimes erratic behaviour
901 If (lRow + 2) * (lCol + 1) &gt; cstItemsLimit Then
902 ReDim Preserve vArray(0 To lRow, 0 To lCol)
903 GoTo CatchOverflow
904 End If
905 Loop
906 End With
908 Finally:
909 If Not IsNull(vCsv) Then
910 vCsv.CloseFile()
911 Set vCsv = vCsv.Dispose()
912 End If
913 ImportFromCSVFile = vArray
914 SF_Utils._ExitFunction(cstThisSub)
915 Exit Function
916 Catch:
917 GoTo Finally
918 CatchCSVFormat:
919 SF_Exception.RaiseFatal(CSVPARSINGERROR, FileName, vCsv.Line, sLine)
920 GoTo Finally
921 CatchOverflow:
922 &apos;TODO SF_Exception.RaiseWarning(SF_Exception.CSVOVERFLOWWARNING, cstThisSub)
923 &apos;MsgBox &quot;TOO MUCH LINES !!&quot;
924 GoTo Finally
925 End Function &apos; ScriptForge.SF_Array.ImportFromCSVFile
927 REM -----------------------------------------------------------------------------
928 Public Function IndexOf(Optional ByRef Array_1D As Variant _
929 , Optional ByVal ToFind As Variant _
930 , Optional ByVal CaseSensitive As Variant _
931 , Optional ByVal SortOrder As Variant _
932 ) As Long
933 &apos;&apos;&apos; Finds in a 1D array the ToFind number, string or date
934 &apos;&apos;&apos; ToFind must exist within the array.
935 &apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
936 &apos;&apos;&apos; If the array is sorted then
937 &apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
938 &apos;&apos;&apos; Empty and Null items are forbidden
939 &apos;&apos;&apos; a binary search is done
940 &apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
941 &apos;&apos;&apos; Args:
942 &apos;&apos;&apos; Array_1D: the array to scan
943 &apos;&apos;&apos; ToFind: a number, a date or a string to find
944 &apos;&apos;&apos; CaseSensitive: Only for string comparisons, default = False
945 &apos;&apos;&apos; SortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
946 &apos;&apos;&apos; Return: the index of the found item, LBound - 1 if not found
947 &apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
948 &apos;&apos;&apos; Examples:
949 &apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, SortOrder := &quot;ASC&quot;) returns 2
950 &apos;&apos;&apos; SF_Array.IndexOf(Array(&quot;A&quot;,&quot;B&quot;,&quot;c&quot;,&quot;D&quot;), &quot;C&quot;, CaseSensitive := True) returns -1
952 Dim vFindItem() As Variant &apos; 2-items array (0) = True if found, (1) = Index where found
953 Dim lIndex As Long &apos; Return value
954 Dim iToFindType As Integer &apos; VarType of ToFind
955 Const cstThisSub = &quot;Array.IndexOf&quot;
956 Const cstSubArgs = &quot;Array_1D, ToFind, [CaseSensitive=False], [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;]&quot;
958 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
960 lIndex = -1
962 Check:
963 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
964 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;&quot;
965 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
966 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;, &quot;DESC&quot;, &quot;&quot;)) Then GoTo Finally
967 If Not SF_Utils._Validate(ToFind, &quot;ToFind&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
968 iToFindType = SF_Utils._VarTypeExt(ToFind)
969 If SortOrder &lt;&gt; &quot;&quot; Then
970 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1, iToFindType) Then GoTo Finally
971 Else
972 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array&quot;, 1) Then GoTo Finally
973 End If
974 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
975 End If
977 Try:
978 vFindItem = SF_Array._FindItem(Array_1D, ToFind, CaseSensitive, SortOrder)
979 If vFindItem(0) = True Then lIndex = vFindItem(1) Else lIndex = LBound(Array_1D) - 1
981 Finally:
982 IndexOf = lIndex
983 SF_Utils._ExitFunction(cstThisSub)
984 Exit Function
985 Catch:
986 GoTo Finally
987 End Function &apos; ScriptForge.SF_Array.IndexOf
989 REM -----------------------------------------------------------------------------
990 Public Function Insert(Optional ByRef Array_1D As Variant _
991 , Optional ByVal Before As Variant _
992 , ParamArray pvArgs() As Variant _
993 ) As Variant
994 &apos;&apos;&apos; Insert before the index Before of the input array the items listed as arguments
995 &apos;&apos;&apos; Arguments are inserted blindly
996 &apos;&apos;&apos; each of them might be a scalar of any type or a subarray
997 &apos;&apos;&apos; Args
998 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
999 &apos;&apos;&apos; Before: the index before which to insert; must be in the interval [LBound, UBound + 1]
1000 &apos;&apos;&apos; pvArgs: a list of items to Insert inside Array_1D
1001 &apos;&apos;&apos; Returns:
1002 &apos;&apos;&apos; the new rxtended array. Its LBound is identical to that of Array_1D
1003 &apos;&apos;&apos; Exceptions:
1004 &apos;&apos;&apos; ARRAYINSERTERROR
1005 &apos;&apos;&apos; Examples:
1006 &apos;&apos;&apos; SF_Array.Insert(Array(1, 2, 3), 2, 4, 5) returns (1, 2, 4, 5, 3)
1008 Dim vInsert As Variant &apos; Return value
1009 Dim lNbArgs As Long &apos; Number of elements to Insert
1010 Dim lMin As Long &apos; LBound of input array
1011 Dim lMax As Long &apos; UBound of input array
1012 Dim i As Long
1013 Const cstThisSub = &quot;Array.Insert&quot;
1014 Const cstSubArgs = &quot;Array_1D, Before, arg0[, arg1] ...&quot;
1016 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1017 vInsert = Array()
1019 Check:
1020 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1021 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1022 If Not SF_Utils._Validate(Before, &quot;Before&quot;, V_NUMERIC) Then GoTo Finally
1023 If Before &lt; LBound(Array_1D) Or Before &gt; UBound(Array_1D) + 1 Then GoTo CatchArgument
1024 End If
1026 Try:
1027 lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
1028 lMin = LBound(Array_1D) &apos; = LBound(vInsert)
1029 lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vInsert)
1030 If lNbArgs &gt; 0 Then
1031 ReDim vInsert(lMin To lMax + lNbArgs)
1032 For i = lMin To UBound(vInsert)
1033 If i &lt; Before Then
1034 vInsert(i) = Array_1D(i)
1035 ElseIf i &lt; Before + lNbArgs Then
1036 vInsert(i) = pvArgs(i - Before)
1037 Else
1038 vInsert(i) = Array_1D(i - lNbArgs)
1039 End If
1040 Next i
1041 Else
1042 vInsert() = Array_1D()
1043 End If
1045 Finally:
1046 Insert = vInsert()
1047 SF_Utils._ExitFunction(cstThisSub)
1048 Exit Function
1049 Catch:
1050 GoTo Finally
1051 CatchArgument:
1052 &apos;TODO SF_Exception.RaiseFatal(ARRAYINSERTERROR, cstThisSub)
1053 MsgBox &quot;INVALID ARGUMENT VALUE !!&quot;
1054 GoTo Finally
1055 End Function &apos; ScriptForge.SF_Array.Insert
1057 REM -----------------------------------------------------------------------------
1058 Public Function InsertSorted(Optional ByRef Array_1D As Variant _
1059 , Optional ByVal Item As Variant _
1060 , Optional ByVal SortOrder As Variant _
1061 , Optional ByVal CaseSensitive As Variant _
1062 ) As Variant
1063 &apos;&apos;&apos; Insert in a sorted array a new item on its place
1064 &apos;&apos;&apos; the array must be filled homogeneously, i.e. all items must be of the same type
1065 &apos;&apos;&apos; Empty and Null items are forbidden
1066 &apos;&apos;&apos; Args:
1067 &apos;&apos;&apos; Array_1D: the array to sort
1068 &apos;&apos;&apos; Item: the scalar value to insert, same type as the existing array items
1069 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1070 &apos;&apos;&apos; CaseSensitive: Default = False
1071 &apos;&apos;&apos; Returns: the extended sorted array with same LBound as input array
1072 &apos;&apos;&apos; Examples:
1073 &apos;&apos;&apos; InsertSorted(Array(&quot;A&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;), &quot;B&quot;, CaseSensitive := True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;)
1075 Dim vSorted() As Variant &apos; Return value
1076 Dim iType As Integer &apos; VarType of elements in input array
1077 Dim lMin As Long &apos; LBound of input array
1078 Dim lMax As Long &apos; UBound of input array
1079 Dim lIndex As Long &apos; Place where to insert new item
1080 Const cstThisSub = &quot;Array.InsertSorted&quot;
1081 Const cstSubArgs = &quot;Array_1D, Item, [SortOrder=&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1083 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1084 vSorted = Array()
1086 Check:
1087 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1088 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1089 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1090 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
1091 If LBound(Array_1D) &lt;= UBound(Array_1D) Then
1092 iType = SF_Utils._VarTypeExt(Array_1D(LBound(Array_1D)))
1093 If Not SF_Utils._Validate(Item, &quot;Item&quot;, iType) Then GoTo Finally
1094 Else
1095 If Not SF_Utils._Validate(Item, &quot;Item&quot;, Array(V_STRING, V_DATE, V_NUMERIC)) Then GoTo Finally
1096 End If
1097 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1098 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1099 End If
1101 Try:
1102 lMin = LBound(Array_1D)
1103 lMax = UBound(Array_1D)
1104 lIndex = SF_Array._FindItem(Array_1D, Item, CaseSensitive, SortOrder)(1)
1105 vSorted = SF_Array.Insert(Array_1D, lIndex, Item)
1107 Finally:
1108 InsertSorted = vSorted()
1109 SF_Utils._ExitFunction(cstThisSub)
1110 Exit Function
1111 Catch:
1112 GoTo Finally
1113 End Function &apos; ScriptForge.SF_Array.InsertSorted
1115 REM -----------------------------------------------------------------------------
1116 Public Function Intersection(Optional ByRef Array1_1D As Variant _
1117 , Optional ByRef Array2_1D As Variant _
1118 , Optional ByVal CaseSensitive As Variant _
1119 ) As Variant
1120 &apos;&apos;&apos; Build a set being the intersection of the two input arrays, i.e. items are contained in both arrays
1121 &apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
1122 &apos;&apos;&apos; Empty and Null items are forbidden
1123 &apos;&apos;&apos; The comparison between strings is case sensitive or not
1124 &apos;&apos;&apos; Args:
1125 &apos;&apos;&apos; Array1_1D: a 1st input array
1126 &apos;&apos;&apos; Array2_1D: a 2nd input array
1127 &apos;&apos;&apos; CaseSensitive: default = False
1128 &apos;&apos;&apos; Returns: a zero-based array containing unique items stored in both input arrays
1129 &apos;&apos;&apos; The output array is sorted in ascending order
1130 &apos;&apos;&apos; Examples:
1131 &apos;&apos;&apos; Intersection(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;C&quot;, &quot;b&quot;)
1133 Dim vIntersection() As Variant &apos; Return value
1134 Dim vSorted() As Variant &apos; The shortest input array after sort
1135 Dim iType As Integer &apos; VarType of elements in input arrays
1136 Dim lMin1 As Long &apos; LBound of 1st input array
1137 Dim lMax1 As Long &apos; UBound of 1st input array
1138 Dim lMin2 As Long &apos; LBound of 2nd input array
1139 Dim lMax2 As Long &apos; UBound of 2nd input array
1140 Dim lMin As Long &apos; LBound of unsorted array
1141 Dim lMax As Long &apos; UBound of unsorted array
1142 Dim iShortest As Integer &apos; 1 or 2 depending on shortest input array
1143 Dim lSize As Long &apos; Number of Intersection items
1144 Dim vItem As Variant &apos; One single item in the array
1145 Dim i As Long
1146 Const cstThisSub = &quot;Array.Intersection&quot;
1147 Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
1149 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1150 vIntersection = Array()
1152 Check:
1153 If IsMissing(CaseSensitive) Then CaseSensitive = False
1154 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1155 If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
1156 iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
1157 If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
1158 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1159 End If
1161 Try:
1162 lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
1163 lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
1165 &apos; If one of both arrays is empty, do nothing
1166 If lMax1 &gt;= lMin1 And lMax2 &gt;= lMin2 Then
1168 &apos; First sort the shortest array
1169 If lMax1 - lMin1 &lt;= lMax2 - lMin2 Then
1170 iShortest = 1
1171 vSorted = SF_Array.Sort(Array1_1D, &quot;ASC&quot;, CaseSensitive)
1172 lMin = lMin2 : lMax = lMax2 &apos; Bounds of unsorted array
1173 Else
1174 iShortest = 2
1175 vSorted = SF_Array.Sort(Array2_1D, &quot;ASC&quot;, CaseSensitive)
1176 lMin = lMin1 : lMax = lMax1 &apos; Bounds of unsorted array
1177 End If
1179 &apos; Resize the output array to the size of the shortest array
1180 ReDim vIntersection(0 To (lMax - lMin))
1181 lSize = -1
1183 &apos; Fill vIntersection one by one only with items present in both sets
1184 For i = lMin To lMax
1185 If iShortest = 1 Then vItem = Array2_1D(i) Else vItem = Array1_1D(i) &apos; Pick in unsorted array
1186 If SF_Array.Contains(vSorted, vItem, CaseSensitive, &quot;ASC&quot;) Then
1187 lSize = lSize + 1
1188 vIntersection(lSize) = vItem
1189 End If
1190 Next i
1192 &apos; Remove unfilled entries and duplicates
1193 If lSize &gt;= 0 Then
1194 ReDim Preserve vIntersection(0 To lSize)
1195 vIntersection() = SF_Array.Unique(vIntersection, CaseSensitive)
1196 Else
1197 vIntersection = Array()
1198 End If
1199 End If
1201 Finally:
1202 Intersection = vIntersection()
1203 SF_Utils._ExitFunction(cstThisSub)
1204 Exit Function
1205 Catch:
1206 GoTo Finally
1207 End Function &apos; ScriptForge.SF_Array.Intersection
1209 REM -----------------------------------------------------------------------------
1210 Public Function Join2D(Optional ByRef Array_2D As Variant _
1211 , Optional ByVal ColumnDelimiter As Variant _
1212 , Optional ByVal RowDelimiter As Variant _
1213 , Optional ByVal Quote As Variant _
1214 ) As String
1215 &apos;&apos;&apos; Join a two-dimensional array with two delimiters, one for columns, one for rows
1216 &apos;&apos;&apos; Args:
1217 &apos;&apos;&apos; Array_2D: each item must be either a String, a number, a Date or a Boolean
1218 &apos;&apos;&apos; ColumnDelimiter: delimits each column (default = Tab/Chr(9))
1219 &apos;&apos;&apos; RowDelimiter: delimits each row (default = LineFeed/Chr(10))
1220 &apos;&apos;&apos; Quote: if True, protect strings with double quotes (default = False)
1221 &apos;&apos;&apos; Return:
1222 &apos;&apos;&apos; A string after conversion of numbers and dates
1223 &apos;&apos;&apos; Invalid items are replaced by a zero-length string
1224 &apos;&apos;&apos; Examples:
1225 &apos;&apos;&apos; | 1, 2, &quot;A&quot;, [2020-02-29], 5 |
1226 &apos;&apos;&apos; SF_Array.Join_2D( | 6, 7, &quot;this is a string&quot;, 9, 10 | , &quot;,&quot;, &quot;/&quot;)
1227 &apos;&apos;&apos; &apos; &quot;1,2,A,2020-02-29 00:00:00,5/6,7,this is a string,9,10&quot;
1229 Dim sJoin As String &apos; The return value
1230 Dim sItem As String &apos; The string representation of a single item
1231 Dim vItem As Variant &apos; Single item
1232 Dim lMin1 As Long &apos; LBound1 of input array
1233 Dim lMax1 As Long &apos; UBound1 of input array
1234 Dim lMin2 As Long &apos; LBound2 of input array
1235 Dim lMax2 As Long &apos; UBound2 of input array
1236 Dim i As Long
1237 Dim j As Long
1238 Const cstThisSub = &quot;Array.Join2D&quot;
1239 Const cstSubArgs = &quot;Array_2D, [ColumnDelimiter=Chr(9)], [RowDelimiter=Chr(10)], [Quote=False]&quot;
1241 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1242 sJoin = &quot;&quot;
1244 Check:
1245 If IsMissing(ColumnDelimiter) Or IsEmpty(ColumnDelimiter) Then ColumnDelimiter = Chr(9)
1246 If IsMissing(RowDelimiter) Or IsEmpty(RowDelimiter) Then RowDelimiter = Chr(10)
1247 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1248 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
1249 If Not SF_Utils._Validate(ColumnDelimiter, &quot;ColumnDelimiter&quot;, V_STRING) Then GoTo Finally
1250 If Not SF_Utils._Validate(RowDelimiter, &quot;RowDelimiter&quot;, V_STRING) Then GoTo Finally
1251 If Not SF_Utils._Validate(Quote, &quot;Quote&quot;, V_BOOLEAN) Then GoTo Finally
1252 End If
1254 Try:
1255 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1256 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1257 If lMin1 &lt;= lMax1 Then
1258 For i = lMin1 To lMax1
1259 For j = lMin2 To lMax2
1260 vItem = Array_2D(i, j)
1261 Select Case SF_Utils._VarTypeExt(vItem)
1262 Case V_STRING : If Quote Then sItem = SF_String.Quote(vItem) Else sItem = vItem
1263 Case V_NUMERIC, V_DATE : sItem = SF_Utils._Repr(vItem)
1264 Case V_BOOLEAN : sItem = Iif(vItem, &quot;True&quot;, &quot;False&quot;) &apos;TODO: L10N
1265 Case Else : sItem = &quot;&quot;
1266 End Select
1267 sJoin = sJoin &amp; sItem &amp; Iif(j &lt; lMax2, ColumnDelimiter, &quot;&quot;)
1268 Next j
1269 sJoin = sJoin &amp; Iif(i &lt; lMax1, RowDelimiter, &quot;&quot;)
1270 Next i
1271 End If
1273 Finally:
1274 Join2D = sJoin
1275 SF_Utils._ExitFunction(cstThisSub)
1276 Exit Function
1277 Catch:
1278 GoTo Finally
1279 End Function &apos; ScriptForge.SF_Array.Join2D
1281 REM -----------------------------------------------------------------------------
1282 Public Function Methods() As Variant
1283 &apos;&apos;&apos; Return the list of public methods of the Array service as an array
1285 Methods = Array( _
1286 &quot;Append&quot; _
1287 , &quot;AppendColumn&quot; _
1288 , &quot;AppendRow&quot; _
1289 , &quot;Contains&quot; _
1290 , &quot;ConvertToDictionary&quot; _
1291 , &quot;CountDims&quot; _
1292 , &quot;Difference&quot; _
1293 , &quot;ExportToTextFile&quot; _
1294 , &quot;ExtractColumn&quot; _
1295 , &quot;ExtractRow&quot; _
1296 , &quot;Flatten&quot; _
1297 , &quot;ImportFromCSVFile&quot; _
1298 , &quot;IndexOf&quot; _
1299 , &quot;Insert&quot; _
1300 , &quot;InsertSorted&quot; _
1301 , &quot;Intersection&quot; _
1302 , &quot;Join2D&quot; _
1303 , &quot;Prepend&quot; _
1304 , &quot;PrependColumn&quot; _
1305 , &quot;PrependRow&quot; _
1306 , &quot;RangeInit&quot; _
1307 , &quot;Reverse&quot; _
1308 , &quot;Shuffle&quot; _
1309 , &quot;Sort&quot; _
1310 , &quot;SortColumns&quot; _
1311 , &quot;SortRows&quot; _
1312 , &quot;Transpose&quot; _
1313 , &quot;TrimArray&quot; _
1314 , &quot;Union&quot; _
1315 , &quot;Unique&quot; _
1318 End Function &apos; ScriptForge.SF_Array.Methods
1320 REM -----------------------------------------------------------------------------
1321 Public Function Prepend(Optional ByRef Array_1D As Variant _
1322 , ParamArray pvArgs() As Variant _
1323 ) As Variant
1324 &apos;&apos;&apos; Prepend at the beginning of the input array the items listed as arguments
1325 &apos;&apos;&apos; Arguments are Prepended blindly
1326 &apos;&apos;&apos; each of them might be a scalar of any type or a subarray
1327 &apos;&apos;&apos; Args
1328 &apos;&apos;&apos; Array_1D: the pre-existing array, may be empty
1329 &apos;&apos;&apos; pvArgs: a list of items to Prepend to Array_1D
1330 &apos;&apos;&apos; Return: the new rxtended array. Its LBound is identical to that of Array_1D
1331 &apos;&apos;&apos; Examples:
1332 &apos;&apos;&apos; SF_Array.Prepend(Array(1, 2, 3), 4, 5) returns (4, 5, 1, 2, 3)
1334 Dim vPrepend As Variant &apos; Return value
1335 Dim lNbArgs As Long &apos; Number of elements to Prepend
1336 Dim lMin As Long &apos; LBound of input array
1337 Dim lMax As Long &apos; UBound of input array
1338 Dim i As Long
1339 Const cstThisSub = &quot;Array.Prepend&quot;
1340 Const cstSubArgs = &quot;Array_1D, arg0[, arg1] ...&quot;
1342 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1343 vPrepend = Array()
1345 Check:
1346 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1347 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1348 End If
1350 Try:
1351 lNbArgs = UBound(pvArgs) + 1 &apos; pvArgs is always zero-based
1352 lMin = LBound(Array_1D) &apos; = LBound(vPrepend)
1353 lMax = UBound(Array_1D) &apos; &lt;&gt; UBound(vPrepend)
1354 If lMax &lt; LBound(Array_1D) And lNbArgs &gt; 0 Then &apos; Initial array is empty
1355 ReDim vPrepend(0 To lNbArgs - 1)
1356 Else
1357 ReDim vPrepend(lMin To lMax + lNbArgs)
1358 End If
1359 For i = lMin To UBound(vPrepend)
1360 If i &lt; lMin + lNbArgs Then vPrepend(i) = pvArgs(i - lMin) Else vPrepend(i) = Array_1D(i - lNbArgs)
1361 Next i
1363 Finally:
1364 Prepend = vPrepend
1365 SF_Utils._ExitFunction(cstThisSub)
1366 Exit Function
1367 Catch:
1368 GoTo Finally
1369 End Function &apos; ScriptForge.SF_Array.Prepend
1371 REM -----------------------------------------------------------------------------
1372 Public Function PrependColumn(Optional ByRef Array_2D As Variant _
1373 , Optional ByRef Column As Variant _
1374 ) As Variant
1375 &apos;&apos;&apos; PrependColumn prepends to the left side of a 2D array a new Column
1376 &apos;&apos;&apos; Args
1377 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
1378 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the last Column of the resulting 2D array
1379 &apos;&apos;&apos; Column: a 1D array with as many items as there are rows in Array_2D
1380 &apos;&apos;&apos; Returns:
1381 &apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
1382 &apos;&apos;&apos; Exceptions:
1383 &apos;&apos;&apos; ARRAYINSERTERROR
1384 &apos;&apos;&apos; Examples:
1385 &apos;&apos;&apos; SF_Array.PrependColumn(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 1), (5, 2), (6, 3))
1386 &apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3)) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(0, i) ≡ i
1388 Dim vPrependColumn As Variant &apos; Return value
1389 Dim iDims As Integer &apos; Dimensions of Array_2D
1390 Dim lMin1 As Long &apos; LBound1 of input array
1391 Dim lMax1 As Long &apos; UBound1 of input array
1392 Dim lMin2 As Long &apos; LBound2 of input array
1393 Dim lMax2 As Long &apos; UBound2 of input array
1394 Dim lMin As Long &apos; LBound of Column array
1395 Dim lMax As Long &apos; UBound of Column array
1396 Dim i As Long
1397 Dim j As Long
1398 Const cstThisSub = &quot;Array.PrependColumn&quot;
1399 Const cstSubArgs = &quot;Array_2D, Column&quot;
1401 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1402 vPrependColumn = Array()
1404 Check:
1405 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1406 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
1407 If Not SF_Utils._ValidateArray(Column, &quot;Column&quot;, 1) Then GoTo Finally
1408 End If
1409 iDims = SF_Array.CountDims(Array_2D)
1410 If iDims &gt; 2 Then
1411 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
1412 End If
1414 Try:
1415 lMin = LBound(Column)
1416 lMax = UBound(Column)
1418 &apos; Compute future dimensions of output array
1419 Select Case iDims
1420 Case 0 : lMin1 = lMin : lMax1 = lMax
1421 lMin2 = 0 : lMax2 = -1
1422 Case 1 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1423 lMin2 = 0 : lMax2 = 0
1424 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1425 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1426 End Select
1427 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax1 - lMin1 Then GoTo CatchColumn
1428 ReDim vPrependColumn(lMin1 To lMax1, lMin2 To lMax2 + 1)
1430 &apos; Copy input array to output array
1431 For i = lMin1 To lMax1
1432 For j = lMin2 + 1 To lMax2 + 1
1433 If iDims = 2 Then vPrependColumn(i, j) = Array_2D(i, j - 1) Else vPrependColumn(i, j) = Array_2D(i)
1434 Next j
1435 Next i
1436 &apos; Copy new Column
1437 For i = lMin1 To lMax1
1438 vPrependColumn(i, lMin2) = Column(i)
1439 Next i
1441 Finally:
1442 PrependColumn = vPrependColumn()
1443 SF_Utils._ExitFunction(cstThisSub)
1444 Exit Function
1445 Catch:
1446 GoTo Finally
1447 CatchColumn:
1448 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Column&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Column, MAXREPR))
1449 GoTo Finally
1450 End Function &apos; ScriptForge.SF_Array.PrependColumn
1452 REM -----------------------------------------------------------------------------
1453 Public Function PrependRow(Optional ByRef Array_2D As Variant _
1454 , Optional ByRef Row As Variant _
1455 ) As Variant
1456 &apos;&apos;&apos; PrependRow prepends on top of a 2D array a new row
1457 &apos;&apos;&apos; Args
1458 &apos;&apos;&apos; Array_2D: the pre-existing array, may be empty
1459 &apos;&apos;&apos; If the array has 1 dimension, it is considered as the last row of the resulting 2D array
1460 &apos;&apos;&apos; Row: a 1D array with as many items as there are columns in Array_2D
1461 &apos;&apos;&apos; Returns:
1462 &apos;&apos;&apos; the new rxtended array. Its LBounds are identical to that of Array_2D
1463 &apos;&apos;&apos; Exceptions:
1464 &apos;&apos;&apos; ARRAYINSERTERROR
1465 &apos;&apos;&apos; Examples:
1466 &apos;&apos;&apos; SF_Array.PrependRow(Array(1, 2, 3), Array(4, 5, 6)) returns ((4, 5, 6), (1, 2, 3))
1467 &apos;&apos;&apos; x = SF_Array.PrependColumn(Array(), Array(1, 2, 3) =&gt; ∀ i ∈ {0 ≤ i ≤ 2} : x(i, 0) ≡ i
1469 Dim vPrependRow As Variant &apos; Return value
1470 Dim iDims As Integer &apos; Dimensions of Array_2D
1471 Dim lMin1 As Long &apos; LBound1 of input array
1472 Dim lMax1 As Long &apos; UBound1 of input array
1473 Dim lMin2 As Long &apos; LBound2 of input array
1474 Dim lMax2 As Long &apos; UBound2 of input array
1475 Dim lMin As Long &apos; LBound of row array
1476 Dim lMax As Long &apos; UBound of row array
1477 Dim i As Long
1478 Dim j As Long
1479 Const cstThisSub = &quot;Array.PrependRow&quot;
1480 Const cstSubArgs = &quot;Array_2D, Row&quot;
1482 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1483 vPrependRow = Array()
1485 Check:
1486 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1487 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;) Then GoTo Finally &apos;Initial check: not missing and array
1488 If Not SF_Utils._ValidateArray(Row, &quot;Row&quot;, 1) Then GoTo Finally
1489 End If
1490 iDims = SF_Array.CountDims(Array_2D)
1491 If iDims &gt; 2 Then
1492 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally &apos;2nd check to manage error
1493 End If
1495 Try:
1496 lMin = LBound(Row)
1497 lMax = UBound(Row)
1499 &apos; Compute future dimensions of output array
1500 Select Case iDims
1501 Case 0 : lMin1 = 0 : lMax1 = -1
1502 lMin2 = lMin : lMax2 = lMax
1503 Case 1 : lMin1 = 0 : lMax1 = 0
1504 lMin2 = LBound(Array_2D, 1) : lMax2 = UBound(Array_2D, 1)
1505 Case 2 : lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1506 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1507 End Select
1508 If iDims &gt; 0 And lMax - lMin &lt;&gt; lMax2 - lMin2 Then GoTo CatchRow
1509 ReDim vPrependRow(lMin1 To lMax1 + 1, lMin2 To lMax2)
1511 &apos; Copy input array to output array
1512 For i = lMin1 + 1 To lMax1 + 1
1513 For j = lMin2 To lMax2
1514 If iDims = 2 Then vPrependRow(i, j) = Array_2D(i - 1, j) Else vPrependRow(i, j) = Array_2D(j)
1515 Next j
1516 Next i
1517 &apos; Copy new row
1518 For j = lMin2 To lMax2
1519 vPrependRow(lMin1, j) = Row(j)
1520 Next j
1522 Finally:
1523 PrependRow = vPrependRow()
1524 SF_Utils._ExitFunction(cstThisSub)
1525 Exit Function
1526 Catch:
1527 GoTo Finally
1528 CatchRow:
1529 SF_Exception.RaiseFatal(ARRAYINSERTERROR, &quot;Row&quot;, SF_Array._Repr(Array_2D), SF_Utils._Repr(Row, MAXREPR))
1530 GoTo Finally
1531 End Function &apos; ScriptForge.SF_Array.PrependRow
1533 REM -----------------------------------------------------------------------------
1534 Public Function Properties() As Variant
1535 &apos;&apos;&apos; Return the list or properties as an array
1537 Properties = Array( _
1540 End Function &apos; ScriptForge.SF_Array.Properties
1542 REM -----------------------------------------------------------------------------
1543 Public Function RangeInit(Optional ByVal From As Variant _
1544 , Optional ByVal UpTo As Variant _
1545 , Optional ByVal ByStep As Variant _
1546 ) As Variant
1547 &apos;&apos;&apos; Initialize a new zero-based array with numeric values
1548 &apos;&apos;&apos; Args: all numeric
1549 &apos;&apos;&apos; From: value of first item
1550 &apos;&apos;&apos; UpTo: last item should not exceed UpTo
1551 &apos;&apos;&apos; ByStep: difference between 2 successive items
1552 &apos;&apos;&apos; Return: the new array
1553 &apos;&apos;&apos; Exceptions:
1554 &apos;&apos;&apos; ARRAYSEQUENCEERROR Wrong arguments, f.i. UpTo &lt; From with ByStep &gt; 0
1555 &apos;&apos;&apos; Examples:
1556 &apos;&apos;&apos; SF_Array.RangeInit(10, 1, -1) returns (10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
1558 Dim lIndex As Long &apos; Index of array
1559 Dim lSize As Long &apos; UBound of resulting array
1560 Dim vCurrentItem As Variant &apos; Last stored item
1561 Dim vArray() &apos; The return value
1562 Const cstThisSub = &quot;Array.RangeInit&quot;
1563 Const cstSubArgs = &quot;From, UpTo, [ByStep = 1]&quot;
1565 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1566 vArray = Array()
1568 Check:
1569 If IsMissing(ByStep) Or IsEmpty(ByStep) Then ByStep = 1
1570 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1571 If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
1572 If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
1573 If Not SF_Utils._Validate(ByStep, &quot;ByStep&quot;, V_NUMERIC) Then GoTo Finally
1574 End If
1575 If (From &lt; UpTo And ByStep &lt;= 0) Or (From &gt; UpTo And ByStep &gt;= 0) Then GoTo CatchSequence
1577 Try:
1578 lSize = CLng(Abs((UpTo - From) / ByStep))
1579 ReDim vArray(0 To lSize)
1580 For lIndex = 0 To lSize
1581 vArray(lIndex) = From + lIndex * ByStep
1582 Next lIndex
1584 Finally:
1585 RangeInit = vArray
1586 SF_Utils._ExitFunction(cstThisSub)
1587 Exit Function
1588 Catch:
1589 GoTo Finally
1590 CatchSequence:
1591 SF_Exception.RaiseFatal(ARRAYSEQUENCEERROR, From, UpTo, ByStep)
1592 GoTo Finally
1593 End Function &apos; ScriptForge.SF_Array.RangeInit
1595 REM -----------------------------------------------------------------------------
1596 Public Function Reverse(Optional ByRef Array_1D As Variant) As Variant
1597 &apos;&apos;&apos; Return the reversed 1D input array
1598 &apos;&apos;&apos; Args:
1599 &apos;&apos;&apos; Array_1D: the array to reverse
1600 &apos;&apos;&apos; Returns: the reversed array
1601 &apos;&apos;&apos; Examples:
1602 &apos;&apos;&apos; SF_Array.Reverse(Array(1, 2, 3, 4)) returns (4, 3, 2, 1)
1604 Dim vReverse() As Variant &apos; Return value
1605 Dim lHalf As Long &apos; Middle of array
1606 Dim lMin As Long &apos; LBound of input array
1607 Dim lMax As Long &apos; UBound of input array
1608 Dim i As Long, j As Long
1609 Const cstThisSub = &quot;Array.Reverse&quot;
1610 Const cstSubArgs = &quot;Array_1D&quot;
1612 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1613 vReverse = Array()
1615 Check:
1616 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1617 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1618 End If
1620 Try:
1621 lMin = LBound(Array_1D)
1622 lMax = UBound(Array_1D)
1623 ReDim vReverse(lMin To lMax)
1624 lHalf = Int((lMax + lMin) / 2)
1625 j = lMax
1626 For i = lMin To lHalf
1627 vReverse(i) = Array_1D(j)
1628 vReverse(j) = Array_1D(i)
1629 j = j - 1
1630 Next i
1631 &apos; Odd number of items
1632 If IsEmpty(vReverse(lHalf + 1)) Then vReverse(lHalf + 1) = Array_1D(lHalf + 1)
1634 Finally:
1635 Reverse = vReverse()
1636 SF_Utils._ExitFunction(cstThisSub)
1637 Exit Function
1638 Catch:
1639 GoTo Finally
1640 End Function &apos; ScriptForge.SF_Array.Reverse
1642 REM -----------------------------------------------------------------------------
1643 Public Function SetProperty(Optional ByVal PropertyName As Variant _
1644 , Optional ByRef Value As Variant _
1645 ) As Boolean
1646 &apos;&apos;&apos; Set a new value to the given property
1647 &apos;&apos;&apos; Args:
1648 &apos;&apos;&apos; PropertyName: the name of the property as a string
1649 &apos;&apos;&apos; Value: its new value
1650 &apos;&apos;&apos; Exceptions
1651 &apos;&apos;&apos; ARGUMENTERROR The property does not exist
1653 Const cstThisSub = &quot;Array.SetProperty&quot;
1654 Const cstSubArgs = &quot;PropertyName, Value&quot;
1656 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1657 SetProperty = False
1659 Check:
1660 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1661 If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
1662 End If
1664 Try:
1665 Select Case UCase(PropertyName)
1666 Case Else
1667 End Select
1669 Finally:
1670 SF_Utils._ExitFunction(cstThisSub)
1671 Exit Function
1672 Catch:
1673 GoTo Finally
1674 End Function &apos; ScriptForge.SF_Array.SetProperty
1676 REM -----------------------------------------------------------------------------
1677 Public Function Shuffle(Optional ByRef Array_1D As Variant) As Variant
1678 &apos;&apos;&apos; Returns a random permutation of a 1D array
1679 &apos;&apos;&apos; https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
1680 &apos;&apos;&apos; Args:
1681 &apos;&apos;&apos; Array_1D: the array to shuffle
1682 &apos;&apos;&apos; Returns: the shuffled array
1684 Dim vShuffle() As Variant &apos; Return value
1685 Dim vSwapValue As Variant &apos; Intermediate value during swap
1686 Dim lMin As Long &apos; LBound of Array_1D
1687 Dim lCurrentIndex As Long &apos; Decremented from UBount to LBound
1688 Dim lRandomIndex As Long &apos; Random between LBound and lCurrentIndex
1689 Dim i As Long
1690 Const cstThisSub = &quot;Array.Shuffle&quot;
1691 Const cstSubArgs = &quot;Array_1D&quot;
1693 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1694 vShuffle = Array()
1696 Check:
1697 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1698 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1699 End If
1701 Try:
1702 lMin = LBound(Array_1D)
1703 lCurrentIndex = UBound(array_1D)
1704 &apos; Initialize the output array
1705 ReDim vShuffle(lMin To lCurrentIndex)
1706 For i = lMin To lCurrentIndex
1707 vShuffle(i) = Array_1D(i)
1708 Next i
1709 &apos; Now ... shuffle !
1710 Do While lCurrentIndex &gt; lMin
1711 lRandomIndex = Int(Rnd * (lCurrentIndex - lMin)) + lMin
1712 vSwapValue = vShuffle(lCurrentIndex)
1713 vShuffle(lCurrentIndex) = vShuffle(lRandomIndex)
1714 vShuffle(lRandomIndex) = vSwapValue
1715 lCurrentIndex = lCurrentIndex - 1
1716 Loop
1718 Finally:
1719 Shuffle = vShuffle()
1720 SF_Utils._ExitFunction(cstThisSub)
1721 Exit Function
1722 Catch:
1723 GoTo Finally
1724 End Function &apos; ScriptForge.SF_Array.Shuffle
1726 REM -----------------------------------------------------------------------------
1727 Public Function Slice(Optional ByRef Array_1D As Variant _
1728 , Optional ByVal From As Variant _
1729 , Optional ByVal UpTo As Variant _
1730 ) As Variant
1731 &apos;&apos;&apos; Returns a subset of a 1D array
1732 &apos;&apos;&apos; Args:
1733 &apos;&apos;&apos; Array_1D: the array to slice
1734 &apos;&apos;&apos; From: the lower index of the subarray to extract (included)
1735 &apos;&apos;&apos; UpTo: the upper index of the subarray to extract (included). Default = the last item of Array_1D
1736 &apos;&apos;&apos; Returns:
1737 &apos;&apos;&apos; The selected subarray with the same LBound as the input array.
1738 &apos;&apos;&apos; If UpTo &lt; From then the returned array is empty
1739 &apos;&apos;&apos; Exceptions:
1740 &apos;&apos;&apos; ARRAYINDEX2ERROR Wrong values for From and/or UpTo
1741 &apos;&apos;&apos; Example:
1742 &apos;&apos;&apos; SF_Array.Slice(Array(1, 2, 3, 4, 5), 1, 3) returns (2, 3, 4)
1744 Dim vSlice() As Variant &apos; Return value
1745 Dim lMin As Long &apos; LBound of Array_1D
1746 Dim lIndex As Long &apos; Current index in output array
1747 Dim i As Long
1748 Const cstThisSub = &quot;Array.Slice&quot;
1749 Const cstSubArgs = &quot;Array_1D, From, [UpTo = UBound(Array_1D)]&quot;
1751 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1752 vSlice = Array()
1754 Check:
1755 If IsMissing(UpTo) Or IsEmpty(UpTo) Then UpTo = -1
1756 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1757 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
1758 If Not SF_Utils._Validate(From, &quot;From&quot;, V_NUMERIC) Then GoTo Finally
1759 If Not SF_Utils._Validate(UpTo, &quot;UpTo&quot;, V_NUMERIC) Then GoTo Finally
1760 End If
1761 If UpTo = -1 Then UpTo = UBound(Array_1D)
1762 If From &lt; LBound(Array_1D) Or From &gt; UBound(Array_1D) _
1763 Or From &gt; UpTo Or UpTo &gt; UBound(Array_1D) Then GoTo CatchIndex
1765 Try:
1766 If UpTo &gt;= From Then
1767 lMin = LBound(Array_1D)
1768 &apos; Initialize the output array
1769 ReDim vSlice(lMin To lMin + UpTo - From)
1770 lIndex = lMin - 1
1771 For i = From To UpTo
1772 lIndex = lIndex + 1
1773 vSlice(lIndex) = Array_1D(i)
1774 Next i
1775 End If
1777 Finally:
1778 Slice = vSlice()
1779 SF_Utils._ExitFunction(cstThisSub)
1780 Exit Function
1781 Catch:
1782 GoTo Finally
1783 CatchIndex:
1784 SF_Exception.RaiseFatal(ARRAYINDEX2ERROR, SF_Array._Repr(Array_1D), From, UpTo)
1785 GoTo Finally
1786 End Function &apos; ScriptForge.SF_Array.Slice
1788 REM -----------------------------------------------------------------------------
1789 Public Function Sort(Optional ByRef Array_1D As Variant _
1790 , Optional ByVal SortOrder As Variant _
1791 , Optional ByVal CaseSensitive As Variant _
1792 ) As Variant
1793 &apos;&apos;&apos; Sort a 1D array in ascending or descending order. String comparisons can be case-sensitive or not
1794 &apos;&apos;&apos; Args:
1795 &apos;&apos;&apos; Array_1D: the array to sort
1796 &apos;&apos;&apos; must be filled homogeneously by either strings, dates or numbers
1797 &apos;&apos;&apos; Null and Empty values are allowed
1798 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1799 &apos;&apos;&apos; CaseSensitive: Default = False
1800 &apos;&apos;&apos; Returns: the sorted array
1801 &apos;&apos;&apos; Examples:
1802 &apos;&apos;&apos; Sort(Array(&quot;a&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;, &quot;C&quot;), CaseSensitive := True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;a&quot;, &quot;b&quot;)
1804 Dim vSort() As Variant &apos; Return value
1805 Dim vIndexes() As Variant &apos; Indexes of sorted items
1806 Dim lMin As Long &apos; LBound of input array
1807 Dim lMax As Long &apos; UBound of input array
1808 Dim i As Long
1809 Const cstThisSub = &quot;Array.Sort&quot;
1810 Const cstSubArgs = &quot;Array_1D, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1812 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1813 vSort = Array()
1815 Check:
1816 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1817 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1818 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1819 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0) Then GoTo Finally
1820 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1821 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1822 End If
1824 Try:
1825 lMin = LBound(Array_1D)
1826 lMax = UBound(Array_1D)
1827 vIndexes() = SF_Array._HeapSort(Array_1D, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
1829 &apos; Load output array
1830 ReDim vSort(lMin To lMax)
1831 For i = lMin To lMax
1832 vSort(i) = Array_1D(vIndexes(i))
1833 Next i
1835 Finally:
1836 Sort = vSort()
1837 SF_Utils._ExitFunction(cstThisSub)
1838 Exit Function
1839 Catch:
1840 GoTo Finally
1841 End Function &apos; ScriptForge.SF_Array.Sort
1843 REM -----------------------------------------------------------------------------
1844 Public Function SortColumns(Optional ByRef Array_2D As Variant _
1845 , Optional ByVal RowIndex As Variant _
1846 , Optional ByVal SortOrder As Variant _
1847 , Optional ByVal CaseSensitive As Variant _
1848 ) As Variant
1849 &apos;&apos;&apos; Returns a permutation of the columns of a 2D array, sorted on the values of a given row
1850 &apos;&apos;&apos; Args:
1851 &apos;&apos;&apos; Array_2D: the input array
1852 &apos;&apos;&apos; RowIndex: the index of the row to sort the columns on
1853 &apos;&apos;&apos; the row must be filled homogeneously by either strings, dates or numbers
1854 &apos;&apos;&apos; Null and Empty values are allowed
1855 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1856 &apos;&apos;&apos; CaseSensitive: Default = False
1857 &apos;&apos;&apos; Returns:
1858 &apos;&apos;&apos; the array with permuted columns, LBounds and UBounds are unchanged
1859 &apos;&apos;&apos; Exceptions:
1860 &apos;&apos;&apos; ARRAYINDEXERROR
1861 &apos;&apos;&apos; Examples:
1862 &apos;&apos;&apos; | 5, 7, 3 | | 7, 5, 3 |
1863 &apos;&apos;&apos; SF_Array.SortColumns( | 1, 9, 5 |, 2, &quot;ASC&quot;) returns | 9, 1, 5 |
1864 &apos;&apos;&apos; | 6, 1, 8 | | 1, 6, 8 |
1866 Dim vSort() As Variant &apos; Return value
1867 Dim vRow() As Variant &apos; The row on which to sort the array
1868 Dim vIndexes() As Variant &apos; Indexes of sorted row
1869 Dim lMin1 As Long &apos; LBound1 of input array
1870 Dim lMax1 As Long &apos; UBound1 of input array
1871 Dim lMin2 As Long &apos; LBound2 of input array
1872 Dim lMax2 As Long &apos; UBound2 of input array
1873 Dim i As Long, j As Long
1874 Const cstThisSub = &quot;Array.SortColumn&quot;
1875 Const cstSubArgs = &quot;Array_2D, RowIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1877 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1878 vSort = Array()
1880 Check:
1881 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1882 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1883 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1884 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
1885 If Not SF_Utils._Validate(RowIndex, &quot;RowIndex&quot;, V_NUMERIC) Then GoTo Finally
1886 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1887 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1888 End If
1890 Try:
1891 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1892 If RowIndex &lt; lMin1 Or RowIndex &gt; lMax1 Then GoTo CatchIndex
1893 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1895 &apos; Extract and sort the RowIndex-th row
1896 vRow = SF_Array.ExtractRow(Array_2D, RowIndex)
1897 If Not SF_Utils._ValidateArray(vRow, &quot;Row #&quot; &amp; CStr(RowIndex), 1, 0) Then GoTo Finally
1898 vIndexes() = SF_Array._HeapSort(vRow, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
1900 &apos; Load output array
1901 ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
1902 For i = lMin1 To lMax1
1903 For j = lMin2 To lMax2
1904 vSort(i, j) = Array_2D(i, vIndexes(j))
1905 Next j
1906 Next i
1908 Finally:
1909 SortColumns = vSort()
1910 SF_Utils._ExitFunction(cstThisSub)
1911 Exit Function
1912 Catch:
1913 GoTo Finally
1914 CatchIndex:
1915 &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
1916 MsgBox &quot;INVALID INDEX VALUE !!&quot;
1917 GoTo Finally
1918 End Function &apos; ScriptForge.SF_Array.SortColumns
1920 REM -----------------------------------------------------------------------------
1921 Public Function SortRows(Optional ByRef Array_2D As Variant _
1922 , Optional ByVal ColumnIndex As Variant _
1923 , Optional ByVal SortOrder As Variant _
1924 , Optional ByVal CaseSensitive As Variant _
1925 ) As Variant
1926 &apos;&apos;&apos; Returns a permutation of the rows of a 2D array, sorted on the values of a given column
1927 &apos;&apos;&apos; Args:
1928 &apos;&apos;&apos; Array_2D: the input array
1929 &apos;&apos;&apos; ColumnIndex: the index of the column to sort the rows on
1930 &apos;&apos;&apos; the column must be filled homogeneously by either strings, dates or numbers
1931 &apos;&apos;&apos; Null and Empty values are allowed
1932 &apos;&apos;&apos; SortOrder: &quot;ASC&quot; (default) or &quot;DESC&quot;
1933 &apos;&apos;&apos; CaseSensitive: Default = False
1934 &apos;&apos;&apos; Returns:
1935 &apos;&apos;&apos; the array with permuted Rows, LBounds and UBounds are unchanged
1936 &apos;&apos;&apos; Exceptions:
1937 &apos;&apos;&apos; ARRAYINDEXERROR
1938 &apos;&apos;&apos; Examples:
1939 &apos;&apos;&apos; | 5, 7, 3 | | 1, 9, 5 |
1940 &apos;&apos;&apos; SF_Array.SortRows( | 1, 9, 5 |, 0, &quot;ASC&quot;) returns | 5, 7, 3 |
1941 &apos;&apos;&apos; | 6, 1, 8 | | 6, 1, 8 |
1943 Dim vSort() As Variant &apos; Return value
1944 Dim vCol() As Variant &apos; The column on which to sort the array
1945 Dim vIndexes() As Variant &apos; Indexes of sorted row
1946 Dim lMin1 As Long &apos; LBound1 of input array
1947 Dim lMax1 As Long &apos; UBound1 of input array
1948 Dim lMin2 As Long &apos; LBound2 of input array
1949 Dim lMax2 As Long &apos; UBound2 of input array
1950 Dim i As Long, j As Long
1951 Const cstThisSub = &quot;Array.SortRow&quot;
1952 Const cstSubArgs = &quot;Array_2D, ColumnIndex, [SortOrder=&quot;&quot;&quot;&quot;|&quot;&quot;ASC&quot;&quot;|&quot;&quot;DESC&quot;&quot;], [CaseSensitive=False]&quot;
1954 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
1955 vSort = Array()
1957 Check:
1958 If IsMissing(SortOrder) Or IsEmpty(SortOrder) Then SortOrder = &quot;ASC&quot;
1959 If IsMissing(CaseSensitive) Or IsEmpty(CaseSensitive) Then CaseSensitive = False
1960 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
1961 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
1962 If Not SF_Utils._Validate(ColumnIndex, &quot;ColumnIndex&quot;, V_NUMERIC) Then GoTo Finally
1963 If Not SF_Utils._Validate(SortOrder, &quot;SortOrder&quot;, V_STRING, Array(&quot;ASC&quot;,&quot;DESC&quot;)) Then GoTo Finally
1964 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
1965 End If
1967 Try:
1968 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
1969 If ColumnIndex &lt; lMin2 Or ColumnIndex &gt; lMax2 Then GoTo CatchIndex
1970 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
1972 &apos; Extract and sort the ColumnIndex-th column
1973 vCol = SF_Array.ExtractColumn(Array_2D, ColumnIndex)
1974 If Not SF_Utils._ValidateArray(vCol, &quot;Column #&quot; &amp; CStr(ColumnIndex), 1, 0) Then GoTo Finally
1975 vIndexes() = SF_Array._HeapSort(vCol, ( SortOrder = &quot;ASC&quot; ), CaseSensitive)
1977 &apos; Load output array
1978 ReDim vSort(lMin1 To lMax1, lMin2 To lMax2)
1979 For i = lMin1 To lMax1
1980 For j = lMin2 To lMax2
1981 vSort(i, j) = Array_2D(vIndexes(i), j)
1982 Next j
1983 Next i
1985 Finally:
1986 SortRows = vSort()
1987 SF_Utils._ExitFunction(cstThisSub)
1988 Exit Function
1989 Catch:
1990 GoTo Finally
1991 CatchIndex:
1992 &apos;TODO SF_Exception.RaiseFatal(ARRAYINDEXERROR, cstThisSub)
1993 MsgBox &quot;INVALID INDEX VALUE !!&quot;
1994 GoTo Finally
1995 End Function &apos; ScriptForge.SF_Array.SortRows
1997 REM -----------------------------------------------------------------------------
1998 Public Function Transpose(Optional ByRef Array_2D As Variant) As Variant
1999 &apos;&apos;&apos; Swaps rows and columns in a 2D array
2000 &apos;&apos;&apos; Args:
2001 &apos;&apos;&apos; Array_2D: the array to transpose
2002 &apos;&apos;&apos; Returns:
2003 &apos;&apos;&apos; The transposed array
2004 &apos;&apos;&apos; Examples:
2005 &apos;&apos;&apos; | 1, 2 | | 1, 3, 5 |
2006 &apos;&apos;&apos; SF_Array.Transpose( | 3, 4 | ) returns | 2, 4, 6 |
2007 &apos;&apos;&apos; | 5, 6 |
2009 Dim vTranspose As Variant &apos; Return value
2010 Dim lIndex As Long &apos; vTranspose index
2011 Dim lMin1 As Long &apos; LBound1 of input array
2012 Dim lMax1 As Long &apos; UBound1 of input array
2013 Dim lMin2 As Long &apos; LBound2 of input array
2014 Dim lMax2 As Long &apos; UBound2 of input array
2015 Dim i As Long, j As Long
2016 Const cstThisSub = &quot;Array.Transpose&quot;
2017 Const cstSubArgs = &quot;Array_2D&quot;
2019 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2020 vTranspose = Array()
2022 Check:
2023 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2024 If Not SF_Utils._ValidateArray(Array_2D, &quot;Array_2D&quot;, 2) Then GoTo Finally
2025 End If
2027 Try:
2028 &apos; Resize the output array
2029 lMin1 = LBound(Array_2D, 1) : lMax1 = UBound(Array_2D, 1)
2030 lMin2 = LBound(Array_2D, 2) : lMax2 = UBound(Array_2D, 2)
2031 If lMin1 &lt;= lMax1 Then
2032 ReDim vTranspose(lMin2 To lMax2, lMin1 To lMax1)
2033 End If
2035 &apos; Transpose items
2036 For i = lMin1 To lMax1
2037 For j = lMin2 To lMax2
2038 vTranspose(j, i) = Array_2D(i, j)
2039 Next j
2040 Next i
2042 Finally:
2043 Transpose = vTranspose
2044 SF_Utils._ExitFunction(cstThisSub)
2045 Exit Function
2046 Catch:
2047 GoTo Finally
2048 End Function &apos; ScriptForge.SF_Array.Transpose
2050 REM -----------------------------------------------------------------------------
2051 Public Function TrimArray(Optional ByRef Array_1D As Variant) As Variant
2052 &apos;&apos;&apos; Remove from a 1D array all Null, Empty and zero-length entries
2053 &apos;&apos;&apos; Strings are trimmed as well
2054 &apos;&apos;&apos; Args:
2055 &apos;&apos;&apos; Array_1D: the array to scan
2056 &apos;&apos;&apos; Return: The trimmed array
2057 &apos;&apos;&apos; Examples:
2058 &apos;&apos;&apos; SF_Array.TrimArray(Array(&quot;A&quot;,&quot;B&quot;,Null,&quot; D &quot;)) returns (&quot;A&quot;,&quot;B&quot;,&quot;D&quot;)
2060 Dim vTrimArray As Variant &apos; Return value
2061 Dim lIndex As Long &apos; vTrimArray index
2062 Dim lMin As Long &apos; LBound of input array
2063 Dim lMax As Long &apos; UBound of input array
2064 Dim vItem As Variant &apos; Single array item
2065 Dim i As Long
2066 Const cstThisSub = &quot;Array.TrimArray&quot;
2067 Const cstSubArgs = &quot;Array_1D&quot;
2069 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2070 vTrimArray = Array()
2072 Check:
2073 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2074 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1) Then GoTo Finally
2075 End If
2077 Try:
2078 lMin = LBound(Array_1D)
2079 lMax = UBound(Array_1D)
2080 If lMin &lt;= lMax Then
2081 ReDim vTrimArray(lMin To lMax)
2082 End If
2083 lIndex = lMin - 1
2085 &apos; Load only valid items from Array_1D to vTrimArray
2086 For i = lMin To lMax
2087 vItem = Array_1D(i)
2088 Select Case VarType(vItem)
2089 Case V_EMPTY
2090 Case V_NULL : vItem = Empty
2091 Case V_STRING
2092 vItem = Trim(vItem)
2093 If Len(vItem) = 0 Then vItem = Empty
2094 Case Else
2095 End Select
2096 If Not IsEmpty(vItem) Then
2097 lIndex = lIndex + 1
2098 vTrimArray(lIndex) = vItem
2099 End If
2100 Next i
2102 &apos;Keep valid entries
2103 If lMin &lt;= lIndex Then
2104 ReDim Preserve vTrimArray(lMin To lIndex)
2105 Else
2106 vTrimArray = Array()
2107 End If
2109 Finally:
2110 TrimArray = vTrimArray
2111 SF_Utils._ExitFunction(cstThisSub)
2112 Exit Function
2113 Catch:
2114 GoTo Finally
2115 End Function &apos; ScriptForge.SF_Array.TrimArray
2117 REM -----------------------------------------------------------------------------
2118 Public Function Union(Optional ByRef Array1_1D As Variant _
2119 , Optional ByRef Array2_1D As Variant _
2120 , Optional ByVal CaseSensitive As Variant _
2121 ) As Variant
2122 &apos;&apos;&apos; Build a set being the Union of the two input arrays, i.e. items are contained in any of both arrays
2123 &apos;&apos;&apos; both input arrays must be filled homogeneously, i.e. all items must be of the same type
2124 &apos;&apos;&apos; Empty and Null items are forbidden
2125 &apos;&apos;&apos; The comparison between strings is case sensitive or not
2126 &apos;&apos;&apos; Args:
2127 &apos;&apos;&apos; Array1_1D: a 1st input array
2128 &apos;&apos;&apos; Array2_1D: a 2nd input array
2129 &apos;&apos;&apos; CaseSensitive: default = False
2130 &apos;&apos;&apos; Returns: a zero-based array containing unique items stored in any of both input arrays
2131 &apos;&apos;&apos; The output array is sorted in ascending order
2132 &apos;&apos;&apos; Examples:
2133 &apos;&apos;&apos; SF_Array.Union(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), Array(&quot;C&quot;, &quot;Z&quot;, &quot;b&quot;), True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;Z&quot;, &quot;b&quot;)
2135 Dim vUnion() As Variant &apos; Return value
2136 Dim iType As Integer &apos; VarType of elements in input arrays
2137 Dim lMin1 As Long &apos; LBound of 1st input array
2138 Dim lMax1 As Long &apos; UBound of 1st input array
2139 Dim lMin2 As Long &apos; LBound of 2nd input array
2140 Dim lMax2 As Long &apos; UBound of 2nd input array
2141 Dim lSize As Long &apos; Number of Union items
2142 Dim i As Long
2143 Const cstThisSub = &quot;Array.Union&quot;
2144 Const cstSubArgs = &quot;Array1_1D, Array2_1D, [CaseSensitive=False]&quot;
2146 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2147 vUnion = Array()
2149 Check:
2150 If IsMissing(CaseSensitive) Then CaseSensitive = False
2151 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2152 If Not SF_Utils._ValidateArray(Array1_1D, &quot;Array1_1D&quot;, 1, 0, True) Then GoTo Finally
2153 iType = SF_Utils._VarTypeExt(Array1_1D(LBound(Array1_1D)))
2154 If Not SF_Utils._ValidateArray(Array2_1D, &quot;Array2_1D&quot;, 1, iType, True) Then GoTo Finally
2155 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
2156 End If
2158 Try:
2159 lMin1 = LBound(Array1_1D) : lMax1 = UBound(Array1_1D)
2160 lMin2 = LBound(Array2_1D) : lMax2 = UBound(Array2_1D)
2162 &apos; If both arrays are empty, do nothing
2163 If lMax1 &lt; lMin1 And lMax2 &lt; lMin2 Then
2164 ElseIf lMax1 &lt; lMin1 Then &apos; only 1st array is empty
2165 vUnion = SF_Array.Unique(Array2_1D, CaseSensitive)
2166 ElseIf lMax2 &lt; lMin2 Then &apos; only 2nd array is empty
2167 vUnion = SF_Array.Unique(Array1_1D, CaseSensitive)
2168 Else
2170 &apos; Build union of both arrays
2171 ReDim vUnion(0 To (lMax1 - lMin1) + (lMax2 - lMin2) + 1)
2172 lSize = -1
2174 &apos; Fill vUnion one by one only with items present in any set
2175 For i = lMin1 To lMax1
2176 lSize = lSize + 1
2177 vUnion(lSize) = Array1_1D(i)
2178 Next i
2179 For i = lMin2 To lMax2
2180 lSize = lSize + 1
2181 vUnion(lSize) = Array2_1D(i)
2182 Next i
2184 &apos; Remove duplicates
2185 vUnion() = SF_Array.Unique(vUnion, CaseSensitive)
2186 End If
2188 Finally:
2189 Union = vUnion()
2190 SF_Utils._ExitFunction(cstThisSub)
2191 Exit Function
2192 Catch:
2193 GoTo Finally
2194 End Function &apos; ScriptForge.SF_Array.Union
2196 REM -----------------------------------------------------------------------------
2197 Public Function Unique(Optional ByRef Array_1D As Variant _
2198 , Optional ByVal CaseSensitive As Variant _
2199 ) As Variant
2200 &apos;&apos;&apos; Build a set of unique values derived from the input array
2201 &apos;&apos;&apos; the input array must be filled homogeneously, i.e. all items must be of the same type
2202 &apos;&apos;&apos; Empty and Null items are forbidden
2203 &apos;&apos;&apos; The comparison between strings is case sensitive or not
2204 &apos;&apos;&apos; Args:
2205 &apos;&apos;&apos; Array_1D: the input array with potential duplicates
2206 &apos;&apos;&apos; CaseSensitive: default = False
2207 &apos;&apos;&apos; Returns: the array without duplicates with same LBound as input array
2208 &apos;&apos;&apos; The output array is sorted in ascending order
2209 &apos;&apos;&apos; Examples:
2210 &apos;&apos;&apos; Unique(Array(&quot;A&quot;, &quot;C&quot;, &quot;A&quot;, &quot;b&quot;, &quot;B&quot;), True) returns (&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;b&quot;)
2212 Dim vUnique() As Variant &apos; Return value
2213 Dim vSorted() As Variant &apos; The input array after sort
2214 Dim lMin As Long &apos; LBound of input array
2215 Dim lMax As Long &apos; UBound of input array
2216 Dim lUnique As Long &apos; Number of unique items
2217 Dim vIndex As Variant &apos; Output of _FindItem() method
2218 Dim vItem As Variant &apos; One single item in the array
2219 Dim i As Long
2220 Const cstThisSub = &quot;Array.Unique&quot;
2221 Const cstSubArgs = &quot;Array_1D, [CaseSensitive=False]&quot;
2223 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
2224 vUnique = Array()
2226 Check:
2227 If IsMissing(CaseSensitive) Then CaseSensitive = False
2228 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
2229 If Not SF_Utils._ValidateArray(Array_1D, &quot;Array_1D&quot;, 1, 0, True) Then GoTo Finally
2230 If Not SF_Utils._Validate(CaseSensitive, &quot;CaseSensitive&quot;, V_BOOLEAN) Then GoTo Finally
2231 End If
2233 Try:
2234 lMin = LBound(Array_1D)
2235 lMax = UBound(Array_1D)
2236 If lMax &gt;= lMin Then
2237 &apos; First sort the array
2238 vSorted = SF_Array.Sort(Array_1D, &quot;ASC&quot;, CaseSensitive)
2239 ReDim vUnique(lMin To lMax)
2240 lUnique = lMin
2241 &apos; Fill vUnique one by one ignoring duplicates
2242 For i = lMin To lMax
2243 vItem = vSorted(i)
2244 If i = lMin Then
2245 vUnique(i) = vItem
2246 Else
2247 If SF_Array._ValCompare(vItem, vSorted(i - 1), CaseSensitive) = 0 Then &apos; Ignore item
2248 Else
2249 lUnique = lUnique + 1
2250 vUnique(lUnique) = vItem
2251 End If
2252 End If
2253 Next i
2254 &apos; Remove unfilled entries
2255 ReDim Preserve vUnique(lMin To lUnique)
2256 End If
2258 Finally:
2259 Unique = vUnique()
2260 SF_Utils._ExitFunction(cstThisSub)
2261 Exit Function
2262 Catch:
2263 GoTo Finally
2264 End Function &apos; ScriptForge.SF_Array.Unique
2266 REM ============================================================= PRIVATE METHODS
2268 REM -----------------------------------------------------------------------------
2269 Public Function _FindItem(ByRef pvArray_1D As Variant _
2270 , ByVal pvToFind As Variant _
2271 , ByVal pbCaseSensitive As Boolean _
2272 , ByVal psSortOrder As String _
2273 ) As Variant
2274 &apos;&apos;&apos; Check if a 1D array contains the ToFind number, string or date and return its index
2275 &apos;&apos;&apos; The comparison between strings can be done case-sensitively or not
2276 &apos;&apos;&apos; If the array is sorted then a binary search is done
2277 &apos;&apos;&apos; Otherwise the array is scanned from top. Null or Empty items are simply ignored
2278 &apos;&apos;&apos; Args:
2279 &apos;&apos;&apos; pvArray_1D: the array to scan
2280 &apos;&apos;&apos; pvToFind: a number, a date or a string to find
2281 &apos;&apos;&apos; pbCaseSensitive: Only for string comparisons, default = False
2282 &apos;&apos;&apos; psSortOrder: &quot;ASC&quot;, &quot;DESC&quot; or &quot;&quot; (= not sorted, default)
2283 &apos;&apos;&apos; Return: a (0:1) array
2284 &apos;&apos;&apos; (0) = True when found
2285 &apos;&apos;&apos; (1) = if found: index of item
2286 &apos;&apos;&apos; if not found: if sorted, index of next item in the array (might be = UBound + 1)
2287 &apos;&apos;&apos; if not sorted, meaningless
2288 &apos;&apos;&apos; Result is unpredictable when array is announced sorted and is in reality not
2289 &apos;&apos;&apos; Called by Contains, IndexOf and InsertSorted. Also called by SF_Dictionary
2291 Dim bContains As Boolean &apos; True if match found
2292 Dim iToFindType As Integer &apos; VarType of pvToFind
2293 Dim lTop As Long, lBottom As Long &apos; Interval in scope of binary search
2294 Dim lIndex As Long &apos; Index used in search
2295 Dim iCompare As Integer &apos; Output of _ValCompare function
2296 Dim lLoops As Long &apos; Count binary searches
2297 Dim lMaxLoops As Long &apos; Max number of loops during binary search: to avoid infinite loops if array not sorted
2298 Dim vFound(1) As Variant &apos; Returned array (Contains, Index)
2300 bContains = False
2302 If LBound(pvArray_1D) &gt; UBound(pvArray_1D) Then &apos; Empty array, do nothing
2303 Else
2304 &apos; Search sequentially
2305 If Len(psSortOrder) = 0 Then
2306 For lIndex = LBound(pvArray_1D) To UBound(pvArray_1D)
2307 bContains = ( SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive) = 0 )
2308 If bContains Then Exit For
2309 Next lIndex
2310 Else
2311 &apos; Binary search
2312 If psSortOrder = &quot;ASC&quot; Then
2313 lTop = UBound(pvArray_1D)
2314 lBottom = lBound(pvArray_1D)
2315 Else
2316 lBottom = UBound(pvArray_1D)
2317 lTop = lBound(pvArray_1D)
2318 End If
2319 lLoops = 0
2320 lMaxLoops = CLng((Log(UBound(pvArray_1D) - LBound(pvArray_1D) + 1.0) / Log(2.0))) + 1
2322 lLoops = lLoops + 1
2323 lIndex = (lTop + lBottom) / 2
2324 iCompare = SF_Array._ValCompare(pvToFind, pvArray_1D(lIndex), pbCaseSensitive)
2325 Select Case True
2326 Case iCompare = 0 : bContains = True
2327 Case iCompare &lt; 0 And psSortOrder = &quot;ASC&quot;
2328 lTop = lIndex - 1
2329 Case iCompare &gt; 0 And psSortOrder = &quot;DESC&quot;
2330 lBottom = lIndex - 1
2331 Case iCompare &gt; 0 And psSortOrder = &quot;ASC&quot;
2332 lBottom = lIndex + 1
2333 Case iCompare &lt; 0 And psSortOrder = &quot;DESC&quot;
2334 lTop = lIndex + 1
2335 End Select
2336 Loop Until ( bContains ) Or ( lBottom &gt; lTop And psSortOrder = &quot;ASC&quot; ) Or (lBottom &lt; lTop And psSortOrder = &quot;DESC&quot; ) Or lLoops &gt; lMaxLoops
2337 &apos; Flag first next non-matching element
2338 If Not bContains Then lIndex = Iif(psSortOrder = &quot;ASC&quot;, lBottom, lTop)
2339 End If
2340 End If
2342 &apos; Build output array
2343 vFound(0) = bContains
2344 vFound(1) = lIndex
2345 _FindItem = vFound
2347 End Function &apos; ScriptForge.SF_Array._FindItem
2349 REM -----------------------------------------------------------------------------
2350 Private Function _HeapSort(ByRef pvArray As Variant _
2351 , Optional ByVal pbAscending As Boolean _
2352 , Optional ByVal pbCaseSensitive As Boolean _
2353 ) As Variant
2354 &apos;&apos;&apos; Sort an array: items are presumed all strings, all dates or all numeric
2355 &apos;&apos;&apos; Null or Empty are allowed and are considered smaller than other items
2356 &apos;&apos;&apos; https://en.wikipedia.org/wiki/Heapsort
2357 &apos;&apos;&apos; http://www.vbforums.com/showthread.php?473677-VB6-Sorting-algorithms-(sort-array-sorting-arrays)&amp;p=2909250#post2909250
2358 &apos;&apos;&apos; HeapSort preferred to QuickSort because not recursive (this routine returns an array of indexes !!)
2359 &apos;&apos;&apos; Args:
2360 &apos;&apos;&apos; pvArray: a 1D array
2361 &apos;&apos;&apos; pbAscending: default = True
2362 &apos;&apos;&apos; pbCaseSensitive: default = False
2363 &apos;&apos;&apos; Returns
2364 &apos;&apos;&apos; An array of Longs of same dimensions as the input array listing the indexes of the sorted items
2365 &apos;&apos;&apos; An empty array if the sort failed
2366 &apos;&apos;&apos; Examples:
2367 &apos;&apos;&apos; _HeapSort(Array(4, 2, 6, 1) returns (3, 1, 0, 2)
2369 Dim vIndexes As Variant &apos; Return value
2370 Dim i As Long
2371 Dim lMin As Long, lMax As Long &apos; Array bounds
2372 Dim lSwap As Long &apos; For index swaps
2374 If IsMissing(pbAscending) Then pbAscending = True
2375 If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
2376 vIndexes = Array()
2377 lMin = LBound(pvArray, 1)
2378 lMax = UBound(pvArray, 1)
2380 &apos; Initialize output array
2381 ReDim vIndexes(lMin To lMax)
2382 For i = lMin To lMax
2383 vIndexes(i) = i
2384 Next i
2386 &apos; Initial heapify
2387 For i = (lMax + lMin) \ 2 To lMin Step -1
2388 SF_Array._HeapSort1(pvArray, vIndexes, i, lMin, lMax, pbCaseSensitive)
2389 Next i
2390 &apos; Next heapify
2391 For i = lMax To lMin + 1 Step -1
2392 &apos; Only indexes as swapped, not the array items themselves
2393 lSwap = vIndexes(i)
2394 vIndexes(i) = vIndexes(lMin)
2395 vIndexes(lMin) = lSwap
2396 SF_Array._HeapSort1(pvArray, vIndexes, lMin, lMin, i - 1, pbCaseSensitive)
2397 Next i
2399 If pbAscending Then _HeapSort = vIndexes() Else _HeapSort = SF_Array.Reverse(vIndexes())
2401 End Function &apos; ScriptForge.SF_Array._HeapSort
2403 REM -----------------------------------------------------------------------------
2404 Private Sub _HeapSort1(ByRef pvArray As Variant _
2405 , ByRef pvIndexes As Variant _
2406 , ByVal plIndex As Long _
2407 , ByVal plMin As Long _
2408 , ByVal plMax As Long _
2409 , ByVal pbCaseSensitive As Boolean _
2411 &apos;&apos;&apos; Sub called by _HeapSort only
2413 Dim lLeaf As Long
2414 Dim lSwap As Long
2417 lLeaf = plIndex + plIndex - (plMin - 1)
2418 Select Case lLeaf
2419 Case Is &gt; plMax: Exit Do
2420 Case Is &lt; plMax
2421 If SF_Array._ValCompare(pvArray(pvIndexes(lLeaf + 1)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then lLeaf = lLeaf + 1
2422 End Select
2423 If SF_Array._ValCompare(pvArray(pvIndexes(plIndex)), pvArray(pvIndexes(lLeaf)), pbCaseSensitive) &gt; 0 Then Exit Do
2424 &apos; Only indexes as swapped, not the array items themselves
2425 lSwap = pvIndexes(plIndex)
2426 pvIndexes(plIndex) = pvIndexes(lLeaf)
2427 pvIndexes(lLeaf) = lSwap
2428 plIndex = lLeaf
2429 Loop
2431 End Sub &apos; ScriptForge.SF_Array._HeapSort1
2433 REM -----------------------------------------------------------------------------
2434 Private Function _Repr(ByRef pvArray As Variant) As String
2435 &apos;&apos;&apos; Convert array to a readable string, typically for debugging purposes (DebugPrint ...)
2436 &apos;&apos;&apos; Args:
2437 &apos;&apos;&apos; pvArray: the array to convert, individual items may be of any type, including arrays
2438 &apos;&apos;&apos; Return:
2439 &apos;&apos;&apos; &quot;[ARRAY] (L:U[, L:U]...)&quot; if # of Dims &gt; 1
2440 &apos;&apos;&apos; &quot;[ARRAY] (L:U) (item1,item2, ...)&quot; if 1D array
2442 Dim iDims As Integer &apos; Number of dimensions of the array
2443 Dim sArray As String &apos; Return value
2444 Dim i As Long
2445 Const cstArrayEmpty = &quot;[ARRAY] ()&quot;
2446 Const cstArray = &quot;[ARRAY]&quot;
2447 Const cstMaxLength = 50 &apos; Maximum length for items
2448 Const cstSeparator = &quot;, &quot;
2450 _Repr = &quot;&quot;
2451 iDims = SF_Array.CountDims(pvArray)
2453 Select Case iDims
2454 Case -1 : Exit Function &apos; Not an array
2455 Case 0 : sArray = cstArrayEmpty
2456 Case Else
2457 sArray = cstArray
2458 For i = 1 To iDims
2459 sArray = sArray &amp; Iif(i = 1, &quot; (&quot;, &quot;, &quot;) &amp; CStr(LBound(pvArray, i)) &amp; &quot;:&quot; &amp; CStr(UBound(pvArray, i))
2460 Next i
2461 sArray = sArray &amp; &quot;)&quot;
2462 &apos; List individual items of 1D arrays
2463 If iDims = 1 Then
2464 sArray = sArray &amp; &quot; (&quot;
2465 For i = LBound(pvArray) To UBound(pvArray)
2466 sArray = sArray &amp; SF_Utils._Repr(pvArray(i), cstMaxLength) &amp; cstSeparator &apos; Recursive call
2467 Next i
2468 sArray = Left(sArray, Len(sArray) - Len(cstSeparator)) &apos; Suppress last comma
2469 sArray = sArray &amp; &quot;)&quot;
2470 End If
2471 End Select
2473 _Repr = sArray
2475 End Function &apos; ScriptForge.SF_Array._Repr
2477 REM -----------------------------------------------------------------------------
2478 Public Function _StaticType(ByRef pvArray As Variant) As Integer
2479 &apos;&apos;&apos; If array is static, return its type
2480 &apos;&apos;&apos; Args:
2481 &apos;&apos;&apos; pvArray: array to examine
2482 &apos;&apos;&apos; Return:
2483 &apos;&apos;&apos; array type, -1 if not identified
2484 &apos;&apos;&apos; All numeric types are aggregated into V_NUMERIC
2486 Dim iArrayType As Integer &apos; VarType of array
2487 Dim iType As Integer &apos; VarType of items
2489 iArrayType = VarType(pvArray)
2490 iType = iArrayType - V_ARRAY
2491 Select Case iType
2492 Case V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY, V_BIGINT, V_DECIMAL, V_BOOLEAN
2493 _StaticType = V_NUMERIC
2494 Case V_STRING, V_DATE
2495 _StaticType = iType
2496 Case Else
2497 _StaticType = -1
2498 End Select
2500 End Function &apos; ScriptForge.SF_Utils._StaticType
2502 REM -----------------------------------------------------------------------------
2503 Private Function _ValCompare(ByVal pvValue1 As Variant _
2504 , pvValue2 As Variant _
2505 , Optional ByVal pbCaseSensitive As Boolean _
2506 ) As Integer
2507 &apos;&apos;&apos; Compare 2 values : equality, greater than or smaller than
2508 &apos;&apos;&apos; Args:
2509 &apos;&apos;&apos; pvValue1 and pvValue2: values to compare. pvValues must be String, Number, Date, Empty or Null
2510 &apos;&apos;&apos; By convention: Empty &lt; Null &lt; string, number or date
2511 &apos;&apos;&apos; pbCaseSensitive: ignored when not String comparison
2512 &apos;&apos;&apos; Return: -1 when pvValue1 &lt; pvValue2
2513 &apos;&apos;&apos; +1 when pvValue1 &gt; pvValue2
2514 &apos;&apos;&apos; 0 when pvValue1 = pvValue2
2515 &apos;&apos;&apos; -2 when comparison is nonsense
2517 Dim iCompare As Integer, iVarType1 As Integer, iVarType2 As Integer
2519 If IsMissing(pbCaseSensitive) Then pbCaseSensitive = False
2520 iVarType1 = SF_Utils._VarTypeExt(pvValue1)
2521 iVarType2 = SF_Utils._VarTypeExt(pvValue2)
2523 iCompare = -2
2524 If iVarType1 = V_OBJECT Or iVarType1 = V_BYTE Or iVarType1 &gt;= V_ARRAY Then &apos; Nonsense
2525 ElseIf iVarType2 = V_OBJECT Or iVarType2 = V_BYTE Or iVarType2 &gt;= V_ARRAY Then &apos; Nonsense
2526 ElseIf iVarType1 = V_STRING And iVarType2 = V_STRING Then
2527 iCompare = StrComp(pvValue1, pvValue2, Iif(pbCaseSensitive, 1, 0))
2528 ElseIf iVarType1 = V_NULL Or iVarType1 = V_EMPTY Or iVarType2 = V_NULL Or iVarType2 = V_EMPTY Then
2529 Select Case True
2530 Case pvValue1 = pvValue2 : iCompare = 0
2531 Case iVarType1 = V_NULL And iVarType2 = V_EMPTY : iCompare = +1
2532 Case iVarType1 = V_EMPTY And iVarType2 = V_NULL : iCompare = -1
2533 Case iVarType1 = V_NULL Or iVarType1 = V_EMPTY : iCompare = -1
2534 Case iVarType2 = V_NULL Or iVarType2 = V_EMPTY : iCompare = +1
2535 End Select
2536 ElseIf iVarType1 = iVarType2 Then
2537 Select Case True
2538 Case pvValue1 &lt; pvValue2 : iCompare = -1
2539 Case pvValue1 = pvValue2 : iCompare = 0
2540 Case pvValue1 &gt; pvValue2 : iCompare = +1
2541 End Select
2542 End If
2544 _ValCompare = iCompare
2546 End Function &apos; ScriptForge.SF_Array._ValCompare
2548 REM ================================================= END OF SCRIPTFORGE.SF_ARRAY
2549 </script:module>