【问题标题】:Map array of objects together with the parent property using Ramda使用 Ramda 将对象数组与父属性一起映射
【发布时间】:2021-05-08 08:10:36
【问题描述】:

我是函数式编程和 ramda 的新手。我有一个案例,可以很容易地以命令式的方式解决,但我在声明式的方式上遇到了困难。 我有以下结构,它描述了多个库存。

inventories = [
  {
    id: 'Berlin',
    products: [{ sku: '123', amount: 99 }],
  },
  {
    id: 'Paris',
    products: [
      { sku: '456', amount: 3 },
      { sku: '789', amount: 777 },
    ],
  },
]

我想要做的是将其转换为一个平面产品列表,其中包含inventoryIdinventoryIndexproductIndex 等附加信息。

products = [
  { inventoryId: 'Berlin', inventoryIndex: 1, sku: '123', amount: 99, productIndex: 1 },
  { inventoryId: 'Paris', inventoryIndex: 2, sku: '456', amount: 3, productIndex: 1 },
  { inventoryId: 'Paris', inventoryIndex: 2, sku: '789', amount: 777, productIndex: 2 },
]

正如我之前所写,以命令式的方式执行此操作不是问题。

function enrichProductsWithInventoryId(inventories) {
  const products = []
  for (const [inventoryIndex, inventory] of inventories.entries()) {
    for (const [productIndex, product] of inventory.products.entries()) {
      product.inventoryId = inventory.id
      product.inventoryIndex = inventoryIndex + 1
      product.productIndex = productIndex + 1
      products.push(product)
    }
  }

  return products
}

问题是当我尝试用 ramda 解决这个问题时。我不知道如何在映射产品时访问inventoryId。很高兴看到一段使用 ramda 编写的代码,与上面的代码相同。

干杯, 托马斯

【问题讨论】:

    标签: javascript functional-programming ramda.js


    【解决方案1】:

    您可以使用flatMap 轻松完成此操作。

    const inventories = [
        {
            id: "Berlin",
            products: [{ sku: "123", amount: 99 }]
        },
        {
            id: "Paris",
            products: [
                { sku: "456", amount: 3 },
                { sku: "789", amount: 777 }
            ]
        }
    ];
    
    const products = inventories.flatMap((inventory, inventoryIndex) =>
        inventory.products.map((product, productIndex) => ({
            inventoryId: inventory.id,
            inventoryIndex: inventoryIndex + 1,
            sku: product.sku,
            amount: product.amount,
            productIndex: productIndex + 1
        })));
    
    console.log(products);

    请注意,flatMap 在 Ramda 中称为 chain,但您需要将其命名为 addIndex

    const inventories = [
        {
            id: "Berlin",
            products: [{ sku: "123", amount: 99 }]
        },
        {
            id: "Paris",
            products: [
                { sku: "456", amount: 3 },
                { sku: "789", amount: 777 }
            ]
        }
    ];
    
    const chainIndexed = R.addIndex(R.chain);
    const mapIndexed = R.addIndex(R.map);
    
    const products = chainIndexed((inventory, inventoryIndex) =>
        mapIndexed((product, productIndex) => ({
            inventoryId: inventory.id,
            inventoryIndex: inventoryIndex + 1,
            sku: product.sku,
            amount: product.amount,
            productIndex: productIndex + 1
        }), inventory.products), inventories);
    
    console.log(products);
    <script src="https://unpkg.com/ramda@0.27.1/dist/ramda.min.js"></script>

    【讨论】:

    • 虽然这可行,但您的内部调用逻辑上不应该是maps 而不是flatMap/chainIndexed
    • @ScottSauyet 很好。不知道为什么没有抛出类型错误。
    • 因为它是 JS,而那些不应该工作的事情会......直到他们不工作!
    • 谢谢你们,这就像一个魅力。我只有一个额外的问题。当“外部”inventoryinventoryIndex 变量用于构建对象时,您能否将内部 mapIndexed 函数视为纯函数?
    • @TomaszLempart 是的,它是一个纯函数。
    【解决方案2】:

    我可能会以与 Aadit 建议的方式类似的方式执行此操作,尽管我会稍微不同地构建它,并使用参数解构

    const convert = (inventories) =>
      inventories .flatMap (({id: inventoryId, products}, i, _, inventoryIndex = i + 1) =>
        products.map (
          ({sku, amount}, i, _, productIndex = i + 1) => 
            ({inventoryId, inventoryIndex, sku, amount, productIndex})
        )
      )
    
    const inventories = [{id: "Berlin", products: [{sku: "123", amount: 99}]}, {id: "Paris", products: [{sku: "456", amount: 3}, {sku: "789", amount: 777}]}]
    
    console .log (convert (inventories));
    .as-console-wrapper {max-height: 100% !important; top: 0}

    但是,如果您想将其分解为更小的组件,Ramda 可以帮助您编写它们并将它们粘合在一起成为一个整体。如果我们试图描述我们正在做的事情,我们可能会将其视为四个步骤。我们将id 字段重命名为inventoryId,我们为我们的库存添加一个运行索引,并为每组products 单独索引,我们通过提升products 数组与它们的合并来非规范化/扁平化我们的嵌套列表父母。

    所有这些都是潜在的可重用转换。如果我们愿意,我们可以为这些分解出单独的辅助函数,然后使用 Ramda 将它们组合成一个函数。它可能看起来像这样:

    const {pipe, toPairs, map, fromPairs, addIndex, chain, merge, evolve} = R
    
    const mapKeys = (cfg) => pipe (
      toPairs,
      map (([k, v]) => [cfg [k] || k, v]),
      fromPairs
    )
     
    const addOrdinals = (name) => 
      addIndex (map) ((x, i) => ({... x, [name]: i + 1 }))
    
    const promote = (name) => 
      chain (({[name]: children, ...rest}) => map (merge(rest), children))
    
    const transform = pipe (
      map (mapKeys ({id: 'inventoryId'})),
      addOrdinals ('inventoryIndex'),
      map (evolve ({products: addOrdinals ('productIndex')})),
      promote ('products')
    )
    
    const inventories = [{id: "Berlin", products: [{sku: "123", amount: 99}]}, {id: "Paris", products: [{sku: "456", amount: 3}, {sku: "789", amount: 777}]}]
    
    console .log (transform (inventories))
    .as-console-wrapper {max-height: 100% !important; top: 0}
    <script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

    这涉及到更多的代码,但我们所做的是创建了许多有用的小函数,并通过组合对这些小函数的调用使我们的主函数更具声明性。在您的项目中可能值得,也可能不值得,但肯定值得考虑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-23
      • 2015-09-01
      • 2015-10-20
      • 2018-12-14
      • 2020-02-10
      • 2019-05-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多