Skip to content

Repository files navigation

Extension functions for Arrays/Iterables 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 maps and filters. Any number of array modifications is guarenteed to run through just one loop at runtime!

// Place at top of file or in import.hxusingMagicArrayTools;
// ---vararr= ["a", "i", "the", "and"];
// At compile-time this code is// converted into a single for-loop.arr.magiter()
.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.
{
varresult=0;
for(itinarr) {
if(it.length!=1) continue;
finalit2=it.charCodeAt(0);
if(it2==null) continue;
if(it2==105) {
result++;
}
}
result;
}

[Installation]

#What to doWhat to write
1Install via haxelib.
haxelib install magic-array-tools
2Add the lib to your .hxml file or compile command.
-lib magic-array-tools
3Add this top of your source file or import.hx.
usingMagicArrayTools;

Now use this library's functions on an Array, Iterable, or Iterator and let the magic happen!


[Feature Index]

FeatureDescription
Inline ModeA shorter, faster syntax for callbacks
Display Generated LoopStringifies and traces the code that will be generated for debugging purposes
map and filterRemapping and filtering functions
forEach and forEachThenIterate and run an expression or callback
size and isEmptyFinds the number of elements
countCounts the number of elements that match the condition
find and findIndexFinds the first element that matches the condition
indexOfReturns the index of the provided element
every and someCheck if some or all elements match the condition
reduceReduce to single value summed together using function
asList and asVectorProvides the result as a haxe.ds.List or haxe.ds.Vector
concatAppends another Array, Iterable, or even separate for-loop
fillFill a subsection or the entire Array with a value

[Features]

Inline Mode

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].magiter().map(i->""+i); // Error:// Int should be String// ... For function argument 'i'
[1, 2, 3].magiter().map(""+_); // Fix using inline mode!
[1, 2, 3].magiter().map((i:Int) ->""+i); // (Explicit-typing also works)

Display Generated Loop

Curious about the code that will be generated? Simply append .displayResult() to the method chain, and the generated for-loop expression will be traced/printed to the console at compile-time! You can even place it between calls to debug up to a certain point in the chain.

["a", "b", "c"]
.magiter()
.map(_.indexOf("b"))
.filter(_>=0)
.asList()
.displayResult();
// -- 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;// }//

map and filter

These functions work exactly like the Array's map and filter functions.

functionmap(callback: (T) ->U):Array<U>;
functionfilter(callback: (T) ->Bool):Array<T>;
vararr= [1, 2, 3, 4, 5];
varlen=arr.magiter().filter(_<2).length;
assert(len==1);
varspaces=arr.magiter().map(StringTools.lpad("", "", _));
assert(spaces[2] =="");

forEach and forEachThen

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.

functionforEach(callback: (T) ->Void):Void;
functionforEachThen(callback: (T) ->Void):Array<T>;
// do something 10 times
(0...10).magiter().forEach(test);
// |// Vfor(itin0...10) {
test(it);
}
// add arbitrary behavior within for-loop
["i", "a", "bug", "hello"]
.magiter()
.filter(_.length==1)
.forEachThen(trace("Letter is: "+_))
.map(_.charCodeAt(0));
// |// V
{
finalresult= [];
for(itin ["i", "a", "bug", "hello"]) {
if(it.length!=1) continue;
trace("Letter is: "+it);
finalit2=it.charCodeAt(0);
result.push(it2);
}
result;
}

size and isEmpty

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.

functionsize():Int;
functionisEmpty():Bool;
(0...5).magiter().filter(_%2==0).size();
// |// V
{
varresult=0;
for(itin0...5) {
if(it%2!=0) continue;
result++;
}
result;
}
(10...20).magiter().filter(_==0).isEmpty();
// |// V
{
varresult=true;
for(itin10...20) {
if(it!=0) continue;
result=false;
break;
}
result;
}

count

count counts the number of elements that match the condition.

functioncount(callback: (T) ->Bool):Int;
(0...20).magiter().count(_>10);
// |// V
{
varresult=0;
for(itin0...20) {
if(it>10) {
result++;
}
}
result;
}

find and findIndex

find returns the first element that matches the condition. findIndex does the same thing, but it returns the index of the element instead.

functionfind(callback: (T) ->Bool):Null<T>;
functionfindIndex(callback: (T) ->Bool):Int;
["ab", "a", "b", "cd"].magiter().find(_.length<=1);
// |// V
{
varresult=null;
for(itin ["ab", "a", "b", "cd"]) {
if(it.length<=1) {
result=it;
break;
}
}
result;
}
vectorIterator.magiter().findIndex(_.magnitude>3);
// |// V
{
varresult=-1;
vari=0;
for(itinvectorIterator) {
if(it.magnitude>3) {
result=i;
break;
}
i++;
}
result;
}

indexOf

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.

functionindexOf(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].magiter().indexOf(33);
// |// V
{
varresult=-1;
vari=0;
for(itin [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.magiter().indexOf(World.FindPlayer(), 1, false);
// |// V
{
varresult=-1;
vari=0;
final_value=World.FindPlayer();
var_indexOfCount:Int=1;
for(itinentitiesIterator) {
if(_indexOfCount>0) {
_indexOfCount--;
} elseif(it==_value) {
result=i;
break;
}
i++;
}
result;
}

every and some

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.

functionevery(callback: (T) ->Bool):Bool;
functionsome(callback: (T) ->Bool):Bool;
[75, 7, 12, 93].magiter().every(_>0);
// |// V
{
varresult=true;
for(itin [75, 7, 12, 93]) {
if(it<=0) {
result=false;
break;
}
}
result;
}
(1...10).magiter().some(_==4);
// |// V
{
varresult=false;
for(itin1...10) {
if(it==4) {
result=true;
break;
}
}
result;
}

reduce

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.

functionreduce(callback: (T, T) ->T):T;
["a", "b", "c", "d"].magiter().reduce(_1+_2);
// |// V
{
varresult=null;
var_hasFoundValue=false;
for(itin ["a", "b", "c", "d"]) {
if(!_hasFoundValue) {
_hasFoundValue=true;
result=it;
} else {
result=result+it;
};
};
result;
}

asList and asVector

These functions change the resulting data-structure to either be a haxe.ds.List or haxe.ds.Vector.

functionasList():haxe.ds.List<T>;
functionasVector():haxe.ds.Vector<T>;
(0...10).magiter().filter(_%3!=0).asList();
// |// V
{
varresult=newhaxe.ds.List();
for(itin0...10) {
if(it%3==0) continue;
result.add(it);
}
result;
}
(0...10).magiter().filter(_%3!=0).asVector();
// |// V
{
varresult= [];
for(itin0...10) {
if(it%3==0) continue;
result.push(it);
}
haxe.ds.Vector.fromArrayCopy(result);
}

concat

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.

functionconcat(other:Array<T> | Iterable<T> | Iterator<T>):Array<T>;
(0...10).magiter().concat([100, 1000, 9999]);
// |// V
{
varresult= [];
for(itin0...10) {
result.push(it);
}
for(itin [100, 1000, 9999]) {
result.push(it);
}
result;
}
// Pass a "for-loop" as an argument and it will be merged.
(0...10).magiter().filter(_%2==0).concat( (0...10).filter(_%3==0) );
// |// V
{
varresult= [];
for(itin0...10) {
if(it%2!=0) continue;
result.push(it);
}
for(itin0...10) {
if(it%3!=0) continue;
result.push(it);
}
result;
}

fill

fill fills the resulting Array with the provided value. A subsection can be filled using the second and third arguments.

functionfill(value:T, startIndex:Int=0, endIndex:Int=this.length):Array<T>;
[1, 2, 3].magiter().fill(10);
// |// V
{
varresult= [];
for(itin [1, 2, 3]) {
varit2=10;
result.push(it2);
};
result;
}
(0...10).magiter().fill(999, 2, 8);
// |// V
{
varresult= [];
vari=0;
for(itin (0...10)) {
varit2=if((i>=2) && (i<8)) {
999;
} else {
it;
};
result.push(it2);
i++;
};
result;
}

About

Extension functions for Arrays/Iterables that are compile-time converted to a single, optimal for-loop.

Resources

Stars

39 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages