Use of Programmaticon V's array methods


#1

For those of you determined enough to get the Programmaticon V the array library may still be a puzzle:
How to use them and what are their advantages?

Simple answer: it allows you to generate some very light and fast code.
Good if your level code keeps crashing or if you are running into HEL (hard execution limit, currently 3 million)

I will explain how to use the most important functions in the library: map, filter and reduce
An appendix will list all the array methods grouped by categories
This document assumes you have already read the methods description. I will not copy that information.

Advantage of array methods:
They allow you to provide a callback function which will be called for every single element of the array.
The transpiled code will be much simpler than when creating your own loop over the array

Severe limitation:
You cannot call any other function inside the callback function. The method’s documentation implies that you can, but even calling

function forwardValue(e){return e;}

will stop your code from working.
(Issue 1/3 @nick)

1. Use Map to generate new information.

Map allows you to construct another array by processing the initial array arguments

enemies=this.findEnemies();
distances=enemies.map(this.distanceTo);

We have just computed the distances to every single enemy with one function call!
That’s extremely light. But we cannot do much with the distance array by itself, because you do not know what each element represents. We must link the info to original enemy list:

2. Define your callback functions to process the array

//Must define this before using
function distanceIndex(distance,index){return {d:distance,i:index};}

enemy_info=distances.map(distanceIndex);

Now enemy_info links the distance value to the enemies index in the original array.
In this example we just use the distance. But in your program you can add as much information about the enemies: estimated DPS, range, speed or how much money he owns you.

Most of the callback functions will receive 3 parameters:

elem //the value of the element in list for which the callback is called
index //the position of the element in list
array //the name of the list

If you are writing Python code make sure you accepting all these paramters

##3. Use filter to select only part of the array elements.

Lets say we are playing agrippa-defence and we are really annoyed by “Palt”,“Smerk”,“Godel” (they didn’t tip at the dinner)
In a real level we’ll go for ogres, throwers, wiches and other stuff - it’s basically about selection

We’ll like to use:

selection=enemies.filter(names.indexOf);

but unfortunately indexOf cannot be used as callback function. My guess is that indexOf expects (elem,searchFromIndex) while a callback function receives (elem,elem_index).
(Issue 2/3 @nick)

The first 2 simple solutions:

  • define your own indexOf function
  • use indexOf but define your own filter function
    are not efficient

Instead we define the values we search for as property names and we use the JavaScript’s automatic hashing of property names:

//instead of names we can have enemy types and 
//instead of 'true' an index to the enemy type information object
var HashNames={"Palt":true,"Smerk":true,"Godel":true};

function inHash(elem){return (hash_list[enemies[elem.i].id]);}

//use can use globals to pass parameters to callback functions
hash_list=HashNames;
selection=enemy_info.filter(inHash);

##4. Use reduce to select a winning value from a list

Cosidering it is an enemy, what he wins is a good beating.
The reduce functions get two elements as arguments and they have to return the best of the two:

function nearestReduce(e1,e2){
    if (e1.d<e2.d) return e1;
    else return e2;
}

if(selection.length){
    nearest=selection.reduce(nearestReduce);
    //We could have used one line instead of array methods 
    //!!! What are you teaching ???
    nearest2=this.findNearest(this.findByType("munchkin",this.findEnemies()));
    this.say(enemies[nearest.i].id);
}

##5. Use a heavy iteration to determine how good is your code:

//https://codecombat.com/play/level/the-agrippa-defense
function distanceIndex(distance,index){return {d:distance,i:index};}
function inHash(elem){return (hash_list[enemies[elem.i].id]);}
function nearestReduce(e1,e2){
    if (e1.d<e2.d) return e1;
    else return e2;
}

var HashNames={"Palt":true,"Smerk":true,"Godel":true};
var enemies,distance,enemy_info,hash_list,selection,nearest;
var i,iterations=0;
loop{
    enemies=this.findEnemies();
    //test your code in a heavy iteration until HEL
    if(!enemies.length) continue;
    for(i=0;i<3000;i++,iterations++){
	distances=enemies.map(this.distanceTo);
	enemy_info=distances.map(distanceIndex); 
        //a real selection decision may use all distances
	hash_list=HashNames; //pass callback parameter through global value
	selection=enemy_info.filter(inHash);
	if(selection.length){
	    nearest=selection.reduce(nearestReduce);
	    //We could have used one line instead of array methods !!! 
            //What are teaching ????
	    //nearest2=this.findNearest(this.findByType("munchkin",this.findEnemies()));
	    //this.say(enemies[nearest.i].id);
	 }
    }
    this.say(iterations);
}

##6. Array methods by cathegory:

// 1. Reduce functions:
every -> boolean AND
some  -> boolean OR
reduce -> iterrative reduce operator from the left
reduceRight 

// 2. Array transformation. 
//Some modify the parent arey, some not. See the comments:
filter 		//elem selection) NOT MODIFIED
map 		//elem transformation by handle function) NOT MOTIFIED
slice (begin,end_not_included)		
               //returns slice of array, the original array is NOT MODIFIED
sort(callback)	//sorts IN PLACE, also passes result. Callback returns true if 1st elem is greater
reverse  	//array is reversed IN PLACE!
splice(index,how_many_deleted, <list of params to be inserted>)  
               // IN PLACE, deletes & inserts in part of the array

// 3. Iterators:
entries // key,value tuple
keys
  
// 4. Search:
find,findIndex(callback)	
        // returns the first element for which the callback function is true
indexOf(elem,fromIndex) 
	// negValue - index position computed from the end of the array 
	// -1 is the last elem
	//array is still searched front to back
lastIndexOf //indexOf, search backwards

//5. Multi array manipulations:
concat

//6. Processing:
forEach	//runs the callback for each element. Like map but no array created

//7. Deletion & Insertion
//The delete functions also return the element deleted
pop     //deletes last place  
shift   //deletes first place
push    //inserts on last
unshift //inserts on first 

//8. Print - join all elemens into a single string
toString	  //joins the elements together using the default separator ','
toLocaleString	  //uses each object's toString function to convert it into a string
join('separator') // joins all array elements in a string using the separator

Question:
Is there a planed level to teach the use of array methods within CombatCombat?
Issue 3/3 @nick


Javascript array sort Method problem?
Code Combat Posters and Curriculum (Standards, Assessments, and Lesson Plans)
#2

##4b Use find to get the first value that is good enough

Reduce gives you the best elem of the whole lot and filter gives you all that are good enough. What if you only want one value?

Case in point: being a long-life fan of the enemy hero, you want to meet him and shake hands as fast as possible:

function findHero(elem){
    return (elem.id=="Hero Placeholder" || elem.id=="Hero Placeholder 1");} 

ehero=enemies.find(findHero);

Unfortunately find cannot be used as a callback because of its parameter list: find(callback_function)


#3

Great post! We will be able to fix all the issues with the callbacks not being able to call functions within them with the new interpreter that Rob is working on, as well as a lot of performance problems and drastically raising the hard execution limit.

I suppose at some point we should make levels teaching map, filter, reduce, etc. We are focusing on filling gaps in the pacing in the earlier levels for the most part, though. Open to player-created levels in the glacier to work on these more advanced concepts in the meantime. :wink:

By the way, we don’t document it, but Lodash functions are also available as well: https://github.com/lodash/lodash/blob/2.4.2/doc/README.md – sometimes those are easier than using the builtin JS map/filter/reduce.


#4

Very nice! Thanks for contributing these excellent resources.

Couple observations:

Yep, that’s true. However, there are also a few other problems: when you pass names.indexOf as an argument to a function, you are actually just passing a reference to the Array.prototype.indexOf method. This method looks for an element in the array (or, more precisely, iterable object) referenced by the this value used for the method invocation. So when you pass a reference to the indexOf function as in enemies.filter(names.indexOf), the indexOf function is not bound to the names array, i.e. the this value used for its invocation would not reference the names array as one may expect. Another problem is that the Array.prototype.indexOf function returns -1 when the searched element is not found, which is a truthy value and would not remove the given array item from the filter() output. What you really want is Array.prototype.includes (instead of indexOf), but that is a new addition to the language and thus not widely available. Though, includes would still have the same problem as indexOf regarding the fromIndex parameter, and that problem cannot be solved with the bind method as the first parameter must stay unbound.

Well, your inHash solution is pretty good, simple and fast, so take the paragraph above just as a brain exercise. :slightly_smiling:

A few more things worth mentioning:

  • The find and findIndex instance methods, as well as the entries and keys iterators, were introduced in ECMAScript 2015. They are new and not available in every browser/environment. This also leads me to wonder, would this impact simulation results depending on who/which browser runs them? @nick

  • I would recommend always declaring your variables with var, even global ones. As you may know, in sloppy mode, when you assign to an undeclared variable, it implicitly creates a global variable. However, this can be bad for several reasons:

    1. Assigning to undeclared variables is an error in strict mode. Strict mode helps catching unintended mistakes and thus speeds up development, therefore it is generally considered a good practice to have it enabled. Also, ECMAScript 2015 modules run implicitly and exclusively in strict mode, so it is better if you get prepared for the future.
    2. In early levels, you may lose bonus points for “clean code” if you forget to declare variables.
    3. It is generally a good practice to be clear and explicit with what you mean. Use var when declaring a variable, even if it is in the global scope.

By the way, how is @rob’s new interpreter coming along? If I recall correctly, you guys were planning to improve ES2015 (ES6) support as well with that. I can only imagine that developing a full-fledged interpreter can take several months or even years, so I’m curious if we will get a chance to see this new interpreter in the upcoming months or if it will take a while longer.

While in this topic, I’ve started working in a fix to the high-order functions on top of the current CodeCombat stack. In fact, I had basically everything to get the built-in high-order functions (Array.prototype.map/filter/etc.) working for quite a long time, I just had to test some edge cases, do some clean up and write the proper documentation. However, life has got in the way—several times!—but I believe this fix should get to see the light of the day soon enough. :smile:


#5

I suppose you are right–using new language features would cause simulation results to differ based on which browser ran the simulation. I hadn’t thought of that. Once we are on the interpreter, though, everything should be consistent.

Rob just showed me some progress from the interpreter today; it’s nearing completion of being able to run all of normal JS code, with some ES6 stuff included so far. After that’s all working, we still need to have it support all of the Aether-specific stuff it’s supplanting, like instrumentation, builtin protection, etc., so not sure how long is left, but he’s making great progress.