给定数组提取对象值的功能方法

Functional way to extract object values given an array

我有一个键值对对象数组,如下所示:

var d = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

和一个数组

var a = ['a','e']

如何编写提取值的函数,本质上我想要的结果是

[{'name':'a', 'value':1},{'name':'e', 'value':5} ]

我想以纯函数的方式编写它,而不是使用 forwhile 循环或 filter 函数。请问有没有合适有效的方法呢?递归是正确的方法吗?我愿意使用 lodashramda 或任何其他 FP 包。

var d = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}];
var a = ['a','e'];

d.filter(function(item){
  return a.indexOf(item.name) > -1; 
})

// will return [{'name':'a', 'value':1}, {'name':'b', 'value':5}]

希望这对您有所帮助

如果你希望你的解决方案是递归的,你会想要这样的东西。

function getValues(objects, names, result) {
  if (objects.length === 0 || names.length === 0) {
    return result;
  }
  var index = names.indexOf(objects[0].name)
  if (index > -1) {
    result.push(objects[0]);
    return getValues(objects.slice(1), [...names.slice(0, index), ...names.slice(index, names.length)], result);

  } else {
    return getValues(objects.slice(1), names, result);
  }
}

var d = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

var a = ['a','e']

console.log(getValues(d, a, []));

我认为 OP 正在寻找这样的东西:

const _ = require('lodash');

function solve(array, keys) {
    if (_.isEmpty(array) || _.isEmpty(keys)) {
        return [];
    } else {
        const [ele, ...tailArray] = array;
        const [key, ...tailKeys] = keys;

        if (ele.name === key) {
            return [ele, ...solve(tailArray, tailKeys)];
        } else {
            return solve(tailArray, keys);
        }
    }
}

const d = [{'name': 'a', 'value': 1}, {'name': 'b', 'value': 2}, {'name': 'c', 'value': 3}, {'name': 'd', 'value': 4}, {'name': 'e', 'value': 5}];
const a = ['a', 'e'];

console.log(solve(d, a));

这假设两个数组都已排序,所有名称都是唯一的,并且 array a 中的所有元素也存在于 array d 中。其他解决方案留作@cherhan 的练习。

Array.prototype.map 和 Array.prototype.find

看来您对实现细节很固执,但我敢肯定您不知道为什么。 forwhilefilter 都没有错。到这篇post结束时,我不希望你相信我,而是希望你知道是真的。

您可以使用 mapfind 轻松解决此问题 – 对于所有意图和目的,如何 mapfind 实现对你来说是个谜。他们使用 for 循环甚至 GOTO 来大声喊叫都没有关系。对您来说重要的是您可以用实用的方式表达您的问题。

mapfind 是谦逊的 JavaScript 函数式程序员的完美伴侣...

var d = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

var a = ['a','e']

var result = a.map(x => d.find(y => y.name === x))

console.log(result)
// [ { name: 'a', value: 1 },
//   { name: 'e', value: 5 } ]


泛型函数促进代码重用

使用一些通用函数,我们可以用更可重用的方式表达解决方案

var data = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

const findBy = k => xs => v =>
  xs.find(x => x[k] === v)

const findAllBy = k => xs => vs =>
  vs.map(findBy(k)(xs))

console.log(findAllBy ('name') (data) (['a', 'e']))
// [ { name: 'a', value: 1 },
//   { name: 'e', value: 5 } ]


为什么固执无济于事

如果你想看看这是如何不依赖any for, while, filter, map,或find...

var data = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

const main = ([x,...xs]) => {
  const aux = ([y,...ys]) => {
    if (y === undefined)
      return null
    else if (y.name === x)
      return y
    else
      return aux (ys)
  }
  if (x === undefined)
    return []
  else
    return [aux(data), ...main(xs)]
}

console.log(main(['a', 'e']))
// [ { name: 'a', value: 1 },
//   { name: 'e', value: 5 } ]

很痛苦吧?与这里的其他答案相比,该代码的进展要少一些,这些答案仍然具有 lengthindexOfslice 或整个 lodash 库等依赖项。

所以这样做真的没有意义。此示例中编码了大量有用的通用函数,我们希望在其他地方使用它们。这就是为什么像 mapreducefilterfind 这样的函数首先存在的原因——forwhile 也是如此。

每次我想遍历一个数组,或者过滤一个数组,或者在数组中查找一个元素时,我都不想手动完成。我使用函数的原因是我不必重复自己......永远。


好奇心

所以也许您对上面的 map+find 解决方案没有问题,但您也很好奇如果您没有它们,您将如何自己实施它们您已经完成了处置——下面只是 无数 种方法中的两种,您可以通过这些方法实现这些功能中的任何一个。

var data = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

const find = f => ([x,...xs]) =>
  x === undefined ? null :
    f(x) === true ? x : find (f) (xs)

const map = f => ([x,...xs]) =>
  x === undefined ? [] : [f(x), ...map(f)(xs)]
  
const main =
  map (x => find (y => y.name === x) (data))
  
console.log(main(['a', 'e']))
// [ { name: 'a', value: 1 },
//   { name: 'e', value: 5 } ]


递归陷入困境 JavaScript

很多人不了解 JavaScript 中的递归是它实际上不是很好。我们被承诺在 ES6 中消除尾调用,但目前 JavaScript 的实现还没有真正支持它。这意味着您编写的任何递归函数(除非您对其进行蹦床)都存在堆栈溢出的潜在风险。

这意味着使用 forwhile 实现功能接口比 所以 好得多。

下面,我们重新实现 mapfind 但这次我们通过避免堆栈溢出的风险来巧妙地实现它。请注意,它实际上对生成的代码没有影响。我们仍然可以以一种很好的、​​实用的方式与 mapfind 交互——它们都是引用透明的

请特别注意 main 函数 - 它与上面的示例完全没有变化。我们的 "ugly" whilefor 循环作为实现细节被巧妙地隐藏起来,最终用户 none 更明智。

var data = [{'name':'a', 'value':1}, {'name':'b', 'value':2}, {'name':'c', 'value':3}, {'name':'d', 'value':4}, {'name':'e', 'value':5}, {'name':'f', 'value':6}]

const find = f => xs => {
  for (let x of xs)
    if (f(x) === true)
      return x
  return null
}

const map = f => xs => {
  const acc = Array(xs.length)
  for (let [i,x] of xs.entries())
    acc[i] = f(x)
  return acc
}

const main =
  map (x => find (y => y.name === x) (data))
  
console.log(main(['a', 'e']))
// [ { name: 'a', value: 1 },
//   { name: 'e', value: 5 } ]

不幸的是我不得不使用 R.map,但是 Ramda:

R.flatten(R.map(R.useWith(R.filter, [R.propEq('name'), R.identity])(R.__, d), a))

这是我能想到的,但我知道有更好的解决方案,我很想听听


编辑:我觉得这个更好

R.filter(R.compose(R.flip(R.contains)(a), R.prop('name')), d)