Extension functions for Array
s/Iterable
s that are compile-time converted to a single, optimal for-loop. Never again be concerned about performance when you need to throw on a couple map
s and filter
s. Any number of array modifications is guarenteed to run through just one loop at runtime!
// Place at top of file or in import.hx
using MagicArrayTools;
// ---
var arr = ["a", "i", "the", "and"];
// At compile-time this code is
// converted into a single for-loop.
arr.filter(s -> s.length == 1)
.map(s -> s.charCodeAt(0))
.filter(s -> s != null)
.count(s -> s == 105);
// |
// V
// This is what is generated and replaces
// the expression at compile-time.
{
var result = 0;
for(it in arr) {
if(it.length != 1) continue;
final it2 = it.charCodeAt(0);
if(it2 == null) continue;
if(it2 == 105) {
result++;
}
}
result;
}
# | What to do | What to write |
---|---|---|
1 | Install via haxelib. | haxelib install magic-array-tools |
2 | Add the lib to your .hxml file or compile command. |
-lib magic-array-tools |
3 | Add this top of your source file or import.hx . |
using MagicArrayTools; |
Now use this library's functions on an Array
, Iterable
, or Iterator
and let the magic happen!
Feature | Description |
---|---|
Inline Mode | A shorter, faster syntax for callbacks |
Disable Auto For-Loop | Temporarily or permanently disable the automatic for-loop creation |
Display For-Loop | Stringifies and traces the for-loop code that will be generated for debugging purposes |
map and filter |
Remapping and filtering functions |
forEach and forEachThen |
Iterate and run an expression or callback |
size and isEmpty |
Finds the number of elements |
count |
Counts the number of elements that match the condition |
find and findIndex |
Finds the first element that matches the condition |
indexOf |
Returns the index of the provided element |
every and some |
Check if some or all elements match the condition |
reduce |
Reduce to single value summed together using function |
asList and asVector |
Provides the result as a haxe.ds.List or haxe.ds.Vector |
concat |
Appends another Array , Iterable , or even separate for-loop |
fill |
Fill a subsection or the entire Array with a value |
While local functions can be passed as an argument, for short/one-line operations it is recommended "inline mode" is used. This resolves any issues that comes from Haxe type inferences, and it helps apply the exact expression where desired.
Any function that takes a callback as an argument can accept an expression (Expr
) that's just the callback body. Use a single underscore identifier (_
) to represent the argument that would normally be passed to the callback (usually the processed array item). This expression will be placed and typed directly in the resuling for-loop.
[1, 2, 3].map(i -> "" + i); // Error:
// Int should be String
// ... For function argument 'i'
[1, 2, 3].map("" + _); // Fix using inline mode!
[1, 2, 3].map((i:Int) -> "" + i); // (Explicit-typing also works)
In certain circumstances, one may want to disable the automatic for-loop building. Using the @disableAutoForLoop
metadata will disable this for all subexpressions. However, for-loops can still be manually constructed by appending .buildForLoop()
.
If manually building for-loops using buildForLoop()
is preferred, defining the compilation flag (-D disableAutoForLoop
) will disable the automatic building for the entire project.
class ConflictTester {
public function new() {}
public function map(c: (Int) -> Int) return 1234;
public function iterator(): Iterator<Int> { return 0...5; }
}
// ---
final obj = new ConflictTester();
// This generates a for-loop.
obj.map(i -> i);
// Unable to call "map" function on this
// Iterable unless auto for-loops are disabled.
@disableAutoForLoop {
obj.map(i -> i); // 1234
obj.map(i -> i).buildForLoop(); // [0, 1, 2, 3, 4]
}
Curious about the code that will be generated? Simply append .displayForLoop()
to the method chain, and the generated for-loop expression will be traced/printed to the console.
["a", "b", "c"]
.map(_.indexOf("b"))
.filter(_ >= 0)
.asList()
.displayForLoop();
// -- OUTPUT --
//
// Main.hx:1: {
// final result = new haxe.ds.List();
// for(it in ["a", "b", "c"]) {
// final it2 = it.indexOf("b");
// if(!(it2 >= 0)) continue;
// result.add(it2);
// }
// result;
// }
//
These functions work exactly like the Array
's map
and filter
functions.
function map(callback: (T) -> U): Array<U>;
function filter(callback: (T) -> Bool): Array<T>;
var arr = [1, 2, 3, 4, 5];
var len = arr.filter(_ < 2).length;
assert(len == 1);
var spaces = arr.map(StringTools.lpad("", " ", _));
assert(spaces[2] == " ");
Calls the provided function/expression on each element in the Array
/Iterable
. forEachThen
will return the array without modifying the elements. On the other hand, forEach
returns Void
and should be used in cases where the iterable object is not needed afterwards.
function forEach(callback: (T) -> Void): Void;
function forEachThen(callback: (T) -> Void): Array<T>;
// do something 10 times
(0...10).forEach(test);
// |
// V
for(it in 0...10) {
test(it);
}
// add arbitrary behavior within for-loop
["i", "a", "bug", "hello"]
.filter(_.length == 1)
.forEachThen(trace("Letter is: " + _))
.map(_.charCodeAt(0));
// |
// V
{
final result = [];
for(it in ["i", "a", "bug", "hello"]) {
if(it.length != 1) continue;
trace("Letter is: " + it);
final it2 = it.charCodeAt(0);
result.push(it2);
}
result;
}
size
counts the number of elements after the other modifiers are applied. isEmpty
is an optimized version that immediately returns false
upon the first element found and returns true
otherwise.
function size(): Int;
function isEmpty(): Bool;
(0...5).filter(_ % 2 == 0).size();
// |
// V
{
var result = 0;
for(it in 0...5) {
if(it % 2 != 0) continue;
result++;
}
result;
}
(10...20).filter(_ == 0).isEmpty();
// |
// V
{
var result = true;
for(it in 10...20) {
if(it != 0) continue;
result = false;
break;
}
result;
}
count
counts the number of elements that match the condition.
function count(callback: (T) -> Bool): Int;
(0...20).count(_ > 10);
// |
// V
{
var result = 0;
for(it in 0...20) {
if(it > 10) {
result++;
}
}
result;
}
find
returns the first element that matches the condition. findIndex
does the same thing, but it returns the index of the element instead.
function find(callback: (T) -> Bool): Null<T>;
function findIndex(callback: (T) -> Bool): Int;
["ab", "a", "b", "cd"].find(_.length <= 1);
// |
// V
{
var result = null;
for(it in ["ab", "a", "b", "cd"]) {
if(it.length <= 1) {
result = it;
break;
}
}
result;
}
vectorIterator.findIndex(_.magnitude > 3);
// |
// V
{
var result = -1;
var i = 0;
for(it in vectorIterator) {
if(it.magnitude > 3) {
result = i;
break;
}
i++;
}
result;
}
indexOf
returns the index of the first element that equals the provided argument. This function has three arguments, but only the first one is required.
function indexOf(item: T, startIndex: Int = 0, inlineItemExpr: Bool = false): Int;
startIndex
dictates the number of elements that must be processed before initiating the search. This functionality will not be generated at all as long as the argument is not provided or the argument is assigned a 0
literal.
inlineItemExpr
is a compile-time argument that must either a true
or false
literal. It defines how the item
expression will be used in the generated for-loop. If true
, the expression will be inserted into the for-loop exactly as passed. If not provided or false
, the expression will be assigned to a variable, and this variable will be used within the for-loop. Single identifiers and numbers will be automatically inlined since there is no additional runtime cost.
[22, 33, 44].indexOf(33);
// |
// V
{
var result = -1;
var i = 0;
for(it in [22, 33, 44]) {
if(it == 33) {
result = i;
break;
}
i++;
}
result;
}
// If the third argument was "true", the "_value" variable would not be generated.
// Instead, the comparison would be: if(it == World.FindPlayer())
// FindPlayer might be an expensive operation, so this is not the default behavior.
EntitiesIterator.indexOf(World.FindPlayer(), 1, false);
// |
// V
{
var result = -1;
var i = 0;
final _value = World.FindPlayer();
var _indexOfCount: Int = 1;
for(it in EntitiesIterator) {
if(_indexOfCount > 0) {
_indexOfCount--;
} else if(it == _value) {
result = i;
break;
}
i++;
}
result;
}
every
returns true
if every element returns true
when passed to the provided callback. On the other hand, some
returns true
as long as at least one element passes.
function every(callback: (T) -> Bool): Bool;
function some(callback: (T) -> Bool): Bool;
[75, 7, 12, 93].every(_ > 0);
// |
// V
{
var result = true;
for(it in [75, 7, 12, 93]) {
if(it <= 0) {
result = false;
break;
}
}
result;
}
(1...10).some(_ == 4);
// |
// V
{
var result = false;
for(it in 1...10) {
if(it == 4) {
result = true;
break;
}
}
result;
}
reduce
calls a function on every element to accumulate all the values. The returned value of the previous call is passed as the first argument; the second argument is the element being iterated on. The returned value of the final call is what reduce
returns.
function reduce(callback: (T, T) -> T): T;
["a", "b", "c", "d"].reduce((a, b) -> a + b);
// |
// V
{
var result = null;
var _hasFoundValue = false;
for(it in ["a", "b", "c", "d"]) {
if(!_hasFoundValue) {
_hasFoundValue = true;
result = it;
} else {
result = result + it;
};
};
result;
}
These functions change the resulting data-structure to either be a haxe.ds.List
or haxe.ds.Vector
.
function asList(): haxe.ds.List<T>;
function asVector(): haxe.ds.Vector<T>;
(0...10).filter(_ % 3 != 0).asList();
// |
// V
{
var result = new haxe.ds.List();
for(it in 0...10) {
if(it % 3 == 0) continue;
result.add(it);
}
result;
}
(0...10).filter(_ % 3 != 0).asVector();
// |
// V
{
var result = [];
for(it in 0...10) {
if(it % 3 == 0) continue;
result.push(it);
}
haxe.ds.Vector.fromArrayCopy(result);
}
Appends the provided array/elements to the current array. The output generates an additional for-loop to iterate over the new elements. This library's functions can be called on the first argument to this function, and the modifiers will be recursively flattened and applied exclusively to the new loop.
function concat(other: Array<T> | Iterable<T> | Iterator<T>): Array<T>;
(0...10).concat([100, 1000, 9999]);
// |
// V
{
var result = [];
for(it in 0...10) {
result.push(it);
}
for(it in [100, 1000, 9999]) {
result.push(it);
}
result;
}
// Pass a "for-loop" as an argument and it will be merged.
(0...10).filter(_ % 2 == 0).concat( (0...10).filter(_ % 3 == 0) );
// |
// V
{
var result = [];
for(it in 0...10) {
if(it % 2 != 0) continue;
result.push(it);
}
for(it in 0...10) {
if(it % 3 != 0) continue;
result.push(it);
}
result;
}
fill
fills the resulting Array
with the provided value. A subsection can be filled using the second and third arguments.
function fill(value: T, startIndex: Int = 0, endIndex: Int = this.length): Array<T>;
[1, 2, 3].fill(10);
// |
// V
{
var result = [];
for(it in [1, 2, 3]) {
var it2 = 10;
result.push(it2);
};
result;
}
(0...10).fill(999, 2, 8);
// |
// V
{
var result = [];
var i = 0;
for(it in (0 ... 10)) {
var it2 = if((i >= 2) && (i < 8)) {
999;
} else {
it;
};
result.push(it2);
i++;
};
result;
}