JavaScript map & find at the same time: findMap?

心已入冬 提交于 2020-08-22 06:15:09

问题


How would you rewrite this without using a for loop?

const a = [2, 5, 78, 4];
const expensiveFunction = n => 2 * n;

let result;

// Find the first number 
for (let i = 0; i < a.length; i++) {
    const r = expensiveFunction(a[i]);

    if (r > 100) {
        result = r;
        break;
    }
}

console.log(result);

My naive approach:

const result = a.map(expensiveFunction).find(x => x > 100);
console.log(result);

But this runs expensiveFunction on all the elements, which I would like to avoid. In the above case, we should avoid running expensiveFunction(4).

Some languages have find_map (e.g, Rust), I didn't find it in lodash nor in underscore.


回答1:


Built-in map is greedy so you have to write your own, lazy version:

const a = [2, 5, 78, 4];
const expensiveFunction = n => {
     console.log('expensiveFunction for', n); 
     return 2 * n 
};


function *map(a, fn) {
    for(let x of a)
        yield fn(x);
}

function find(a, fn) {
    for(let x of a)
        if (fn(x))
            return x;
}



r = find(map(a, expensiveFunction), x => x > 100)
console.log('result', r)

Unlike the stock map, this map is a generator and returns (yields) results on demand rather than processing the whole array at once. find and map in this example are "coroutines" and play some kind of a ping-pong game where find asks for results and map delivers them when asked. As soon as find is satisfied with what it's got, it quits and so does map, because nobody is asking for its results anymore.

You can also add map, find and friends to the IteratorPrototype to make them available for all iterators and be able to use dot notation:

const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));

Object.defineProperties(IteratorPrototype, {
    map: {
        value: function* (fn) {
            for (let x of this) {
                yield fn(x);
            }
        },
        enumerable: false
    },

    find: {
        value: function (fn) {
            for (let x of this) {
                if (fn(x))
                    return x;
            }
        },
        enumerable: false
    },

});

//

const a = [2, 5, 78, 4];
const expensiveFunction = n => {
    console.log('expensiveFunction', n);
    return 2 * n
};


let r = a.values().map(expensiveFunction).find(x => x > 100);

console.log(r)

Here's a small library based on this technique: https://github.com/gebrkn/armita




回答2:


Something like this

const a = [2, 5, 78, 4];
const expensiveFunction = n => 2 * n;
let findMap = arr => {
  let found = arr.find(a => expensiveFunction(a) > 100)
  return found !== undefined ? expensiveFunction(found) : found
}

console.log(findMap(a));

Alert:- JUST out of curiosity , But hacky or you can call it misuse of find

const a = [2, 5, 78, 4];
const expensiveFunction = n => 2 * n;
let findMap = arr => {
  let returnValue;
  let found = arr.find(a => {
    returnValue = expensiveFunction(a)
    return returnValue > 100
  })
  return returnValue
}

console.log(findMap(a));



回答3:


If you are willing to accept that the first matching element in your array is modified, you can do this:

a[a.findIndex((value, index) => {
    value = expensiveFunction(value); 
    return (value > 100 && (a[index] = value))
})] //Returns 156

Otherwise, you will need to use a placeholder variable to make this work - quite possibly making a for-loop the cleanest option.




回答4:


You can use .reduce, the only down side is that you can't stop once a value is found but you won't have to run expensiveFunction for each value.

Here is an example:

const a = [2, 5, 78, 4];
const expensiveFunction = n => 2 * n;

const result = a.reduce((acc, cur) => {
  if (!acc) {
    const r = expensiveFunction(cur);
    if (r > 100) {
      acc = r;
    }
  }
  return acc;
}, null);



console.log(result);



回答5:


Why not use a smarter function for find?

let desiredValue;
const result = a.find( x =>{
      desiredValue = expensiveFunction(x);
      return desiredValue > 100;
});
log( desiredValue );

It will quit the expensive loop immediately after finding out the first result.




回答6:


You can go through a shortest way by using ternary operator to simplify the condition and filter() to remove Boolean(null) values of an Array.

const a = [2, 5, 78, 100];
const result  = a.map((n)=>  2*n > 100 ? 2*n : null ).filter(Boolean)[0];
console.log(result);



回答7:


The approach I followed is to decrease the possibility of calling the 'expensiveFunction' function to the least possible number of times. For this purpose I used the 'Divide and conquer algorithms'. You divide the array into half parts and call the expensive function on the dividing element to decide which half to proceed. Do this step recursively until you find the smallest element above 100. Particularly for a very large sized array this method will reduce the expensive function call to a significantly smaller number. So the 'expensiveFunCaller' function will call your 'expensiveFunction' economically. The array should also be sorted first.

const a = [2, 5,78, 80].sort((a,b) => a-b);
const expensiveFunction = n => 2 * n;

const expensiveFunCaller= ([...arr]) =>{
  if(arr.length<2){
    let r = expensiveFunction(arr[0]);
    if(r>100) return r;
    return;
  }
  else if(arr.length === 2){
    let r = expensiveFunction(arr[0]);
    if(r>100) return r;
    r = expensiveFunction(arr[1]);
    if(r>100) return r;
    return;
  }
  let idx = Math.floor(arr.length / 2);
  let r = expensiveFunction(arr[idx]);

  return (r<100)?expensiveFunCaller(arr.slice(idx+1, arr.length)):expensiveFunCaller(arr.slice(0, idx+1));
}
console.log(expensiveFunCaller(a));


来源:https://stackoverflow.com/questions/57760111/javascript-map-find-at-the-same-time-findmap

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!