如何使用 ramda 过滤嵌套数组中的记录?

How to filter records in a nested array using ramda?

我在好几个地方看到过这个问题,但还是想不通。 使用 ramda,如何将以下对象过滤为 return tomatoestrue 记录?

[
    {
        "id": "a",
        "name": "fred",
        "food_prefs": {
            "tomatoes": true,
            "spinach": true,
            "pasta": false
        },
        "country": "singapore"
    },
    {
        "id": "b",
        "name": "alexandra",
        "food_prefs": {
            "tomatoes": false,
            "spinach": true,
            "pasta": true
        },
        "country": "france"
    },
    {
        "id": "c",
        "name": "george",
        "food_prefs": {
            "tomatoes": true,
            "spinach": false,
            "pasta": false
        },
        "country": "argentina"
    }
]

将此数组存储为 myData 对象,我认为以下应该可行:

const R = require("ramda")

const lovesTomatoes = R.pipe ( // taken from: 
    R.path (["food_prefs"]),
    R.filter (R.prop ("tomatoes"))
)

console.log(lovesTomatoes(myData))

但我最终遇到错误:

if (typeof obj[methodNames[idx]] === 'function') {

我做错了什么?


编辑


@Ori Drori 和@ThanosDi 提供的答案都很棒,但我想强调的是,基于 pipe 的解决方案将是理想的,因为我有后续步骤我希望进行过滤阵列。例如考虑以下数组。它与上面的类似,但包含更多数据:year_bornyear_record.

[
    {
        "id": "a",
        "name": "fred",
        "year_born": 1995,
        "year_record": 2010,
        "food_prefs": {
            "tomatoes": true,
            "spinach": true,
            "pasta": false
        },
        "country": "singapore"
    },
    {
        "id": "b",
        "name": "alexandra",
        "year_born": 2002,
        "year_record": 2015,
        "food_prefs": {
            "tomatoes": false,
            "spinach": true,
            "pasta": true
        },
        "country": "france"
    },
    {
        "id": "c",
        "name": "george",
        "year_born": 1980,
        "year_record": 2021,
        "food_prefs": {
            "tomatoes": true,
            "spinach": false,
            "pasta": false
        },
        "country": "argentina"
    }
]

所以,例如,要回答一个完整的问题,例如 “对于那些喜欢西红柿的人,记录创建时的平均年龄是多少?”

我们需要:

  1. 过滤喜欢番茄的记录;
  2. 提取元素year_bornyear_record
  3. 获取值之间的差异
  4. 取差异的平均值

因此,使用管道将非常有益。

哪里出了问题?

您尝试从数组中获取 food_prefs 的值。由于数组没有此键 - R.path (["food_prefs"])undefined,然后您尝试过滤此 undefined 值。

如何解决这个问题?

过滤数组,用R.path得到tomatoes

const { filter, path, identity } = R

const lovesTomatoes = filter(path(['food_prefs', 'tomatoes']))

const data = [{"id":"a","name":"fred","food_prefs":{"tomatoes":true,"spinach":true,"pasta":false},"country":"singapore"},{"id":"b","name":"alexandra","food_prefs":{"tomatoes":false,"spinach":true,"pasta":true},"country":"france"},{"id":"c","name":"george","food_prefs":{"tomatoes":true,"spinach":false,"pasta":false},"country":"argentina"}]

const result = lovesTomatoes(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

使用管道过滤:

使用R.pipe。对于嵌套属性的简单过滤器,我不会采用这种方式,但您可以使用 Schwartzian transform。思路是创建一个新数组if pairs [value of tomatoes, original object],通过tomatoes的值过滤,然后提取原始对象:

const { pipe, map, applySpec, path, identity, filter, last, head } = R

const lovesTomatoes = pipe(
  map(applySpec([path(['food_prefs', 'tomatoes']), identity])), // create an array of [value of tomatoes, original object] 
  filter(head), // filter by the value of the tomatoes
  map(last) // extract the original object
)

const data = [{"id":"a","name":"fred","food_prefs":{"tomatoes":true,"spinach":true,"pasta":false},"country":"singapore"},{"id":"b","name":"alexandra","food_prefs":{"tomatoes":false,"spinach":true,"pasta":true},"country":"france"},{"id":"c","name":"george","food_prefs":{"tomatoes":true,"spinach":false,"pasta":false},"country":"argentina"}]

const result = lovesTomatoes(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

如何在管道中组合第一个lovesTomatoes过滤函数:

但是,如果您只是需要管道对过滤后的数组执行其他操作,请使用过滤器作为步骤之一:

const { filter, path, identity, pipe, map, prop, uniq } = R

const lovesTomatoes = filter(path(['food_prefs', 'tomatoes']))

const countriesOfTomatoLovers = pipe(
  lovesTomatoes,
  map(prop('country')),
  uniq
)

const data = [{"id":"a","name":"fred","food_prefs":{"tomatoes":true,"spinach":true,"pasta":false},"country":"singapore"},{"id":"b","name":"alexandra","food_prefs":{"tomatoes":false,"spinach":true,"pasta":true},"country":"france"},{"id":"c","name":"george","food_prefs":{"tomatoes":true,"spinach":false,"pasta":false},"country":"argentina"}]

const result = countriesOfTomatoLovers(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

const myData = [
    {
        "id": "a",
        "name": "fred",
        "food_prefs": {
            "tomatoes": true,
            "spinach": true,
            "pasta": false
        },
        "country": "singapore"
    },
    {
        "id": "b",
        "name": "alexandra",
        "food_prefs": {
            "tomatoes": false,
            "spinach": true,
            "pasta": true
        },
        "country": "france"
    },
    {
        "id": "c",
        "name": "george",
        "food_prefs": {
            "tomatoes": true,
            "spinach": false,
            "pasta": false
        },
        "country": "argentina"
    }
];

const lovesTomatoes = filter(pathOr(false, ['food_prefs','tomatoes']));

lovesTomatoes(myData);

拉姆REPL

Ramda 已经带有一整套谓词built-in, 我在这里使用的其中一个是 pathEq.

我建议采用 mapreduce 类方法,而匹配函数与实际聚合是分开的...

  1. 收集你的数据点
  2. 将其缩减为您需要的信息

const tomatoLovers = R.filter(
  R.pathEq(['food_prefs', 'tomatoes'], true),
);

const avgAge = R.pipe(R.pluck('age'), R.mean);

const data = [{
    "id": "a",
    age: 16,
    "name": "fred",
    "food_prefs": {
      "tomatoes": true,
      "spinach": true,
      "pasta": false
    },
    "country": "singapore"
  },
  {
    "id": "b",
    age: 66,
    "name": "alexandra",
    "food_prefs": {
      "tomatoes": false,
      "spinach": true,
      "pasta": true
    },
    "country": "france"
  },
  {
    "id": "c",
    age: 44,
    "name": "george",
    "food_prefs": {
      "tomatoes": true,
      "spinach": false,
      "pasta": false
    },
    "country": "argentina"
  }
]

console.log(
  'Average age of tomato lovers is:',
  R.pipe(tomatoLovers, avgAge) (data),
);

console.log(
  'They are the tomato lovers',
  R.pipe(tomatoLovers, R.pluck('name')) (data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.js" integrity="sha512-ZZcBsXW4OcbCTfDlXbzGCamH1cANkg6EfZAN2ukOl7s5q8skbB+WndmAqFT8fuMzeuHkceqd5UbIDn7fcqJFgg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>