【问题标题】:Recursively filter an array of infinitely nested objects by mutliple matching conditions but only return parent that has an instance of both matches通过多个匹配条件递归过滤无限嵌套的对象数组,但仅返回具有两个匹配项的实例的父对象
【发布时间】:2022-01-13 16:33:52
【问题描述】:

我有以下对象数组;然而,这可能是任何未知的键/值并且可以无限嵌套,现在这是一个测试示例:

[
  {
    "reference_id": "R123",
    "customer": "Person 1",
    "customer_email": "person1@email.com",
    "location": "UK",
    "bookings": [
      {
        "product": "Product 1",
        "provider": "Company 1",
        "cancellable": true
      },
      {
        "product": "Product 2",
        "provider": "Company 2",
        "cancellable": true
      },
      {
        "product": "Product 3",
        "provider": "Company 1",
        "cancellable": true
      }
    ]
  },
  {
    "reference_id": "R1234",
    "customer": "Person 2",
    "customer_email": "person2@email.com",
    "location": "USA",
    "bookings": [
      {
        "product": "Product 1",
        "provider": "Company 1",
        "cancellable": true
      },
      {
        "product": "Product 3",
        "provider": "Company 1",
        "cancellable": true
      }
    ]
  },
  {
    "reference_id": "R12345",
    "customer": "Person 3",
    "customer_email": "person3@email.com",
    "location": "UK",
    "bookings": [
      {
        "product": "Product 2",
        "provider": "Company 2",
        "cancellable": true
      },
      {
        "product": "Product 3",
        "provider": "Company 1",
        "cancellable": true
      }
    ]
  }
]

我目前的实现如下:

const selected = [
  {
    term: 'Company 1',
    column: 'provider',
  },
  {
    term: 'Person 1',
    column: 'customer',
  },
];

const recursivelyFilterByValue = () => (value) => selected.every((item) => {
  if (!value) return false;

  if (typeof value === 'string') {
    // console.log('value', value === item.term);
    return value === item.term;
  }

  if (Array.isArray(value)) {
    return value.some(this.recursivelyFilterByValue());
  }

  if (typeof value === 'object') {
    return Object.values(value).some(this.recursivelyFilterByValue());
  }

  return false;
});

const results = data.filter(recursivelyFilterByValue());

基本上,我将添加到“选定”数组中,然后使用它来过滤数据数组。我确实想确保键与“列”匹配,但我还没有添加。

对于上面的输入,我希望输出以下内容:

[
  {
    "reference_id": "R123",
    "customer": "Person 1",
    "customer_email": "person1@email.com",
    "location": "UK",
    "bookings": [
      {
        "product": "Product 1",
        "provider": "Company 1",
        "cancellable": true
      },
      {
        "product": "Product 2",
        "provider": "Company 2",
        "cancellable": true
      },
      {
        "product": "Product 3",
        "provider": "Company 1",
        "cancellable": true
      }
    ]
  },
]

但是输出数组是空的。如果我只搜索一个术语(从所选数组中删除除一个术语之外的所有术语),则该术语的输出是正确的,但是任何后续术语都会返回一个空白数组。

我想知道我对 .some() 的使用是否是问题,但是更改它会导致太多递归错误。

本质上,只要在所选数组中的所有条件都存在键:值匹配项,无论其子对象的任何级别,我都想返回原始父对象。

任何指导将不胜感激,谢谢。

【问题讨论】:

  • 我有这个权利吗?您有一个(可能是动态的)条件,该条件表示一个对象要么具有 provider 属性,值为 "Customer 1",要么具有(递归)后代对象。并且您有关于customer"Person 1" 的第二个条件,并且您正在寻找满足这两个(或所有)这些条件的对象。这是否描述了您正在尝试做的事情?
  • @ScottSauyet 没错。本质上,我想返回每个条件至少有一个匹配项的原始/根父对象(或其后代具有)。父项必须与数组中的每个条件都匹配,然后才能返回,因此我们需要搜索所有后代以找到每个条件的至少一个匹配项。条件将始终在数据中至少有一个匹配项,因为条件是根据剩余数据的匹配顺序添加的。
  • 如果您知道只有一个匹配项 - 或者即使您知道最多有一个匹配项 - 那么您可以将我的答案中的 filter 替换为 find

标签: javascript arrays recursion multidimensional-array filter


【解决方案1】:

我不太确定这是否是您要查找的内容。它假设我在 cmets 中的猜测是正确的:

我有这个权利吗?您有一个(可能是动态的)条件,它表示一个对象要么有一个 provider 值为 "Customer 1" 的属性,要么有一个(递归的)后代对象。您还有关于customer"Person 1" 的第二个条件,并且您正在寻找同时满足这两个(或所有)这些条件的对象。这是否描述了您正在尝试做的事情?

这里我们有两个相当简单的辅助函数testRecursivemakePredicates 以及主函数recursivelyFilterByValue

const testRecursive = (pred) => (obj) => 
  pred (obj) || Object (obj) === obj && Object .values (obj) .some (testRecursive (pred))

const makePredicates = (criteria) => 
  criteria .map (({term, column}) => (v) => v [column] == term)

const recursivelyFilterByValue = (criteria, preds = makePredicates (criteria)) => (xs) =>
  xs .filter (obj => preds .every (pred => testRecursive (pred) (obj)))


const selected = [{term: 'Company 1', column: 'provider'}, {term: 'Person 1', column: 'customer'}]

const input = [{reference_id: "R123", customer: "Person 1", customer_email: "person1@email.com", location: "UK", bookings: [{product: "Product 1", provider: "Company 1", cancellable: true}, {product: "Product 2", provider: "Company 2", cancellable: true}, {product: "Product 3", provider: "Company 1", cancellable: true}]}, {reference_id: "R1234", customer: "Person 2", customer_email: "person2@email.com", location: "USA", bookings: [{product: "Product 1", provider: "Company 1", cancellable: true}, {product: "Product 3", provider: "Company 1", cancellable: true}]}, {reference_id: "R12345", customer: "Person 3", customer_email: "person3@email.com", location: "UK", bookings: [{product: "Product 2", provider: "Company 2", cancellable: true}, {product: "Product 3", provider: "Company 1", cancellable: true}]}]

console .log (recursivelyFilterByValue (selected) (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
  • testRecursive 检查谓词对于对象或嵌套在其中的任何对象是否为真。

  • makePredicates{term, column}-objects 数组转换为谓词函数,用于测试对象在列命名的属性中是否具有适当的术语。

  • recursivelyFilterByValue 将这些结合起来,调用makePredicates 将所选项目转换为谓词函数,然后通过测试每个谓词是否为真来过滤输入。

这不是可以想象的最有效的代码。它重新扫描每个谓词的层次结构。我相信我们可以找出一个版本来只进行一次扫描,但我认为它会产生更复杂的代码。因此,您可能希望在生产规模的数据中测试它是否足够快以满足您的需求。

【讨论】:

  • 问题需要更多的规范,但我很确定你把它压碎了?
  • 这绝对是完美的。像我希望的那样工作,我在某种程度上走在正确的道路上。我知道效率并不完美,但是更大的数据集最终将由 API 处理,因此无需为这个较小的用例过度复杂化。感谢您的帮助和详细的回答(我也被提醒要更加熟悉柯里化函数)。
【解决方案2】:

这个解决方案没有公认的答案那么优雅,但为什么不展示我的努力。也许有人会觉得这种方式更容易理解。

const selected = [{term: 'Company 1', column: 'provider'}, {term: 'Person 1', column: 'customer'}]
const input = [{reference_id: "R123", customer: "Person 1", customer_email: "person1@email.com", location: "UK", bookings: [{product: "Product 1", provider: "Company 1", cancellable: true}, {product: "Product 2", provider: "Company 2", cancellable: true}, {product: "Product 3", provider: "Company 1", cancellable: true}]}, {reference_id: "R1234", customer: "Person 2", customer_email: "person2@email.com", location: "USA", bookings: [{product: "Product 1", provider: "Company 1", cancellable: true}, {product: "Product 3", provider: "Company 1", cancellable: true}]}, {reference_id: "R12345", customer: "Person 3", customer_email: "person3@email.com", location: "UK", bookings: [{product: "Product 2", provider: "Company 2", cancellable: true}, {product: "Product 3", provider: "Company 1", cancellable: true}]}]


const iter = (obj, sel) => 
  Object.entries(obj).some(([key, value]) => {
    if (Array.isArray(value))
      return value.some((obj) => iter(obj, sel));

    if (typeof value === 'object' && value !== null)
      return iter(value, sel);
    
    return (key === sel.column) && (value === sel.term);
  });

const deepFilter = (arr, sels) => 
  arr.filter((obj) => 
    sels.every((sel) => iter(obj, sel)));

console.dir(deepFilter(input, selected), {depth: null});
.as-console-wrapper {max-height: 100% !important; top: 0}

【讨论】:

  • 在某些方面,我比我的版本更喜欢这个。两个问题:首先,你为什么选择只下降到数组中?用对象测试替换Array.isArray 会更灵活。二、为什么要在main函数里面重命名termcolumn?另外,我认为一些轻微的签名更改可能会simplify this 相当多。
  • @ScottSauyet,非常感谢您的全新外观。 1. 我只使用下降到数组,因为那是问题中模型的结构。但是你说得对,我们也应该考虑嵌套对象,我更新了代码。 2.我的错,提取iter后重构不好。
猜你喜欢
  • 2022-01-26
  • 1970-01-01
  • 2021-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多