Lodash drop() method

https://www.codecademy.com/paths/web-development/tracks/web-dev-js-arrays-loops-objects/modules/pjs-javascript-capstone/projects/lodash

I’ve read through the forum under the drop() method sections before writing, but did not get the answer i am looking for.

The video tutorial and like some of my colleagues here in the forum have this:

drop(arr, number){
if(number === undefined){
number=1;
}
let droppedArray= arr.slice(number);
return droppedArray;
},

I completely understand this and it works. My confusion is with my code. I have:

drop(arr, number){
if(!number){
number=1;
}
let droppedArray= arr.slice(number);
return droppedArray;
},

This also works or i think it does because i didn’t get any errors. Can someone explain what the difference is? Presumably, my code checks if there is a number that has been submitted. If not true, it assigns 1 to number. then, it does the rest. when is it better to use the NOT operator and the Undefined. Is it just preference? if my code is wrong, but correct on the terminal, why is it still wrong to use it? Clarification is very much appreciated.

if (number === undefined)

vs.

if (! number)

To start with, the former is explicit where the latter is coercive, and therefore implicit.

The explicit form will only yield true in the case of an identity relation. All others will yield false.

The coercive form casts a boolean based upon the truthiness of the argument. Any non-truthy, or falsy argument will be coerced to a boolean false, which the not will negate (as in, toggle). That means we could supply, undefined, '', "", 0, NaN, or null. false is literal and needs no coercion, but it too will still be a valid argument.

How strict do you want your code to be? Assuming one uses the looser approach, are there any edge cases that might yield false positives?

In some cases, yes. I prefer it to verbosity, the above criteria (among others) notwithstanding. There is no one broad stroke. Each case needs to be considered carefully, and if we can lighten the code load in certain cases, then by all means do it.

Bottom line, explicitness is a better guarantor of stricter code. When we need to loosen requirements, implicitness is one way of still keeping an eye on things but letting things come as they may in terms of data types. There is no one rule.

That is what i was looking for. Thank you very much. So in the case of the drop() method, we wanted to see whether a number has been literally defined and not just things like ( “”, 0, NaN, null) that can potentially be included to stand for number. The key words here are explicit and implicit. Makes so much sense now. Thanks again.

1 Like

There is a definitiveness to the drop method in that the number parameter has to be a valid index since everything to the left is being truncated. An invalid index will not give us a result we would normally expect.

In this case we would definitely not be using loose relational analysis since, a) we are looking for strictly an integer; and, b) zero is a valid index and NOT would foul that.

Not even comparing to undefined solves this criterion. It must be a number that we can use in the slice() method. Note that negative indices are allowed. str.slice(-1) will be the last character in the string.

drop() doesn’t care what the contents of the array are, whether homogenous or fixed type. It does depend on a valid index, though. number in this case must be a Number.

Consider a perfect world where data types are never an issue…

drop (array, n=1) {
  return array.slice(n)
}

Where no n is supplied that is no problem. What if n is a bogus value that slice() doesn’t recognize?

Do we build in try..catch or do we validate logically? Do we expect that our program will validate before the call to this method? An in_range method would be in order for that validation.

Given that we’re using the slice() method, our validation needs to take negative indices into account.

    drop = (arr, number) => {
        n = arr.length
        fail = isNaN(number)  // false if it looks like a number
        pass = fail || +number >= -n && +number < n
        // unary `+` coerces `Number` from `string` representation
        if (pass) {
            return arr.slice(+number)
        } else {
            return arr    // no change
        }
    }

This is not rock hardened code, but it does lead the way…


One more consideration onto the pile…

return arr.slice(+number)

By this point we know we have a number or it wouldn’t have passed. That could be any number, though, and may have a decimal fraction which is useless to our cause. We can only use integers for indices.

To the rescue we bring in the cleanup crew to perfect this value for final use (replaces if structure).

return pass ? arr.slice(parseInt(number, 10)) : arr

In the above we don’t need to coerce Number since parseInt does that for us. We’ve been threading a needle this whole way.

Here is a deeper exploration into logical validation within the method…

  drop (arr, x=1){
    let n = Array.isArray(arr) && arr.length
    let fail = isNaN(x) || isNaN(n)
    let pass = fail || +x >= -n && +x < n
    if (pass) {
      return arr.slice(parseInt(x, 10))
    } else {
      return arr
    }
  },
Passed!

The logic is not bullet proof, in that it hasn’t been extensively tested, but it gives correct results (either return a truncated array, or the original input). If you do get to testing this, please keep us apprised of any annomalies.

very much appreciated for following up. I understood half of this, probably because i haven’t used the “+number” before or the parse Int() before. I didn’t quite follow but i did get the idea about wanting a number for the number variable. many thanks.

1 Like

NaN is a special float value, isn’t about types

And besides, what is there to check if there’s already a default value for that parameter? That is the reason for doing the check - to implement a default parameter, except, we’ve got that in the language, so that leaves nothing to check for.

Specifying a too-large value to drop/take is usually a valid thing.

haskell:

> drop 5 [1]
[]
> take 5 [1]
[1]

lodash:

> lodash.drop([1], 5)
[]
> lodash.take([1], 5)
[ 1 ]

clojure:

> (drop 5 '(1))
()
> (take 5 '(1))
(1)

python:

> [1][5:]
[]
> [1][:5]
[1]
1 Like

Thank you for putting it in those terms. I was very suspect of all my silly code (think, Silly Putty) though it was a fun exercise, especially being thus illuminated.

I got the colons on the wrong side, that’s what I get for writing it manually.

Definitely easier to get slicing right with drop and take:

drop 3 >>> take 7 $ [0 .. 10]
[3,4,5,6,7,8,9]
1 Like

The point was made, nonetheless. Have that to wrap my brain around.

I have an additional question about the .drop() method.

In the video you are using: let droppedArray = array.slice(n, array.length)

The code tests positive when you don’t use array.length as an argument.

What I understand from MDN regarding the slice method is that the first argument (ie. ‘n’) defines up to which index in the array, elements will be skipped. The remainder of the array is copied into a new array object.

Since this is what we want, why add array.length as additional argument?

Can you link to the exercise, please. I was led to believe we were passing in an array, and an index.

https://www.codecademy.com/paths/web-development/tracks/web-dev-js-arrays-loops-objects/modules/pjs-javascript-capstone/projects/lodash
Step 31

I am using the following code:
drop(array, dropNum){
if (dropNum === undefined){
return dropNum = 1;
};
let droppedArray = array.slice(dropNum);
return droppedArray;
},

1 Like

We can write a default parameter rather than run this test.

drop (array, n = 1) {
    return array.slice(n)
}

which is really just a simplification of your code.

As you observed in your earlier remarks, array.length is redundant.

1 Like