A course on SFO - chapter 2.3

lib_array: better handling of WEIDU array data

Back to contents

The functions in lib_array are all aimed at making it easier to work with arrays, WEIDU's powerful-but-awkward datatype. Unless otherwise noted, they can handle arrays with up to five keys (one limitation of WEIDU is that you can't readily write code to handle an arbitrary number of keys). Unless otherwise noted, all functions here are dimorphic.

2.3.1 Displaying arrays

You can get an array printed out on the screen using array_echo:

LAF array_echo STR_VAR array=spell_array END

This is unlikely to be useful in live code, but it's good for debugging.

2.3.2 Getting information out of arrays

array_contains

The 'array_contains' function tells you whether an array contains an entry with a given key and/or given value. For instance, suppose we define this array:

ACTION_CLEAR_ARRAY test_array
ACTION_DEFINE_ASSOCIATIVE_ARRAY test_array BEGIN
	Minsc=>"awesome"
	Anomen=>"annoying"
	Sarevok=>"Treacherous; watch out for him"
END

Then this command returns value=1:

LAF array_contains STR_VAR array=test_array key="Minsc" RET value END

And so does this one:

LAF array_contains STR_VAR array=test_array val="annoying" RET value END

And this one:

LAF array_contains STR_VAR array=test_array key="Minsc" val="awesome" RET value END

But this one returns value=0:

LAF array_contains STR_VAR array=test_array key="Minsc" val="annoying" RET value END

(There is an entry with key='Minsc' and an entry with value='annoying', but no entry with both.)

For key checks, the difference between using array_contains and just doing VARIABLE_IS_SET is that array_contains checks the actual array, whereas VARIABLE_IS_SET checks the underlying struct. If you CLEAR_ARRAY the test array above, array_contains will report that there is no entry with key 'Minsc', whereas VARIABLE_IS_SET "test_array_Minsc" will return true.

array_contains is case-sensitive. In multi-key arrays it checks only the first key.

array_length

The array_length function just tells you how many entries there are in an array:

LAF array_length STR_VAR array=test_array RET value END

will, if run on the above array, return value=3.

array_keys

The array_keys function returns arrays (in k=>_ format) of the various levels of its keys. For instance, consider this array:

ACTION_CLEAR_ARRAY test_array
ACTION_DEFINE_ASSOCIATIVE_ARRAY test_array BEGIN
	Minsc,awesome => 1
	Anomen,awesome=>0
	Anomen,annoying =>1
	Sarevok,treacherous=>0
END

If you do

LAF array_keys STR_VAR array=test_array RET keys1 keys2 END

then keys1 will be an array with keys 'Minsc', 'Anomen', 'Sarevok' and keys2 will be an array with keys 'awesome', 'annoying', 'treacherous'.

2.3.3 Manipulating arrays

array_split

The array_split function takes an array and splits it into two arrays, based on criteria specified by the SFO-standard-format functions 'match_key' and/or 'match_value'. For instance, consider this code:

ACTION_CLEAR_ARRAY npc_class
ACTION_DEFINE_ASSOCIATIVE_ARRAY npc_class BEGIN
	Minsc=>ranger
	Anomen=>cleric
	Sarevok=>fighter
	Jan=>mage_thief
	Aerie=>cleric_mage
END

DEFINE_PATCH_FUNCTION is_annoying
	STR_VAR arguments=""
	RET value
BEGIN
	PATCH_MATCH "%arguments%" WITH
	Anomen Jan Aerie BEGIN
		value=1
	END
	DEFAULT
		value=0
	END
END

LAF array_split 
	STR_VAR 
		array=npc_class 
		match_key=is_annoying 
	RET_ARRAY 
		annoying_npcs=split 
		tolerable_npcs=rest 
END

The annoying_npcs array will contain the Jan, Aerie and Anomen entries, the tolerable_npcs array will contain the rest. You can match values instead, or match both at once.

You can use the anonymous function construct with array_split: here's the same task as above via anonymous functions:

LAF array_split 
	STR_VAR 
		array=npc_class 
		match_key="! ~__~ STRING_MATCHES_REGEXP ~\(Anomen\|Jan\|Aerie\)~"
	RET_ARRAY 
		annoying_npcs=split 
		tolerable_npcs=rest 
END

This function checks only the first key in multi-key arrays.

array_join

The array_join function joins two arrays together, so that an entry is in the new array whenever it is in the old array. If there is a mismatch (the first and second array assign a different value to the same key(s)), the second array takes precedence.

For instance:

ACTION_CLEAR_ARRAY annoying_npcs
ACTION_CLEAR_ARRAY tolerable_npcs
ACTION_DEFINE_ASSOCIATIVE_ARRAY annoying_npcs BEGIN
	Anomen=>1
	Aerie=>1
	Jan=>1
END
ACTION_DEFINE_ASSOCIATIVE_ARRAY tolerable_npcs BEGIN
	Minsc=>1
	Sarevok=>1
END

LAF array_join 
	STR_VAR 
		array1=annoying_npcs 
		array2=tolerable_npcs 
	RET_ARRAY all_npcs=array 
END

The output array will have all five entries in it.

array_copy

This just copies an array to a new array (something surprisingly hard to do, in full generality, in WEIDU).

LAF array_copy STR_VAR array=old_array RET_ARRAY new_array=array END

array_invert

The array_invert function just swaps an array so that keys become values and vice versa. Note that if more than one key has the same value, array_invert will delete data. (Later entries in the array will take priority over earlier ones.)

For instance, if you apply array_invert to the 'annoying_npcs' array, defined above, like this:

LAF array_invert STR_VAR array=annoying_npcs RET_ARRAY annoying_npcs_inverted=array END

then the resultant array will contain only

1=>Jan

array_sort

The array_sort function sorts an array by its keys. The sort rule is specified by an sfo-standard function which acts on the keys: its outputs are sorted lexicographically. (The sort function doesn't have to be one-to-one; if it isn't, the original order breaks ties.)

For instance, let is_annoying be the function defined in array_split, above, and consider this code:

ACTION_CLEAR_ARRAY all_npcs
ACTION_DEFINE_ASSOCIATIVE_ARRAY all_npcs BEGIN
	Minsc=>1
	Anomen=>1
	Jan=>1	
	Aerie=>1
	Sarevok=>1
END
LAF array_sort STR_VAR array=all_npcs function=is_annoying RET_ARRAY all_npcs=array END

The result is that the keys of all_npcs now appear in the order (Minsc,Sarevok,Anomen,Jan,Aerie). You can use the anonymous function construct.

array_map

The array_map function manipulates an array by applying a sfo standard function 'keymap' to its keys and/or 'map' to its values. (Either function can be absent, in which case no change is made.)

For instance:

ACTION_CLEAR_ARRAY npc_classes
ACTION_DEFINE_ASSOCIATIVE_ARRAY npc_class BEGIN
	Minsc=>RANGER
	Jaheira=>DRUID
	Aerie=>CLERIC_MAGE
	Edwin=>MAGE
	Imoen=>MAGE_THIEF
END

DEFINE_PATCH_FUNCTION castertype STR_VAR arguments="" RET value BEGIN
	PATCH_MATCH "%arguments%" WITH
	CLERIC_MAGE BEGIN
		SPRINT value "mixed"
	END
	".*MAGE.*" "BARD" "SORCERER" BEGIN
		SPRINT value "arcane"
	END
	".*CLERIC.*" ".*DRUID.*" "RANGER" "PALADIN" "SHAMAN" BEGIN
		SPRINT value "divine"
	END
	DEFAULT
		SPRINT value "none"
	END
END

LAF array_map 
	STR_VAR 
		array=npc_classes 
		map=castertype 
	RET_ARRAY 
		npc_castertypes=array 
END

The outputted array will be

Minsc=>none
Jaheira=>divine
Aerie=>mixed
Edwin=>arcane
Imoen=>arcane

If you use 'keymap' instead, the function acts on the keys rather than the values. You can use the anonymous function construct.

array_fill

Given an array, array_fill fills all entries in the array with some fixed entry. For instance:

LAF array_fill STR_VAR array=npc_array fill="-1" RET_ARRAY npc_array=array END

sets every entry value in npc_array to "-1".

2.3.4 Reading and writing arrays to/from files

array_read

The array_read function reads data from a file (or, when used as a patch function, from the current file) and puts it into an array. The file should either be a single column of entries (in which case each becomes a key, and the value of each key is "") or else two columns of entries (in which the first entry in each row is a key and the second is the value of that key). Any columns after the second are ignored.

For instance, here is code to read in the contents of race.ids into an array with keys equal to the numerical race identifiers and values equal to their human-readable versions, so that (e.g.) $race_array("107")="ETTERCAP":

COPY_EXISTING - race.ids nowhere
	LPF array_read INT_VAR firstrow=1 RET_ARRAY race_array=array END

Here, firstrow is an optional argument telling the function which row to start at (counting up from zero) – in race.ids, the first row is 'IDS V1.0' and should be skipped.

By default, array_read reads arrays in in mixed case (i.e., the case that they’re actually stored in). If you set the STR_VAR 'case' to 'upper' or 'lower' you can force uppercase or lowercase. You can also specify the INT_VAR backwards=1 if you want to treat column 2 as keys and column 1 as values: here's code to read the races so that (e.g.) $race_array("ETTERCAP")=107:

COPY_EXISTING - race.ids nowhere
	LPF array_read INT_VAR backwards=1 firstrow=1 RET_ARRAY race_array=array END

Here's the format for action context:

LAF array_read 
	STR_VAR 
		file="weapons.txt" 
		location="resource" 
	RET_ARRAY 
		array=weapprof_array 
END

'File' is the file to be read in (here, some file in your mod detailing weapon data); you can specify its location via any/all of 'location', 'locbase', or 'path', as usual for SFO.

array_write

The array_write function writes an array (assumed to have only one key per entry) to a file, as a 2-column table. The file is by default assumed to live in %data_loc% (i.e., weidu_external/data/[your mod]), you can override this by setting the STR_VAR 'path' to a different location. (array_log doesn't support SFO's 'location' or 'locbase' ways of specifying where a file is, since these point at your mod folder and in normal circumstances you shouldn't be editing that. Set path=override if you want the file to appear in-game.)

For instance, here's code to write the array 'npc_classes' to file:

LAF array_log STR_VAR file="npc_classes.txt" array=npc_classes END

There are two optional boolean (INT_VAR) arguments. If 'new' is set to 1, then any existing file of the same name is overwritten (if new=0, the default, the new values are instead appended). If 'permanent' is set to 1, any existing file is overwritten and the file will not be deleted when the mod component is uninstalled. (You should seldom if ever release a mod that uses this setting: it's for development, research, and testing.) Setting permanent=1 automatically sets new=1.

2.3.5 Reading and writing arrays to/from strings

array_from_string

This function takes as input a string of key-value pairs in the format 'k=>v', separated by spaces, and turns it into an array of key-value pairs. You can use quotation marks if the key/value has spaces in. For instance,

LAF array_from_string 
	STR_VAR string="Minsc=>Awesome Amonen=>Annoying Sarevok=>~Treacherous, watch out~" 
	RET_ARRAY test_array=array
END

generates the 'test_array' array defined above in array_contains.

array_keys_from_string

This function takes a list of keys, separated by spaces, and turns them into an array (with all values blank). For instance,

LAF array_keys_from_string 
	STR_VAR string="Minsc Anomen ~Sir Anomen~ Sarevok Aerie" 
	RET_ARRAY npc_array=array 
END

generates this array:

Minsc=>""
Anomen=>""
"Sir Anomen"=>""
Sarevok=>""
Aerie=>""

If you want the string separated by something other than spaces, you can specify a different character, like this:

LAF array_keys_from_string 
	STR_VAR 
		string="Minsc,Anomen,Sir Anomen,Sarevok,Aerie" 
		separator=","
	RET_ARRAY 
		npc_array=array 
END

This generates the same array.

array_values_from_string

This is very similar to array_keys from string, but it generates an array with sequentially-numbered keys (starting at zero) and string entries as values. For instance,

LAF array_values_from_string 
	STR_VAR string="Minsc Anomen ~Sir Anomen~ Sarevok Aerie" 
	RET_ARRAY npc_array=array 
END

generates this array:


0=>Minsc
1=>Anomen
2=>"Sir Anomen"
3=>Sarevok
4=>Aerie

This basically replicates the native WEIDU command ACTION_DEFINE_ARRAY, but it can be used with strings generated elsewhere. Again, you can use 'separator' to specify a string with entries separated by something other than spaces.