• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

GML Utility Array Functions (now available for 2.3)

samspade

Member
There are a number of utility array functions I use and I've recently been taking some javascript courses so I wanted to formalize and add to those functions and make it a free to use utility package. I'll upload it to the marketplace at some point.

If anyone wants to use them feel free, but right now I'd love feedback on what is there. Specifically:
  • Any missing functions that should be added?
  • Have I made any mistakes?
  • Are there better ways to do any of these?
The current list of scripts are:

  • array_swap
  • array_add
  • array_insert
  • array_delete
  • array_splice
  • array_find_value
  • array_shuffle
  • array_sort
  • array_copy_shallow
  • array_copy_deep
  • array_combine
  • array_filter
  • array_map
  • array_for_each
  • array_flatten
  • array_accumulate
  • array_to_list
  • list_to_array

A couple general notes. I haven't tried for optimization yet. I think my ultimate goal will be to have a set of scripts that are a little more verbose, extra assignments, lines and so on, for ease of understanding and debugging, and then make an extension that as the most compact versions of them.

There's also a couple scripts I made that I'm not sure are worth having. For example, splice could be easily accomplished in two lines with delete then insert and that would probably be more understandable. Also, map and for_each will be significantly less optimized and require you to write special callback scripts which doesn't really seem worthwhile for the gain. So I'm tempted to remove those three scripts.

I also might want to add an array_to_list and list_to_array deep copy.

Also, if I did my research right, there's no way to make an existing array shorter is there? Seems like the only option is to create a new shorter array and reassign.

You can view the repository here.

Any and all feedback welcome here or directly to the repository.
 

CloseRange

Member
These are vary useful but I will say there does come a point where it's just better to create your own data structure.
Or for the individual user make one based on your needs. At least if you care about optimization.

Other than that you might want to consider some debugging functions.
for instance a deep/shallow log of all entries
log the index of all entries that are equal to some input
log the total size of the array (adding all sub arrays as well)

maybe create a safegard functon for setting and getting entries.
for instance:
Code:
array[3] = 0;
var temp = array[5];
will give me an error for going out of bounds but:
Code:
array[3] = 0;
var temp = array_get(array, 5);
would return noone or 0 and just logs the error so the game still runs

Also a custom function sort would be nice as well.
Let's say me, the user of these functions made this script:
Code:
/// myCustomSort(a, b);
var a = argument[0];
var b = argument[1];
return a+5 < b;
I could pass this into one of your functions:
Code:
array_custom_sort(array, myCustomSort);
so the user creates a function that takes in 2 inputs (2 values to compare) and returns true or false based on what value should come first.

I also noticed you didn't have a replace function (unless that's what you mean by swap?)
Honestly some of these functions are probably what I already described but I'm not super used to javascripts array utility functions because I my own structures more than I use arrays, usually.
 
Last edited:

FrostyCat

Redemption Seeker
I'd love to see and regularly use a library like this if its variable scoping is up to scratch.

Some pointers:
  • If array_find_value() is for finding the position of a value in an array, I would rename it to array_find_index() to be consistent with the naming of list functions.
  • It would be nice if there is a strict and a non-strict version of array_find_value(). The strict version would use == (which for arrays will be true only if it's the exact same array, not just two different arrays with the same content), while the non-strict version would multiplex between == for non-array values and array_equals() for array values.
  • array_sort() should optionally take an argument for which direction to sort and/or a callback for how to compare two adjacent values.
  • It would be nice if there is an array_equals_deep() function that checks the value equivalence of two entire nested array structures.
  • array_to_list() should be more clear as to whether only its topmost layer will be converted to lists, or if it recursively converts all sublayers to lists as well. In the same vein, there is currently no way of telling an integer in a list from a list nested in a list, so you'll need a disclaimer too or somehow work around that.
  • There should be an in-place version of array_for_each() and/or array_map().
  • The library would be easier to import if it's in extension form rather than a collection of scripts.
  • The library should be tested by automated scripts instead of visual inspections using print statements. At the very least, use array_equals() to help with that.
As for callbacks and extra assignments, I'd say your concern about them will be pointless given the upcoming GML 2020 updates. Anonymous functions and named parameters will both become norms soon, and this library should be built facing forward. I wouldn't find it useful to bend over backwards to accommodate legacy dinosaurs at this point.
 
  • Like
Reactions: KPJ

samspade

Member
Thank you both for the inputs. Those are helpful.

@CloseRange

for instance a deep/shallow log of all entries
Both the debugger and show_debug_message already do this, so I didn't feel it was necessary.

log the index of all entries that are equal to some input
This is probably worth adding.

log the total size of the array (adding all sub arrays as well)
This is already in there in the form of flatten and array_length_1d although a custom function would be more optimized.

maybe create a safegard functon for setting and getting entries
This might be worth while. As a note, GML already has an array_get function. It will throw an error if you try to access outside of an array. I go back and forth on this. Personally, 9 out of 10 times I'd probably rather have the error. But a safe function might be nice for those times where I don't.

Also a custom function sort would be nice as well.
I'm not sure how far you looked into it, but the sort function is already custom. It takes two arguments, the array and evaluation script I have a couple basic ones included, but you could write and pass any that you wanted.

I also noticed you didn't have a replace function (unless that's what you mean by swap?)
I didn't create functions that are easier to do in code so for example:

Code:
//array destroy
array = -1;

//array clear
array = [];

//array replace
array[index] = value;
array_set(array, value); //this one is a built in GML function
As a side note, I don't know how to do array_clear in a script because while you can use the @ accessor to prevent the copy upon write behavior when writing to an array inside a script, if you do:

Code:
///array_clear(array)

argument0 = [];
That copies the array instead of modifying the existing one.

@FrostyCat

If array_find_value() is for finding the position of a value in an array, I would rename it to array_find_index() to be consistent with the naming of list functions.
I thought I had, but I always get these mixed up, so I'll fix that.

It would be nice if there is a strict and a non-strict version of array_find_value(). The strict version would use == (which for arrays will be true only if it's the exact same array, not just two different arrays with the same content), while the non-strict version would multiplex between == for non-array values and array_equals() for array values.
Will add it to my list.

array_sort() can optionally take an argument for which direction to sort and/or a callback for how to compare two adjacent values.
I think it already does this if I understand what you mean.

It would be nice if there is an array_equals_deep() function that checks the value equivalence of two entire nested array structures.
Will add it to my list.

array_to_list() should be more clear as to whether only its topmost layer will be converted to lists, or if it recursively converts all sublayers to lists as well. In the same vein, there is currently no way of telling an integer in a list from a list nested in a list, so you'll need a disclaimer too or somehow work around that.
Is says it in the function description, but it would probably be good to rename both versions and make deep copy versions as well.

There should be an in-place version of array_for_each() and/or array_map().
This one I don't understand. What do you mean?

The library would be easier to import if it's in extension form rather than a collection of scripts.
That's the plan.

The library should be tested by automated scripts instead of visual inspections using print statements. At the very least, use array_equals() to help with that.
On my list of general programming goals is to learn how to implement actual testing but I'll start with the array_equals as I understand that currently.
 

FrostyCat

Redemption Seeker
===There should be an in-place version of array_for_each() and/or array_map().===
This one I don't understand. What do you mean?
I mean versions of these scripts that actually work on the given array instead of returning a modified copy. Example:
Code:
/// @function array_map_overwrite(array, script, ...script_arguments)

/// @param {array} array
/// @param {scripts} script_id
/// @param {array} arguments

/// @description Performs the script on each element of the array and sets each to the script's return value. Note - returns the array given.


//rename arguments for ease of use
var _array, _script, _script_argument_array;
_array = argument0;
_script = argument1;
_script_argument_array = argument2;

//loop through given array and execute the script on each element
for (var i = 0; i < array_length_1d(_array); i++) {
   _array[@ i] = script_execute(_script, _array[i], _script_argument_array);
}

//return given array
return _array;
 

samspade

Member
Repository updated.

Added:
  • array_valid_index - takes an array and index and checks to see whether it is a valid index for the array. I think I tested all of the possibilities.
  • array_get_safe and array_set_safe - only allow getting and setting an array if the index is valid. I might remove these functions and just leave the array_valid_index. In general, I think this is the type of error you want to immediately crash your game rather than live on unnoticed. And these functions are very easy to write so people could add them.
  • array_find_index_all - returns an array of all indexes that contain the value
  • array_find_index_non_strict - can find equivalent arrays. Does not do deep searches.
  • array_equals_deep - same as array equals but works on nested arrays
  • array_to_list_deep - same as the shallow version, but works on nested arrays.
I attempted to write a list_to_array_deep however it is either not possible in GML or I'm missing something. The issue comes in testing whether a a list contains another list. I'm not sure how you can do this as lists are just numbers so I can test a number against available lists but there isn't anyway that I know of to tell if it is supposed to be a number or a list. Maybe there's away that I didn't think of (e.g. parse a JSON file into an array?).

@FrostyCat in the prior version, for each operated directly on the array and map operated on a copy, but that was a little hidden by the way I structured the callback scripts. I modified them and renamed array_map to array_for_each_copy to the way you suggested. I think it is a little cleaner this way but I haven't written any complicated callback scripts.
 
Last edited:

samspade

Member
I added GMAssert to the project and added some automated testing. Fixed one problem. I'll work on making it an extension next and then probably upload it to the marketplace for ease of use.
 

Gavolot

Member
I would be glad if someone had decided for me earlier all possible ways of sorting arrays, dynamic lists, and two-dimensional arrays.
 

samspade

Member
A handful of these could be making use of array_copy/array_create/helper structures - take a look at my implementation of Haxe's standard array library [that compiles to GML] for some hints.
I looked through all the functions, and, while I don't understand Haxe, I'm not exactly sure where I could add in the gml array_copy and array_create functions that I'm not already using them. Let me know if I'm missing them. However, it did help me do a couple things better. For example, array_insert now operates on the array instead of creating a copy.

I would be glad if someone had decided for me earlier all possible ways of sorting arrays, dynamic lists, and two-dimensional arrays.
I'm not exactly sure what this means, but it does include a customizable sort. I don't want to deal with two-dimensional arrays for a couple reasons. The biggest reason is that GML's array handling is going to change soon. The second being that most of the functions I'm trying to create don't have an equivalent in the two-dimensional version (what does filter or for each mean in a grid?) and to the extent that there are some useful things you want to do with grids there are ds_grids and GMLineaer already.

GENERAL UPDATE -- SOURCE CODE HERE

I've turned it into an extension. Everything still seems to work. For the moment, I'm leaving the scripts in this project, prefixed now with src_.

I'm making another pass through for consistency and here are my questions:

Array Extension Questions:
  • I'm planning on removing array_get_safe and array_set_safe but leaving array_valid_index. I think they do more harm than good and in general conflict with most other GML assumptions (like errors crash your game).
  • Should array_valid_index accepts true and false? This seems like it should stay that way given how the rest of GML accepts true and false for 0 and 1.
  • Is it worth having both array_copy_shallow and array_copy_deep?
  • For scripts that accept variable arguments, it seems like you must assign the array to a variable in order to use it?
  • There's a bunch of find index type scripts for example, some, every, last_index I could include a find_index_all_non_strict. I'm not sure how many of these are worthwhile and how many are just bloat.
  • one big question I have is how to handle the callback scripts. Is my current method the best way of doing so?

Extension specific questions:
  • It seems like pure GML extensions should be compatible with all systems by default?
  • What does the Extra Platforms mean in Extensions?

EDIT:

Here is what I am going to consider the final list of scripts to be included in the extension and asset:

EXTENSION FUNCTIONS
- array_valid_index
- array_swap_positions
- array_copy_shallow
- array_copy_deep
- array_add_to_end
- array_insert
- array_delete
- array_splice
- array_join
- array_combine_strings
- array_to_list_shallow
- array_to_list_deep
- list_to_array_shallow
- array_equals_deep
- array_flatten
- array_find_index
- array_find_index_custom
- array_find_index_last
- array_find_index_last_custom
- array_find_index_all
- array_find_index_all_custom
- array_every
- array_some
- array_filter
- array_reduce
- array_sort
- array_shuffle
- array_reverse
- array_for_each_copy
- array_for_each

HELPER SCRIPT TEMPLATES
- callback_template
- sort_template

SORT SCRIPT TEMPLATES
- sort_alphabetical_safe
- sort_ascending
- sort_ascending

I will work on the actual asset later today and tomorrow. The market place version will only include the above for simplicity sake, though the github repository will include the testing suit and demo functions and script versions.
 
Last edited:

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Let me know if I'm missing them.
array_delete can use array_copy to join two halves of new array.
array_filter can make use of a ds_list to collect elements before allocating an array for them.
there was something else when I was looking at it
 

samspade

Member
I've updated all of these for 2.3 now. It's a lot easier when you can pass in functions. As a side note, array_equals now works on deep nested arrays.

GML:
// Script File to add additional array functionality

/// @function array_valid_index(array, index)
/// @param {array_id} array
/// @param {int} index
/// @description Returns true if the index is a valid index for the array. Otherwise returns false.
function array_valid_index(_array, _index) {
    if (is_real(_index)) {
        if (_index == floor(_index)) {
            if (_index >= 0) && (_index < array_length(_array)) {
                return true;
            }
        }
    }   
    return false;
}


/// @function array_swap_positions(array, position_1, position_2)
/// @param {array_id} array           
/// @param {int} position_1               
/// @param {int} position_2
/// @description Swaps two positions in an array. It does return the array; however, it swaps them in position.
function array_swap_positions(_array, _pos_1, _pos_2) {
    var _temp = _array[_pos_1];
    _array[@ _pos_1] = _array[_pos_2];
    _array[@ _pos_2] = _temp;
    return _array;
}
    
    
/// @function array_copy_shallow(array)
/// @param {array_id} array       
/// @description Copies an array and returns the copied array.
/// You must assign the returned array to a variable for it to work.
/// This is a shallow copy, and does not copy nested levels.
/// If you want to copy nested levels, use array_copy_deep.
function array_copy_shallow(_array) {
    var _length, _new_array;
    _length = array_length(_array);
    _new_array = array_create(_length);
    array_copy(_new_array, 0, _array, 0, _length);
    return _new_array;
}   
 
  
/// @function array_copy_deep(array)
/// @param {array_id} array
/// @description Returns a deep copy of the provided array. It will copy over any nested arrays. Note - returns a new array. MUST be assigned to be any use.
function array_copy_deep(_array) {
    var _new_array, _length;
    _new_array = [];
    _length = array_length(_array);
    for(var i = 0; i < _length; i++){
        if(is_array(_array[i])) {
            array_add_to_end(_new_array, array_copy_deep(_array[i])); 
        } else {
            array_add_to_end(_new_array, _array[i]);
        }
    }
    return _new_array;
}
    
    
/// @function array_add_to_end(array, val1 [, val2, ... max_val])
/// @param {array_id} array
/// @param {variable} value_1
/// @param {variables} ...values           
/// @description Add variables to the end of an array. It does return the array; however, it will add them to the array itself.
function array_add_to_end() {
    var _array = argument[0];   
    for (var i = 1; i < argument_count; i += 1) {
        _array[@ array_length(_array)] = argument[i];
    }   
    return _array;
}
    
    
/// @function array_insert(array, position, val1 [, val2, ... max_val])
/// @param {array_id} array
/// @param {int} position
/// @param {variable} value_1
/// @param {variables} ...values
/// @description Inserts the values specified starting at the specified point. It does return the array; however, it will add them to the array itself.
function array_insert() {
    var _array, _pos, _amount_to_add, _new_length;
    _array = argument[0];
    _pos = argument[1];
    _amount_to_add = argument_count - 2;
    _new_length = array_length(_array) + _amount_to_add;

    if (_pos > array_length(_array)) return _array;

    for (var i = _new_length - 1; i >= 0; i--) {
        if (i < _pos) {
            _array[@ i] = _array[i];
        } else if (i >= _pos + _amount_to_add) {
            _array[@ i] = _array[i - _amount_to_add];
        } else {
            _array[@ i] = argument[i - _pos + 2];
        }
    }

    return _array;
}
    
    
/// @function array_delete(id, position, amount)
/// @param {array_id} id
/// @param {int} position
/// @param {int} amount
/// @description Deletes the amount of values specified starting at the specified position. Note - returns a new array. MUST be assigned to be any use. Note - due to the way this script works, it will not error out if you use position or amounts outside of the array's range, but it will not work the way you think it will.
function array_delete(_id, _position, _amount) {
    var _amount_to_delete, _length, _new_array;
    _length = array_length(_id);
    _amount_to_delete = min(_length - _position, _amount);
    _new_array = array_create(_length - _amount_to_delete);

    for (var i = 0; i < _length - _amount_to_delete; i++) {
        if (i < _position) {
            _new_array[i] = _id[i];
        } else {
            _new_array[i]= _id[i + _amount_to_delete];
        }
    }

    return _new_array;
}   
    
    
/// @function array_splice(array, position, amount_to_delete, val1 [, val2, ... max_val])
/// @param {array_id} array
/// @param {int} position
/// @param {int} amount_to_delete
/// @param {variable} value_1
/// @param {variables} ...values
/// @description First deletes teh amount of values specified starting at the specified position. Then adds in the new values starting at that position. Note - this will return a new list.
function array_splice() {
    var _array, _position, _amount_to_delete, _amount_to_add, _new_array;
    _array = argument[0];
    _position = argument[1];
    _amount_to_delete = argument[2];
    _amount_to_add = argument_count - 3;

    _new_array = array_delete(_array, _position, _amount_to_delete);

    for (var i = array_length(_new_array) + _amount_to_add - 1; i >= 0; i--) {
        if (i < _position) {
            _new_array[@ i] = _new_array[i];
        } else if (i >= _position + _amount_to_add) {
            _new_array[@ i] = _array[i - _amount_to_add];
        } else {
            _new_array[@ i] = argument[i - _position + 3];
        }
    }

    return _new_array;
}   
    
    

/// @function array_join(array, array, [...arrays])
/// @param {array_id} array
/// @param {array_id} array
/// @param {array_id} ...arrays
/// @description Combines multiple arrays into one, and returns the new array. Note - returns a new array. MUST be assigned to be any use.
function array_join() {
    var _new_array, _counter, _current_array;
    _new_array = [];
    _counter = 0;

    for (var i = 0; i < argument_count; i++) {
        _current_array = argument[i];
        for (var j = 0; j < array_length(_current_array); j++) {
            _new_array[_counter++] = _current_array[j];
        }
    }

    return _new_array;
}   
    
    
/// @function list_to_array_shallow(list)
/// @param {ds_list} list
/// @description Takes a list and returns an array. Note - this function does not destroy the list. Note - This is a shallow copy.
function list_to_array_shallow(_list) {
    var _length, _new_array;
    _length = ds_list_size(_list);
    _new_array = array_create(_length);

    for (var i = 0; i < _length; i += 1) {
        _new_array[i] = _list[| i];
    }

    return _new_array;
}   
    
    
/// @function array_to_list_shallow(array)
/// @param {array_id} array
/// @description Takes an array and returns an list. Note - this function creates and returns a list. This list must be destroyed at some point. Note - this is a shallow copy only. Will not create nested lists.
function array_to_list_shallow(_array)
{
    var _new_list, _length;
    _new_list = ds_list_create();
    _length = array_length(_array)
    for (var i = 0; i < _length; i += 1) {
        ds_list_add(_new_list, _array[i]);
    }

    return _new_list;
}   
    
    
/// @function array_to_list_deep(array)
/// @param {array_id} array
/// @description Takes an array and returns an list. Note - this function creates and returns an list. This list must be destroyed at some point. Note - this is a deep copy and will create nested lists. This lists must be destroyed as well. Switching ds_list_add with list_add_list will mark all sub lists as lists, allowing for automatic destruction of sub lists when destroying the main list.
function array_to_list_deep(_array) {
    var _new_list, _length;
    _new_list = ds_list_create();
    _length = array_length(_array)
    for(var i = 0; i < _length; i++){
        if(is_array(_array[i])) {
            ds_list_add(_new_list, array_to_list_deep(_array[i])); 
        } else {
            ds_list_add(_new_list, _array[i]);
        }
    }   
    return _new_list;
}   
    
    
/// @function array_flatten(array)
/// @param {array_id} array
/// @description Recursively loops through the provided array and returns a new array the contains the values of the old array without any nesting. Note - will remove any completely empty arrays.  Note - returns a new array. MUST be assigned to be any use.
function array_flatten(_array) {
    var _new_array, _length;
    _new_array = [];
    _length = array_length(_array);
    
    for(var i = 0; i < _length; i++){
        if(is_array(_array[i])) {
            _new_array = array_join(_new_array, array_flatten(_array[i])); 
        } else {
            array_add_to_end(_new_array, _array[i]);
        }
    }

    return _new_array;
}   
    
    
/// @function array_find_index(array, value)
/// @param {array_id} array
/// @param {variable} value
/// @description Returns the first index of the value specified, searching from left to right. Returns -1 if the value is not found.
function array_find_index(_array, _value){
    var _length = array_length(_array);
    for (var i = 0; i < _length; i++) {
        if (_array[i] == _value) {
            return i;
        }
    }
    return -1;   
} 

/// @function array_find_index_last(array, value)
/// @param {array_id} array
/// @param {variable} value
/// @description Returns the last index of the value specified, searching from right to left. Returns -1 if the value is not found.
function array_find_index_last(_array, _value) {
    var _length = array_length(_array);
    for (var i = array_length(_array) - 1; i >= 0; i--) {
        if (_array[i] == _value) {
            return i;
        }
    }
    return -1;   
}
    

/// @function array_find_index_custom(array, script, [...script_arguments_array])
/// @param {array_id} array
/// @param {script_id} script
/// @param {array} script_arguments_array
/// @description Returns the first index of the value specified, searching from left to right. Returns -1 if the value is not found.
function array_find_index_custom(_array, _script) {
    var _length = array_length(_array);
    for (var i = 0; i < _length; i++) {
        if (_script(_array[i])) {
            return i;
        }
    }
    return -1;   
}


/// @function array_find_index_last_custom(array, script, [...script_arguments_array])
/// @param {array_id} array
/// @param {script_id} script
/// @param {array} script_arguments_array
/// @description Returns the last index of the value specified, searching from right to left. Returns -1 if the value is not found.
function array_find_index_custom_last(_array, _script) {
    for (var i = array_length(_array) - 1; i >= 0; i--) {
        if (_script(_array[i])) {
            return i;
        }
    }
    return -1;   
}


/// @function array_find_index_all(array, value)
/// @param {array} array
/// @param {variable} value
/// @description Returns an array of all indexs containing the value specified, searching from left to right. Returns an empty array if the value is not found.
function array_find_index_all(_array, _value) {
    var _length, _new_array, _counter;
    _length = array_length(_array);
    _new_array = [];
    _counter = 0;
    
    for (var i = 0; i < _length; i++) {
        if (_array[i] == _value) {
            _new_array[_counter++] = i;
        }
    }

    return _new_array;
}   
    

/// @function array_find_index_all_custom(array, script, [...script_arguments_array])
/// @param {array_id} array
/// @param {script_id} script
/// @description Returns an array of all indexs containing the value specified, searching from left to right. Returns an empty array if the value is not found.
function array_find_index_all_custom(_array, _script) {
    var _new_array, _counter, _length;
    _length = array_length(_array);
    _new_array = [];
    _counter = 0;
    for (var i = 0; i < _length; i++) {
        if (_script(_array[i])) {
            _new_array[_counter++] = i;
        }
    }

    return _new_array;
}
    
    
/// @function array_every(array, script, [...script_arguments_array])
/// @param {array_id} array
/// @param {script_id} script
/// @param {array} script_arguments_array
/// @description Returns true if all values matche.
function array_every(_array, _script) {
    var _length = array_length(_array);

    for (var i = 0; i < _length; i++) {
        if (!_script(_array[i])) {
            return false;
        }
    }

    return true;
}


/// @function array_some(array, script, [...script_arguments_array])
/// @param {array_id} array
/// @param {script_id} script
/// @param {array} script_arguments_array
/// @description Returns true if at least one value matches.
function array_some(_array, _script) {
    var _length = array_length(_array);

    for (var i = 0; i < _length; i++) {
        if (_script(_array[i])) {
            return true;
        }
    }

    return false;   
}


/// @function array_shuffle(array)
/// @param {array} array
/// @description Randomizes the order of an array. It does return the array, however, it will add them to the array itself.
function array_shuffle(_array) {
    var _length = array_length(_array);
    repeat (_length) {
        array_swap_positions(_array, irandom(_length - 1), irandom(_length - 1));
    }
    return _array;
}


/// @function array_filter(array, script, [script_arguments_array])
/// @param {array_id} array
/// @param {script_id} filter_script
/// @param {array} script_arguments_array
/// @description Creates and returns a new array that only has the values from the prior array that meet the requirement of the filter script.
function array_filter(_array, _script) {
    var _length, _new_array, _counter;
    _length = array_length(_array);
    _new_array = [];
    _counter = 0;
    for (var i = 0; i < _length; i += 1) {
        if (_script(_array[i])) {
            _new_array[_counter++] = _array[i];
        }
    }
    return _new_array;
}


/// @function array_reduce(array, script, [script_arguments_array])
/// @param {array_id} array
/// @param {script_id} filter_script
/// @description Returns the total value of all numbers in an array. Note - this script will cause an error if the array contains anything except numbers.
function array_reduce(_array, _script) {
    var _length, _accumulator;
    _length = array_length(_array);
    _accumulator = 0;
    for (var i = 0; i < _length; i++) {
        _accumulator += _script(_array[i]);
    }
    return _accumulator;   
}


/// @function array_sort(array, sorting_script)
/// @param {array_id} array
/// @param {script} sorting_script
/// @description Sorts an array using the sorting script provided. Modifies and eturns the original array. Does not need to be assigned. Uses a simple bubble sort: Time Complexity is O(n^2)
function array_sort(_array, _script) {
    var _no_change;
    for (var i = array_length(_array); i > 0; i -= 1) {
        _no_change = true;
        for (var j = 0; j < i - 1; j+= 1) {
            if (_script(_array[j], _array[j + 1])) {
                array_swap_positions(_array, j, j + 1);
                _no_change = false;
            }
        }
        if (_no_change) break;
    }
    return _array;
}


/// @function array_reverse(array)
/// @param {array_id} array
/// @description Returns the array in reverse order.
function array_reverse(_array) {
    var _position_1, _position_2, _position_3;
    _position_1 = 0;
    _position_2 = array_length(_array);
    while (_position_1 < --_position_2) {
        _position_3 = _array[_position_1];
        _array[_position_1++] = _array[_position_2];
        _array[_position_2] = _position_3;
    }
    return _array;
}


/// @function array_for_each(array, script, [script_arguments_array])
/// @param {array_id} array
/// @param {scripts} script_id
/// @param {array} script_arguments_array
/// @description Performs the script on each element of the array. It does return the array, however, it will add them to the array itself.
function array_for_each(_array, _script) {
    var _length = array_length(_array);   
    for (var i = 0; i < _length; i++) {
        _array[@ i] = _script(_array[i]);
    }
    return _array;
}


/// @function array_for_each_copy(array, script, [script_arguments_array])
/// @param {array_id} array
/// @param {scripts} script_id
/// @param {array} script_arguments_array
/// @description Copies the array and performs the script on each element of the copied array. Note - returns a new array. MUST be assigned to be any use.
function array_for_each_copy(_array, _script) {   
    var _length, _new_array;
    _length = array_length(_array);
    _new_array = array_copy_shallow(_array);   
    for (var i = 0; i < _length; i++) {
        _new_array[i] = _script(_array[i]);
    }
    return _new_array;   
}
 
Top