Tutorial: Array Variables 3

From GECK
Jump to: navigation, search

This is is the third part of a tutorial on NVSE Array Variables, the second part can be found here.

Walking Arrays

If one wishes to create an array with more than 20 elements, or does not know how many elements may be required, several techniques are possible. By walking an array, one can select each element in turn and do something with it.

Suppose one has two arrays and wishes to merge them. With regular arrays, because the keys are consecutive integers, we could use the Ar_Size function (returns total elements stored) and use that to build a While loop in which we add each value held in array2 to array1 with the Ar_Append function (adds a new element to the end):

 
let array1 := ar_list value1, value2, value3, value4, ... value20   
let array2 := ar_list value1, value2, value3, value4, ... value20

  ; * the values could be anything - in docs, that is called a 'multi' type

let iSize := ar_size array2         ; * we could skip this step
let iNum := -1
while (iNum += 1) < iSize 
  ; * the lowest index is 0 and the highest possible index is total size - 1

    let someVar := array2[iNum]
    ar_append array1 someVar
loop

And that'll work fine, as long as each value in array2 is the same type (forms, numbers, strings or arrays) so that we know what 'someVar' is supposed to be declared as. The trouble is that arrays can hold any combination of such values. Strictly speaking, we could use the handy TypeOf function to let us know the type of value in the form of a string:

let iNum := -1
while (iNum += 1) < (ar_size array2)
    let sv_somestring := TypeOf array2[iNum] 
    ; * TypeOf determines the type of the value under array2[iNum] and 
    ; * passes it to our string var

    if eval sv_somestring == "Form"    
    ; * TypeOf returns "Form" for forms and refs
        let rRefVar := array2[iNum]
        ar_append array1 rRefVar
        continue                      
        ; * we got what we need, let us skip the rest of the body and 
        ; * move on with our loop

    elseif eval sv_somestring == "Number"   
    ; * TypeOf returns "Number" for floats and ints
        let fFloatVar := array2[iNum]
        ar_append array1 fFloatVar
        continue

    elseif eval sv_somestring == "String"    
    ; * TypeOf returns "String" for strings
        let sv_StringVar := array2[iNum]
        ar_append array1 svStringVar
        continue

    else                                    
    ; * TypeOf  returns either "Array", "Map" or "StringMap" for arrays - in this
    ; * case we just need to catch them all
        let ar_ArrayVar := array2[iNum]    
        ; * remember this makes ar_ArrayVar be a _reference_ to the array 
        ; * under array2[iNum], not a copy
        ar_append array1 ar_ArrayVar
        continue
    endif
loop

But that's a rather roundabout way isn't it, and either way a while loop won't work to walk maps (gaps, floats) or stringmaps (string keys). Enter the foreach loop. (The Ar_InsertRange function is also useful here, but will be covered later)

Foreach Loops

Foreach loops select each element of a collection type in a forwards loop, and like while - loop you need to see foreach - loop as a block. For strings they select each character, for containers each inventory reference, and for any type of array they select each array element. As with while loops, you interrupt a foreach loop with the "Break" command, and skip an instance of the loop body with the "Continue" command.

When you use a foreach loop on an array, it'll copy each element to a stringmap- the iterator, that you need to declare - the key of the element being inspected will be found under SomeStringMap["key"], the value under SomeStringMap["value"]:

["key"]   :  the key of the element
["value"] :  the value of the element

The iterator, ie the temporary stringmap, will only ever have those 2 elements in it: "key"::KeyOfTheInspectedElement and "value"::ValueOfTheInspectedElement. You don't need to initialize it - the foreach command does that - and when the loop is done it is immediately nullified/uninitialized.

array_var arraytowalk
array_var tempstringmap  
 ; * for iterators, I like to use 'entry' or 'ar_entry' as the variable name, like you're leafing through an encyclopaedia


foreach tempstringmap <- arraytowalk    ; * you need that arrow-like symbol there
    ; check out or do something with tempstringmap["key"] 
    ; and/or tempstringmap["value"]
loop

so that earlier lengthy while loop could become this short foreach loop:

array_var array1
array_var array2
array_var ar_entry

let array1 := ar_List Value1, Value2, ... Value20
let array2 := ar_List someRef, someFloat, "someString", someStringVar, someArray, ... Value20

foreach ar_entry <- array2
    ar_append array1 ar_entry["value"]    
    ; *  no matter what type each value in array2 is, we can refer to each one
    ; *  with ar_entry["value"]
loop

An example with our map that's holding some exterior cells by their X coordinates: let's say we want to split it up into a map holding western cells (-X) and one with eastern cells (+X).

array_var CellMap
array_var CellMapWest
array_var CellMapEast
array_var entry
float fXPos

let CellMap := ar_Map -21::MojaveOutPost -9::MojaveDriveIn 7::188TradingPost 12::BoulderCity 17::BitterSprings
let CellMapWest := ar_construct Map
let CellMapEast := ar_construct Map

foreach entry <- CellMap
    let fXPos := entry["key"]   ; * passing the key, the X position, to a float
    if 0 > fXPos                ; * float is negative, so the cell is west
        let CellMapWest[fXPos] := entry["value"]   
        ; * storing the cell - entry["value"] - under the fXPos key in the West map
    else
        let CellMapEast[fXPos] := entry["value"]
    endif
loop

But do we really need the variable, fXPos? No.

foreach entry <- CellMap
    if eval 0 > entry["key"]
        let CellMapWest[entry["key"]] := entry["value"]
    else
        let CellMapEast[entry["key"]] := entry["value"]
    endif
loop


Part 4: Overview of available functions


External Links