CodingForums.com

CodingForums.com (http://www.codingforums.com/index.php)
-   JavaScript programming (http://www.codingforums.com/forumdisplay.php?f=2)
-   -   filter() function question (http://www.codingforums.com/showthread.php?t=285289)

jmrker 01-04-2013 09:31 PM

filter() function question
 
I'm trying to understand the filter function and have run into a problem with my first test. :confused:

Why does the function seem to add 1 to each element of the original array
but returns one less element in the new array?

Code:

<!DOCTYPE HTML>
<html>
<head>
<title> Untitled </title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript">

function addOne(element, index, array) { return (element++); }
var Aarr = [0,1,2,3,4,5,6,7,8,9];
var Barr = Aarr.filter(addOne);
alert('Orig: \n'+Aarr+'\n\nadd 1: \n'+Barr);

/*
// From: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter
function isBigEnough(element, index, array) { return (element >= 10); }
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
alert(filtered);    // filtered is [12, 130, 44]
*/

</script>

</head>
<body>

</body>
</html>

The commented out test (isButEnough) seems to work as advertised.

Old Pedant 01-04-2013 10:05 PM

Ready to kick yourself?

Doing return element++; means you return the ORIGINAL VALUE of the value of the element.

That's what POST-INCREMENT means. "Get the value before the increment as the value of the expression, *then* do the increment."

Because you are incrementing a LOCAL COPY of element, that effectively means you are just throwing away the incremented value;

You will get *EXACTLY* the same results doing
Code:

function addOne(element, index, array)
{
    return element;
}

And because *ONLY* the first element of the array has a value of zero, *THAT* element is the only one that is seen as "false" by JavaScript as the result of the call to the filter function.

Try this, instead:
Code:

function addOne(element, index, array) { return (element++); }
var Aarr = [0,1,0,3,4,5,0,7,0,9];
var Barr = Aarr.filter(addOne);
alert('Orig: \n'+Aarr+'\n\nadd 1: \n'+Barr);

Your filter is saying "filter out all the elements that have a value of zero." Nohthing more.

What were you *expecting* to happen??

DaveyErwin 01-04-2013 10:06 PM

function addOne(element, index, array) { return ++array[index]; }
var Aarr = [0,1,2,3,4,5,6,7,8,9];
Aarr.filter(addOne);
alert(Aarr);

Old Pedant 01-04-2013 10:16 PM

Quote:

Originally Posted by DaveyErwin (Post 1304109)
function addOne(element, index, array) { return ++array[index]; }
var Aarr = [0,1,2,3,4,5,6,7,8,9];
Aarr.filter(addOne);
alert(Aarr);

Well, yes. That will increment each element of the array by one.

But you would get the same results using
Code:

function addOne(element, index, array)
{
    ++array[index];
    return false; // or return Math.random() < 0.5; even
}

The return value from filter has no impact on what happens in the *original* array.

If you were to do this:
Code:

function addOne(element, index, array) { return ++array[index]; }
var Aarr = [0,1,2,3,4,5,6,7,8,9];
var Barr = Aarr.filter(addOne);
alert(Aarr + "\n" + Barr);

You would find that Barr will contain a copy of the *original* array (0 through 9) while Aaar has every element incremented by one.

But, again, if you started with (say)
Code:

var Aarr = [0,1,-1,3,-1,7,0];
Then you would end up with
Code:

Aarr == [ 1,2,0,4,0,8,1 ]
Barr == [ 0,1,3,7,0 ]

Because the -1s on being increment become zero and so the return from filter is, effectively, false and those -1s get filtered out.

Old Pedant 01-04-2013 10:18 PM

So, again, I'm not at all sure what JMrker was trying to accomplish.

jmrker 01-04-2013 10:39 PM

Quote:

Originally Posted by Old Pedant (Post 1304113)
So, again, I'm not at all sure what JMrker was trying to accomplish.

I was trying to add 1 to each element of the original array
And see that the new array had values all incremented by one.

I thought I could avoid a for...loop with a filter() command.

Old Pedant 01-04-2013 11:02 PM

Well, it's kind of hacky, but you could do this:
Code:

function addOne(element, index, array)
{
    ++array[index];
    return true; // just in case the element was -1 !!!
}
var Aarr = [0,1,2,3,4,5,6,7,8,9];
var temp = Aarr.filter(addOne);
var Barr = Aarr;
Aarr = temp;
alert(Aarr + "\n" + Barr);

No?

But, really, it would be more sensible to maybe do this:
Code:

Array.prototype.addOne = function( )
{
    var ar = [];
    for ( var i = 0; i < this.length; ++i )
    {
        ar[i] = this[i] + 1;
    }
    return ar;
}
var Aarr = [0,1,2,3,4,5,6,7,8,9];
var Barr = Aarr.addOne();

*SOMEWHERE* there has to be a loop through the elements. Granted, with filter() the loop is in native code, not JS, but then the call to the filter method for *each* element has to be tons slower than a simple loop in JS code. I would bet it would be an order of magnitude slower, in fact.

jmrker 01-04-2013 11:30 PM

Thanks. I like the prototype version the best.

Obviously I have a way to go to understand all the nuances of this JS language!

Old Pedant 01-04-2013 11:52 PM

Another easy option:
Code:

<script type="text/javascript">
Array.prototype.forEach = function( callback )
{
    var ar = [];
    for ( var i = 0; i < this.length; ++i )
    {
        ar[i] = ( callback == null ) ? this[i] : callback( this[i] );
    }
    return ar;
}

var Aarr = [0,1,2,3,4,5,6,7,8,9];

var Barr = Aarr.forEach( function(element) { return element + 17; } );

var Carr = Barr.forEach( );

alert(Aarr + "\n" + Barr + "\n" + Carr);
</script>

See it? Now you have a forEach method that in turn takes a function that allows you to mangle each element, one at a time, as you wish.

As demonstrated with the var Carr = line, if you omit the callback function then forEach turns into a simple array copy. (Not efficient, but better than crashing when the callback is omitted.)

Old Pedant 01-04-2013 11:59 PM

If you wanted, you could write that as
Code:

<script type="text/javascript">
Array.prototype.forEach = function( callback )
{
    var ar = [];
    for ( var i = 0; i < this.length; ++i )
    {
        ar[i] = ( callback == null ) ? this[i] : callback( this[i], this, i );
    }
    return ar;
}

var Aarr = [0,1,2,3,4,5,6,7,8,9];

var Barr = Aarr.forEach(
        function(element, array, index)
        {
            array[index] *= 100;
            return element + 17;
        }
    );

var Carr = Barr.forEach( );

alert(Aarr + "\n" + Barr + "\n" + Carr);
</script>

So that the caller of forEach could (as demonstrated) optionally modify the original array at the same time he/she returns a value for the new array. Taking a lead from how filter( ) works.

jmrker 01-05-2013 01:45 AM

Well, you've managed to confuse the fool out of me.

I started out trying to figure out the .filter() function,
but have abandoned that because I thought I understood your Array.prototype function.

Now I don't understand what's happening with your latest modification with the 'callback' addition.

Here is the test code I am using to try to understand the working.
In the comment section at the end, I list what I expected and what is displayed.
??? indicates that I don't understand the results.

If you have the time, now that you have fogged my prior clarity, could you explain the
difference between what I thought I know and what is really happening?
(I may be testing your mind reading skills with that last statement).

Code:

<script type="text/javascript">
Array.prototype.forEach = function( callback ) {
  var ar = [];
  for ( var i = 0; i < this.length; ++i ) {
    ar[i] = ( callback == null ) ? this[i] : callback( this[i], this, i );
  }
  return ar;
}

var Aarr = [0,1,2,3,4,5,6,7,8,9];

var Barr  = Aarr.forEach( function(element) { return element + 1; } );
var BBarr = Aarr.forEach( function(element, array, index) { array[index] += 1; return element; } );
var Carr = Barr.forEach( );
var Darr = Aarr.forEach( );
var Earr = Aarr.forEach( function(element, array, index) { array[index] *= 100; return element; } );

alert('A : '+Aarr + "\nB : "+Barr +'\nBB: '+BBarr + "\nC : "+Carr + "\nD : "+Darr + "\nE : "+Earr +"\nA : "+Aarr);

/*
    Expected to see:
A : 0,1,2,3,4,5,6,7,8,9
B : 1,2,3,4,5,6,7,8,9,10
BB: 1,2,3,4,5,6,7,8,9,10
C : 1,2,3,4,5,6,7,8,9,10
D : 0,1,2,3,4,5,6,7,8,9
E : 0,100,200,300,400,500,600,700,800,900
A : 0,1,2,3,4,5,6,7,8,9
 
    Actual display:
A : 100,200,300,400,500,600,700,800,900,1000  ???
B : 1,2,3,4,5,6,7,8,9,10
BB: 0,1,2,3,4,5,6,7,8,9                        ???
C : 1,2,3,4,5,6,7,8,9,10
D : 1,2,3,4,5,6,7,8,9,10                      ???
E : 1,2,3,4,5,6,7,8,9,10                      ???
A : 100,200,300,400,500,600,700,800,900,1000  ???

*/
</script>


Old Pedant 01-05-2013 02:06 AM

Let's take them one at a time.

First of all, understand this: Whatever the callback function *RETURNS* for each element it is called for is what ends up in the OUTPUT array. Anything the function does to the *ORIGINAL* array has *NO IMPACT AT ALL* on what happens to the output array.

SO;
Code:

var Barr  = Aarr.forEach( function(element) { return element + 1; } )
You are not changing any elements of the Aaar array; you are returning each element + 1. So each element of Barr will be one greater than Aarr.

Great. That's what you expected. That's what you got.
*********
Code:

var BBarr = Aarr.forEach( function(element, array, index) { array[index] += 1; return element; } );
You are changing each element of the input array. But you are returning the *original* element value. So the output array (BBarr) will get a copy of the original array while, at the same time, each element of the original array is incremented by 1.

Remember: the value of element *IS* the ORIGINAL value of the element. Changing the element value inside the current (thiis) array does *NOT* impact the value of element.

I *THINK* that what you *MEANT* to do here was
Code:

var BBarr = Aarr.forEach( function(element, array, index) { array[index] += 1; return array[index]; } );
Do you see the *HUGE* difference that makes? Here, you IGNORE the original value of element and go fetch it again as the return value. You could have also written this as
Code:

var BBarr = Aarr.forEach( function(element, array, index) { return ++array[index]; } );
************
Code:

var Earr = Aarr.forEach( function(element, array, index) { array[index] *= 100; return element; } );
Same thing as with BBarr. You are modifying the input (this) array but returning the ORIGINAL element value, so Earr gets a copy of the input array before the modifications.

Old Pedant 01-05-2013 02:08 AM

If you do NOT want to modify the input (this) array, then do *NOT* use the array or index arguments that are passed to the callback! Simple as that.

What you expected could have been produced via
Code:

var Aarr = [0,1,2,3,4,5,6,7,8,9];

var Barr  = Aarr.forEach( function(element) { return element + 1; } );
var BBarr = Aarr.forEach( function(element) { return ++element; /* same as element + 1 */ } );
var Carr = Barr.forEach( );
var Darr = Aarr.forEach( );
var Earr = Aarr.forEach( function(element) { return element * 100; } );


Old Pedant 01-05-2013 02:23 AM

Maybe if we look at the forEach method it will be clearer. It truly is simple.

First of all, let me simplify it to *require* that the callback function be supplied:
Code:

Array.prototype.forEach = function( callback ) {
  // we build the new array here:
  var ar = [];
  // we loop through every element of the old array:
  for ( var i = 0; i < this.length; ++i ) {
    // and we put the RESULT of calling the callback function into
    // the corresponding element of the new array:
    ar[i] = callback( this[i], this, i );
  }
  // finally returning the new array:
  return ar;
}

So the only thing to really need to understand is this line:
Code:

    ar[i] = callback( this[i], this, i );
The assignment into the new array is hopefully obvious. So let's look at *HOW* the callback function is called:
Code:

    callback(
        this[i], /* the *VALUE* of the current (i-th) element of the array */
        this,  /* the *ENTIRE* current array */
        i /* the element number */
    );

So you can see that in your callback function:
Code:

// using one of your example:
function(element, array, index)
{
    // remember: element is the *VALUE* of array[index]
    // but it was *ALREADY* fetched out of the array in the forEach method/function
    // so when we do this, we are indeed changing the element number index in the
    // original array...
    array[index] += 1;
    // but that in no way affects the value of element:
    return element;
}

Did you perhaps forget that arrays are passed to functions *BY REFERENCE*? So the variable array in your callback function is 100% the same *OBJECT* as the array referred to by this in the forEach method.

Clearer?

jmrker 01-05-2013 02:32 AM

Thank you very much for that extensive discussion.
It helps me and perhaps others who may view this thread.
I appreciate your time.


All times are GMT +1. The time now is 05:43 PM.

Powered by vBulletin®
Copyright ©2000 - 2013, Jelsoft Enterprises Ltd.