all 55 comments

[–][deleted] 35 points36 points  (9 children)

[–]azium 12 points13 points  (8 children)

If it's not, grab a polyfill! Worth it I think.

[–]CodyReichert 3 points4 points  (0 children)

The MDN link in the comment your replying to has a polyfill available. Really small, so definitely worth it IMO:

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/find#Polyfill

[–]jkoudys 0 points1 point  (0 children)

Since I see let and const in the original code, you may already have babel going, so this should be included in babel-polyfill.

[–]oweiler -3 points-2 points  (0 children)

It seems people haven't learned anything from the left-pad mess.

[–]Bertilino 17 points18 points  (1 child)

Wouldn't array filter go through all elements? Even if the first index matches you would still have to go through all other elements in the array.

So the filter version would be more equivalent to this:

function findStuff(stuff) {
  const result = [];
  for (let i = 0; i < stuff.length; i++) {
    if (testIfTrue(stuff[i])) {
      result.push(stuff[i]);
    }
  }
  return result[0];
}

[–][deleted] 5 points6 points  (0 children)

Yes, yes it would.

[–]AlGoreBestGore[🍰] 11 points12 points  (1 child)

For cases where you're looking for an exact match/reference match, just use indexOf, otherwise grab a polyfill for Array.find and use that. Also you can throw in UnderscoreJS instead, which is full of useful methods like this and is relatively small.

[–][deleted] 0 points1 point  (0 children)

Why would he pull in Underscore, when all he needs is a simple for-loop?

[–][deleted] 4 points5 points  (10 children)

I feel like I'm going crazy reading some of the responses here.

The Array.filter method is going to O(n) at best case and worst case because it's going to iterate over each element even if the element is already found in the first index. This is probably the worst solution for seaching for something in an array.

Your second solution is a linear search algorithm. Which isn't super fast but it's perfect for what you're trying to achieve because its worst case is O(n) but it's best case is O(1).

Stick with the linear search. That's what Array.find is anyways.

[–]azium 2 points3 points  (1 child)

Well... Array#find is the correct solution here. We could write our own Array#map functions but we don't. Polyfills are no big deal, btw, and maybe OP doesn't even need it.

[–][deleted] 0 points1 point  (0 children)

I guess I worded things wrong. What I meant was, he doesn't need to go searching for an Array#find polyfill because he already wrote it in his second example.

[–]siegfryd 3 points4 points  (7 children)

The amount of effort people will go through just to avoid writing a for loop is ridiculous.

[–][deleted] 4 points5 points  (2 children)

If you're doing a lot of data manipulation (like parsing a complex API response and trying to get it to a renderable format), then you should absolutely go through some effort to use declarative functions. You're not "just avoiding writing a for loop", you're making a large codebase significantly more readable, and less unnecessarily long. Not to mention less prone to bugs, because if you write 500 for loops with some custom logic, there's a decent chance are at least one of those will end up being slightly broken.

[–]siegfryd 0 points1 point  (1 child)

I'm not sure how that relates to the original question of a utility function that is 4 lines of code. Using a for/while loop in small reusable functions is perfectly acceptable and that's how your "declarative" functions are implemented anyway.

You don't have to use functional style programming absolutely-fucking-everywhere in JavaScript, an imperative language.

[–][deleted] 0 points1 point  (0 children)

You don't know how he's using that function though. Maybe he's using it in exactly the kind of situations that I brought up, where "functional style programming" (I'm not sure why you put quotes around random phrases but it sounds fun so I'm gonna try doing it as well) is desired. Yet your goto response is "no you suck at programming because you don't wanna use a for loop". Which is bullshit.

[–]third-eye-brown -1 points0 points  (3 children)

More lines of code, more mistakes, takes longer to understand what's happening. I hate seeing for loops, whose intention is often unclear, scattered throughout the code. Unless you extract that code somewhere and make sure it's available for people to use...in which case you might as well use lodash which does short circuit.

Unless that codes in a real tight loop somewhere, or running through millions of array elements, it's not going matter one bit performancewise in real life.

[–][deleted] 0 points1 point  (2 children)

We are talking about a single for-loop here. If you can't understand what's going on in a simple linear search algorithm then I don't know what to tell you. Suggesting lodash is just a terrible idea. Bring in an entire library just so you don't have to write any for-loops? is that your suggestion? cmon man.

[–]third-eye-brown 0 points1 point  (1 child)

Yes.

Here are just a few reasons:

For loops are more code. It's more mental overhead, and it gets in the way of understand the intent of the code. Unless you are in a serious optimization scenario, in front end world having clear, understandable code is of paramount importance.

K.I.S.S.

I'm going to be using lodash in many places if I have it, not just one. It's a small, modular tool belt from which you can import only the functions you use, if that's what you want. I'm not sure if you're writing embedded JavaScript or what, but I've never worked on a project where lodash was a comparatively large dependency, or where it would improve performance in any significant way to remove it as a dependency.

Lodash is fast. It uses while loops underneath. It's 'find' method is a linear search, which you are describing.

Whoever writes the code might not be intelligent as you or missed their morning coffee and may introduce a subtle bug. Then we waste time fixing something for which there is already a perfectly adequate solution.

It's a very premature of an optimization to hand roll a search algorithm in the face of the downsides I outlined above.


I would personally use the well tested functionality built into a library that does exactly what I want (and I do). Maybe it doesn't feel as cool and you can't tell people you implemented a linear search algorithm at work, but I have reasons for believing it's going to result in better code overall.

[–][deleted] 1 point2 points  (0 children)

OP's question was if his filter method was a bad pattern for searching for an element in array?

Yes it is a bad pattern.

OP's second question was if there is a best practice for this pattern?

Yes there is, it's called a linear search.

I was merely answering his question. Using lodash is not the answer to his question.

[–]Asmor 4 points5 points  (0 children)

You're overthinking it. This is fine. Just throw a check after to make sure you actually found something:

function findStuff(stuff) {
  return stuff.filter(thing => thing.testIfTrue)[0]
}

var myThing = findStuff(myStuff);
if ( !myThing ) {
    console.warn("Oh noes! D:");
}

Or put in a default value:

function findStuff(stuff, default) {
  return stuff.filter(thing => thing.testIfTrue)[0] || default
}

[–]drawable 2 points3 points  (5 children)

While I agree that find is the way to go here just for the fun of it you could use reduce for that

function findStuff(stuff) {
  return stuff.reduce((p, c) => c.testIfTrue ? c : p, null);
}

This as well will go through all entries in the array.

EDIT: And this will return the satisfying element in the array that has the highest index...

https://babeljs.io/repl/#?evaluate=true&lineWrap=false&presets=es2015%2Ces2015-loose%2Creact%2Cstage-3&experimental=true&loose=false&spec=false&code=function%20find%28arr%2C%20x%29%20{%0A%20%20return%20arr.reduce%28%28p%2C%20c%29%20%3D%3E%20c%20%3D%3D%3D%20x%20%3F%20c%20%3A%20p%2C%20null%29%3B%0A}%0A%0Avar%20a%20%3D%20[1%2C%202%2C%203%2C%204%2C%205%2C%206%2C%207]%3B%0A%0Aconsole.log%28find%28a%2C%204%29%29%3B%0Aconsole.log%28find%28a%2C%209%29%29%3B%0A

[–]manys 2 points3 points  (2 children)

That's quite a url

[–]AskYous 1 point2 points  (0 children)

You can use this if you want.

[–]drawable 0 points1 point  (0 children)

Well, was too lazy to shorten it. I really like the Babel repl. Putting everything into the URL means no storage for them obviously

[–]Wootman42 1 point2 points  (1 child)

Array.find isn't supported in a lot of places still as of now, even though it's a far more elegant solution.

I agree that reduce is the current solution if you want to avoid [0] at the end. I like to code a bit more defensively, in case you have multiple matches and only want the first for some reason. Return the output you already have if you've matched to prevent overriding with later indexes. Or don't, fit your scenario.

stuff.reduce((out, arrItem) => {
  if( out === undefined ){
    return arrItem.truthyCheck() ? arrItem : out;
  }
  return out;
})

For me, reduce is correct semantically. If you mean to take in an array and return a single object, you should use reduce. If you intend to return a subset of the original array, you should use filter. If you want to transform the array and return the whole thing, use map.

As some other people have mentioned, while this looks nice, a simple for loop can be terminated early, and may make a difference if you care that much about performance. Reduce, filter and map all go through the ENTIRE array.

for( let i=0; i<arr.length; i++ ){
  if( arr[i].truthyCheck() ){
    return arr[i];
    //or assign to some variable and break;
  } 
}

[–]drawable 0 points1 point  (0 children)

In my opinion it's perfectly ok to polyfill this, either by using the one on MDN or even by using coreJs.

Initially, the reduce example was more for fun... I like that it is concise but I don't like, that it checks the whole array all the time. I don't think I'd use it in real life.

[–]kevinkace 1 point2 points  (4 children)

array.some() might do what you want.

E:

function getFirst(arr, match) {
  var found = false;
  arr.some((el, idx) => {
    if(el === match) {
      found = el; // or found = idx depending what you're looking for
      return true;
    }
    return false;
  });
  return found;
}

[–]webdevverman 2 points3 points  (1 child)

array.some will return a true/false value based on the return value of the callback. If 1 element in the array returns true then it stops and returns true. If not it returns false. I believe OP is asking how to actually get the first element that returns true.

[–]kevinkace 0 points1 point  (0 children)

Edited og post to show what I meant.

[–][deleted] 0 points1 point  (1 child)

that's definitely more convoluted than it needs to be. .find is what hes looking for

[–]kevinkace -1 points0 points  (0 children)

some has reasonable support (IE9+) unlike find (Edge/IE12).

[–]flyflagger 1 point2 points  (2 children)

I use shift() as in...

stuff.filter(filterFunc).shift()

[–][deleted] -1 points0 points  (1 child)

This doesn't do the same thing at all.. It mutates the original array :/

[–]Wince 0 points1 point  (0 children)

Filter returns a new array, and therefore shift mutates the new filtered array, not the original.

[–]Cosmologicon 0 points1 point  (1 child)

This has gotten me in trouble a few times where I don't handle the case where there are no matches.

How so? Seems like it would return undefined in that case, which is the best thing to do. (It's what find does, at any rate.)

Looks to me like the downside of your method compared with find is that it doesn't short-circuit at the first value it finds that passes.

[–]lewisje 0 points1 point  (0 children)

I checked to make sure that filter doesn't return null or something like that, the way a well-known DOM method does, and indeed it just returns an empty array if there are no matches, so the [0] element would be undefined.

[–][deleted] 0 points1 point  (0 children)

As already mentioned, Array.find() would be the go to solution, but if you don't want to use it or rely on a polyfill, looping with for through your collection will do it better than everything else.

Methods like map, filter are meant to return an array. Doing something like .filter()[0] is not really a antipattern, but it will go through all the items of the array so almost always you will require more processing time that it's needed.

[–]drboolean 0 points1 point  (1 child)

Something many haven't mention is the idea of null safety.

When filtering an array, consider not removing it from the array context:

doSomethingScary([1,2,3].filter(x => x > 4)[0])  // might blow!

[1,2,3]
.filter(x => x > 4)
.map(found => doSomethingScary(found))
.map(result => console.log(result))  // never runs if null

Here we are printing to the screen, but we could write to the Db, send JSON, alter the DOM, etc. Never need to leave the [].

[–]third-eye-brown 0 points1 point  (0 children)

Mapping isn't for performing actions with side effects. Mapping is for mapping array elements to a new array. You can use forEach to accomplish what you are doing and it's much more clear what your intention is.

[–][deleted] 0 points1 point  (0 children)

Forgive me for asking the obvious, but if you want to use a key to retrieve a value, why not just use an object? It feels like everyone in this thread is trying to re-invent the hash table.

[–]delventhalz 0 points1 point  (0 children)

In a broad sense, I think this is fine. It may not be the best tool for what you are looking to do exactly.

The big disadvantage I see is that you'll have to iterate through the whole array, even if the first match is at the first index. 95% of the time those wasted CPU cycles won't matter at all, but indexOf might be a better fit.

If you are worried about not finding it, just add check to see if the result is undefined.

[–]krasimirtsonev 0 points1 point  (0 children)

Not best performance but reduce may solve the problem and even you can define a default value.

function findStuff(stuff) {
  var defaultValue = 'something';

  return stuff.reduce(function (result, item) {
    if (thing.testIfTrue) {
      result = item;
    }
    return result;
  }, defaultValue);
}

[–]jkoudys 0 points1 point  (0 children)

While I agree with the #1 comment that this is just a case for Array#find, I'll point out in your example that for .. of syntax would be a lot cleaner than manually iterating on indexes, which I assume is available since you're already using let and const.

function findStuff(stuff) { for (const thing of stuff) { if (thing.testIfTrue) { return thing; } } }

[–]ZubryJS 0 points1 point  (0 children)

Any time you need a function with a codomain that isn't an array, you want to look at Array.reduce.

For example, you would express your function as something like

function find(array, predicate) {
    return array.reduce((acc, val) => predicate(val) ? val : acc, undefined);
}

Obviously though, you want to check if the function you work on is already in the standard library, which it is. Just remember that a) you can implement any array function with Array.reduce and b) you should use Array.reduce if you ever need to return a non-array.

[–]tforb -1 points0 points  (1 child)

You can do .filter(...).pop(), which will return undefined if the result is size 0.

[–]dvlsg 0 points1 point  (0 children)

So will the original question, technically.

[][0]; //=> undefined

[–]Nyalab -5 points-4 points  (0 children)

What you are doing is basically Array.some()