Determine Whether a String Contains Digits

The `wasStringContainsDigits` returns `1` in case the supplied string contains digits or `0` otherwise.

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasStringContainsDigits: procedure /* True if string contains digits. */
Parse ARG String
Do i=0 To 9
If Pos(i, String) ~= 0 Then
Return 1
End
Return 0```

An example call is the following:

```If wasStringContainsDigits("V2.3") ~= 1 Then
Do
Say "The string does not contain digits..."
End```

Generate UUIDv4

The `wasGenerateUUIDv4` function takes as input a seed and generates an UUIDv4:

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasGenerateUUIDv4: procedure /* Generates a version 4 UUID. */
Parse ARG Seed
UUID = ''
Do i = 0 To 35
Select
When i = 8 | i = 13 | i = 18 | i = 23 Then
UUID = UUID || '-'
When i = 14 Then
UUID = UUID || '4'
When i = 19 Then
UUID = UUID || SubStr('89AB', Random(1, 4, Seed))
Otherwise
UUID = UUID || D2X(Random(0, 15, Seed))
End
End
Return UUID```

An example call is the following:

```Result = wasGenerateUUIDv4(Time(SECONDS))
Say 'Generated UUID: ' Result```

Map Preserving Quicksort

Given two sets of words, where the first set consists of numbers and the second set consists of strings, the `wasDualQuicksort` function will sort by the numbers while preserving the relative indices between the first set and the second set.

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasDualQuicksort: procedure /* Sorts by the first argument associatively */
Parse ARG Score,Names

Count = Words(Score)
If Count <= 1 Then Return Space(Score' 'Names, 1)

Mid = Trunc(Count/2)

PivotS = SubWord(Score, Mid, 1)
PivotN = SubWord(Names, Mid, 1)

Score = Space(DelWord(Score, Mid, 1), 1)
Names = Space(DelWord(Names, Mid, 1), 1)

LessS = ''
LessN = ''
MoreS = ''
MoreN = ''

Count = Words(Score)
Do i = 1 To Count
Select
When SubWord(Score, i, 1) > PivotS Then
Do
LessS = SubWord(Score, i, 1)' 'LessS
LessN = SubWord(Names, i, 1)' 'LessN
End
Otherwise
Do
MoreS = SubWord(Score, i, 1)' 'MoreS
MoreN = SubWord(Names, i, 1)' 'MoreN
End
End
End
Return Strip(wasDualQuicksort(LessS, LessN)' 'PivotS' 'PivotN' 'wasDualQuicksort(MoreS, MoreN))```

For example, a call such as:

```Score = '1.5 8 3 17'
Names = 'Gogo Moco Coco Jun'

Say wasDualQuicksort(Score, Names)```

will produce the output:

`17 Jun 8 Moco 3 Coco 1.5 Gogo`

Replace Sub-String

When you have a several character to replace in a string, the TRANSLATE built-in function works fine:

`Say Translate('hellos/world', ' ', '/') `

will output:

`hellos world`

However, when you want to replace a sequence of characters, then the `wasReplaceSubString` does this for you:

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasReplaceSubString: procedure /* Replace a sub-string if found. */
Parse ARG String,Search,Insert
If Length(String) = 0 Then Return ''
i = Pos(Search,String);
If i = 0 Then Return String
Less = Left(String, i - 1)
More = SubStr(String, i + Length(Search))
Return Less || Insert || wasReplaceSubString(More,Search,Insert)```

Token-Delimited Words

A series of utility functions used by Wizardry and Steamworks in various functions that emulate arrays based on a token sequence that delimits each element.

Compare Two Strings

`wasStringCompare` compares two strings by the ASCII values of each character:

• if the first string is greater than the second string then the function returns `1`
• if the first string is smaller than the second string then the function returns `-1`
• otherwise, the strings are equal and the function returns `0`
```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasStringCompare: procedure /* Compares two strings by ASCII values.     */
Parse ARG One,Two

Select
When One = '' & Two ~= '' Then Return 1
When One ~= '' & Two = '' Then Return -1
When One = '' & Two = '' Then Return 0
Otherwise Nop
End

a = Upper(Left(One, 1));
b = Upper(Left(Two, 1));

If C2D(a) < C2D(b) Then Return 1
If C2D(a) > C2D(b) Then Return -1

a = Right(One, Length(One)-1)
b = Right(Two, Length(Two)-1)

Return wasStringCompare(a, b)```

Quicksort

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasStringQuicksort: procedure /* Sorts strings separated by token.       */
Parse ARG String,Lambda

Count = Words(String)

If Count <= 1 Then Return String

Middle = Trunc(Count/2)

Pivot = SubWord(String,Middle,1)
String = DelWord(String,Middle,1)
Count = Count - 1

Less = ''
More = ''

Do i = 1 To Count
Word = SubWord(String,i,1)
Compare = wasStringCompare(Word, Pivot)
Select
When Compare = 1 Then
Select
When Less ~= '' Then
Less = Less||' '||Word
Otherwise Less = Word
End
Otherwise
Select
When More ~= '' Then
More = More||' '||Word
Otherwise More = Word
End
End

End

Interpret Lambda

Less = wasStringQuicksort(Less,Lambda)
More = wasStringQuicksort(More,Lambda)

If Less = '' & More ~= '' Then Return Pivot||' '||More
If Less ~= '' & More = '' Then Return Less||' '||Pivot

Return Less||' ' ||Pivot||' '||More```

Binary Search

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasBinarySearch: procedure /* Finds a string within a string by token.   */
Parse ARG String,Search,Lambda

Count = Words(String)

If Count <= 1 Then Return String

Pivot = SubWord(String,Trunc(Count/2),1)
Index = Find(String,Pivot)

Compare = wasStringCompare(Pivot, Search)
Select
When Compare < 0 Then
Do
Less = ''
Do i = 1 To Index - 1
Word = SubWord(String,i,1)
Select
When Less ~= '' Then Less = Less||' ' ||Word
Otherwise Less = Word
End
End
String = Less
End
When Compare > 0 Then
Do
More = ''
Do i = Index + 1 To Count
Word = SubWord(String,i,1)
Select
When More ~= '' Then More = More||' ' ||Word
Otherwise More = Word
End
End
String = More
End
Otherwise Return Pivot
End

Interpret Lambda

Return wasBinarySearch(String,Search,Lambda)```

Intersection of Two Sorted Sets

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasSortedSetsIntersect: procedure /* Intersects two sorted sets.         */
Parse ARG A,B,Lambda

If A = '' | B = '' Then Return ''

Parse VAR A AHead ' ' ATail
Parse VAR B BHead ' ' BTail

Interpret Lambda

Do
If Compare < 0 Then
Return wasSortedSetsIntersect(A, BTail, Lambda)
If Compare > 0 Then
Return wasSortedSetsIntersect(ATail, B, Lambda)
End

Look = wasSortedSetsIntersect(ATail, BTail, Lambda)
If Look ~= '' Then Return AHead||' '||Look

Subtract Two Sorted Sets

Given two sets of space-delimited words:

```SetA = '2345 Aweet Bram Clipboards Disk.info ENV FMS Gram Ham Jam kketc sdiierr SSFEerr ssqqq T Zam'
SetB = 'Disk.info ENV kketc'```

The `wasSortedSetsSubtract` function subtracts `SetB` from `SetA`, resulting in:

`2345 Aweet Bram Clipboards FMS Gram Ham Jam sdiierr SSFEerr ssqqq T Zam`
```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasSortedSetsSubtract: procedure /* Subtract two sorted sets: A \ B      */
Parse ARG A,B,Lambda

If A = '' Then Return ''

Parse VAR A AHead ' ' ATail
Parse VAR B BHead ' ' BTail

Interpret Lambda

Select
Otherwise Return wasSortedSetsSubtract(ATail, BTail, Lambda)
End

Replace a Word

Since ARexx does not allow replacing a word in a sequence of words, the following trick should work fine:

```Search = 'Clipboards ENV FMS T Disk.info Test Bram Gram Ham Jam Zam Aweet 2345 Pooww SSFEerr sdiierr ssqqq kketc'

Index = Find(Search, 'FMS')
Search = SubWord(Search, 1, Index - 1)||' '||'NewWord'||' '||SubWord(Search, Index + 1)

Say Search```

The snippet searches the string of words `Search` for the word `FMS` and grabs the word index. After that, the `Search` string of words is rebuilt as a composition of all words before the word `FMS`, the new word to be inserted `NewWord` and the rest of the remaining words.

Insertion Sort

Given a set of words, the `wasStringInsertSort` function will sort the words and return the resulting set.

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasStringInsertSort: procedure /* Sorts strings of words.                */
Parse ARG String
Count = Words(String)
Do i = 2 To Count
Select = SubWord(String, i, 1)
j = i
Do While j > 1
Previous = SubWord(String, j - 1, 1)
If wasStringCompare(Previous, Select) ~= -1 Then Leave
String = SubWord(String, 1, j - 1)||' '||Previous||' '||SubWord(String, j + 1)
j = j - 1
End
If SubWord(String, 1, j) ~= Select Then
Do
String = SubWord(String, 1, j - 1)||' '||Select||' '||SubWord(String, j + 1)
End
End
Return String```

The `wasRandomHexString` function returns a random hexadecimal string of a specified size:

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasRandomHexString: procedure /* Generates a random hexadecimal string.  */
Parse ARG Seed,Size
If Size = 0 Then Return ''
Random = Random(0, 15, Size + Seed)
Return D2X(Random)||wasRandomHexString(Random, Size - 1)```

For example:

`Say wasRandomHexString(TIME(SECONDS), 5)`

will output a hexadecimal string of length `5`.

Merge Sort

Based on the string comparer we can write a merge-sort algorithm for ARexx:

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasStringMergeSortMerge: procedure /* Compares and merges partitions.    */
Parse ARG L,R,Lambda

If Words(L) = 0 | Words(R) = 0 Then Return L||' '||R

Parse VAR L LHead ' ' LTail
Parse VAR R RHead ' ' RTail

Interpret Lambda

/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasStringMergeSort: procedure /* Merge sort a set of words.              */
Parse ARG String,Lambda

Count = Words(String)

If Count <= 1 Then Return String

Middle = Trunc(Count/2)

Interpret Lambda

L = wasStringMergeSort(SubWord(String, 1, Middle), Lambda)
R = wasStringMergeSort(SubWord(String, Middle+1), Lambda)

Return wasStringMergeSortMerge(L, R, Lambda)```

Given some input words, we can call `wasStringMergeSort` on the set of input words:

```Search = 'Clipboards ENV FMS T Disk.info Test Bram Gram Ham Jam Zam Aweet 2345 Pooww SSFEerr sdiierr ssqqq kketc'
Say wasStringMergeSort(Search)```

which produces the result:

`2345 Aweet Bram  Clipboards  Disk.info  ENV  FMS  Gram  Ham Jam  kketc Pooww  sdiierr  SSFEerr  ssqqq T  Test  Zam`

Encode and Decode To and From Base64

Following conversations with Stefan Haubenthal, here are some functions that encode to Base64 strings and decode Base64 strings. Note that these functions do not account for the `CR LF` that must be added at the 76th character in order to conform to MIME however once you have the encoded strings it is easy to just split them on the 76th character and insert the `CR LF` yourself.

Here is an example call of both functions:

```String = 'Good day!'

Encoded = wasBase64Encode(String)
Say 'Encoded: 'Encoded
Decoded = wasBase64Decode(Encoded)
Say 'Decoded: 'Decoded```

Encode

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasBase64Encode: procedure /* Encodes a string into Base64.              */
Parse ARG String

If Length(String) = 0 Then Return ''

Do While Length(String) // 3 ~= 0
String = String||X2C(0)
End

Count = Length(String)
BinaryString = ''
Do i = 1 To Count
BinaryString = BinaryString||C2B(SubStr(String, i, 1))
End

b64 =,
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

Count = Length(BinaryString)
Result = ''
Do i = 1 To Count By 6
Result = Result||,
SubStr(b64, C2D(B2C("00"SubStr(BinaryString, i, 6))) + 1, 1)
End

Decode

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasBase64Decode: procedure /* Decodes a Base64 string.                   */
Parse ARG String

Count = Length(String)

If Count = 0 Then Return ''

Select
When SubStr(String, Count-1, 2) = '==' Then Pad = 'AA'
When SubStr(String, Count, 1) = '=' Then Pad = 'A'
Otherwise Nop
End

b64 =,
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

Count = Length(String)
Result = ''
Do i = 1 To Count By 4
b1 = Pos(SubStr(String, i, 1), b64) - 1
b2 = Pos(SubStr(String, i+1, 1), b64) - 1
b3 = Pos(SubStr(String, i+2, 1), b64) - 1
b4 = Pos(SubStr(String, i+3, 1), b64) - 1
r1 = b1 * 4 + b2 % 16
r2 = 16 * (b2 // 16) + (b3 % 4)
r3 = 64 * (b3 // 4) + b4
Result = Result||D2C(r1)||D2C(r2)||D2C(r3)
End

Return SubStr(Result, 1, Length(Result) - Length(Pad))```

Generate Sequential Strings

The `wasGenerateStringSequence` function generates string sequences starting with a string and provided a number of increments to perform. The starting string can be alpha-numeric.

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasGenerateStringSequence: procedure /* Generate a string sequence.      */
Parse ARG First,Count,X

If Count = 0 Then Return ''

If Length(X) ~= 1 Then
Return First||" "||wasGenerateStringSequence(First, Count - 1, 1)

Result = ''
c = 1
Length = Length(First)
Do While Length ~= 0
x = SubStr(First, Length, 1)
Select
When DataType(x) = 'NUM' Then
Do
Sum = x + c
Select
When Sum > 9 Then
Do
Result = 0||Result
c = 1
End
Otherwise
Do
Result = Sum||Result
c = 0
End
End
End
Otherwise
Do
Sum = D2C(C2D(x) + c)
Select
When Sum > 'z' Then
Do
Result = 'a'||Result
c = 1
End
Otherwise
Do
Result = Sum||Result
c = 0
End
End
End
End
Length = Length - 1
End

If c ~= 0 Then Result = c||Result

If Count - 1 = 0 Then Return Result

Return Result||" "||wasGenerateStringSequence(Result, Count - 1, 1)```

For example, a call such as:

`Say wasGenerateStringSequence("0yz8", 3)`

Will output the following three sequential words:

`0yz8 0yz9 0za0`

The generator follows the rules:

• the generator always increments from the last character of the string
• if the last character is an alphabetic character, it is incremented by one alphabetically
• if the resulting character exceeds the last alphabetic character `z`, then the previous number or letter in the sequence is incremented
• if the last character is a numeric character, it is incremented by one
• if the resulting number exceeds `9`, the the previous number or letter in the sequence is incremented
• in case the increments would increase the original string length, then the string is extended to include the carry

Here is another example call that demonstrates the extension of the string by one:

`Say wasGenerateStringSequence("9z", 30)`

that outputs the words:

`9z 10a 10b 10c 10d 10e 10f 10g 10h 10i 10j 10k 10l 10m 10n 10o 10p 10q 10r 10s 10t 10u 10v 10w 10x 10y 10z 11a`

Bit-Shift Operations

• `wasShiftLeft` shifts left `c` by `n` bits.
• `wasShiftRight` shifts right `c` by `n` bits.

Shift Left

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasShiftLeft: procedure /* logical shift left of c by n bits             */
Parse ARG c,n
Return Right(D2C(C2D(c) * 2 ** n), 1)```

Shift Right

```/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
wasShiftRight: procedure /* logical shift right of c by n bits           */
Parse ARG c,n
Return D2C(C2D(c) % 2 ** n) ```