【问题标题】:Splitting/nesting _.flow with lodash (or ramda)使用 lodash(或 ramda)拆分/嵌套 _.flow
【发布时间】:2019-06-20 12:17:29
【问题描述】:

我有两个对象,一个描述位置的features,另一个描述这些特征的prices

features = {
  improvements: [...] // any array of many id's
  building: {} // only one id, may be undefined
}
prices = {
  id_1: 10,
  ...
}

我想遍历features 并整理所有prices有时features.buildingundefined,有时features.improvements 为空

Additional code/workbench on repl.it

我可以用lodash 这样做:

result = _(features.improvements)
  .map(feature => prices[feature.id])
  .concat(_.cond([
    [_.isObject, () => prices[features.building.id]]
  ])(features.building)) // would like to clean this up
  .compact()
  .value();

我有兴趣以更实用的方式写这个,我最终得到:

result = _.flow([
  _.partialRight(_.map, feature => prices[feature.id]),
  _.partialRight(_.concat, _.cond([
    [_.isObject, () => prices[features.building.id]]
  ])(features.building)),
  _.compact,
])(features.improvements)

我还是要在中途几乎偷偷打电话给features.building,这让我感觉很尴尬。

我想要的是(伪编码):

flow([
  // maybe need some kind of "split([[val, funcs], [val, funcs]])?
  // the below doesn't work because the first
  // flow's result ends up in the second

  // do the improvement getting
  flow([
    _.map(feature => prices[feature.id])
  ])(_.get('improvements')),

  // do the building getting
  flow([
    _.cond([
      [_.isObject, () => [argument.id]]
    ])
  ])(_.get('building')),

  // concat and compact the results of both gets
  _.concat,
  _.compact,
])(features); // just passing the root object in

有可能吗?经验丰富的 FP 程序员会如何处理这个问题?

我对使用 lodash-fprambda(或任何我可以尝试理解的具有良好文档的内容)编写的解决方案持开放态度,因为这些解决方案可能会提供更简洁的代码,因为它们比标准的 @987654338 更面向功能/咖喱@。

【问题讨论】:

  • 您的代码变得复杂并需要undefined 来指示缺少数据的原因是使用了您应该使用unions(产品)的产品类型。
  • 我不确定这应该如何适合我的代码。是自定义类型吗?我使用 Vuex 作为数据存储,它更喜欢使用 POJO 和原始类型。
  • 难道我还会遇到同样的问题吗? union({id:_} | not_set),我还需要过滤掉我的 not_set 值吗?

标签: functional-programming lodash ramda.js


【解决方案1】:

洛达什

这是一个使用_.flow()的解决方案:

  1. 使用_.values()_.flatten()_.compact() 将特征转换为数组(undefined 时忽略building)。
  2. 使用_.map() 转换为ids 数组。
  3. 使用_.at() 获取值。

const { values, flatten, compact, partialRight: pr, map, partial, at } = _;

const fn = prices => _.flow([
  values,
  flatten,
  compact,
  pr(map, 'id'),
  partial(at, prices)
])

const prices = {
  i_1: 'cost_i_1',
  i_2: 'cost_i_2',
  i_3: 'cost_i_3',
  i_4: 'cost_i_4',
  b_1: 'cost_b_1',
};

const features = {
  improvements: [
    {id: 'i_1'},
    {id: 'i_2'},
    {id: 'i_3'},
    {id: 'i_4'},
  ],
  building: {
    id: 'b_1'
  },
};

const result = fn(prices)(features);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

lodash/fp

const { values, flatten, compact, map, propertyOf } = _;

const fn = prices => _.flow([
  values,
  flatten,
  compact,
  map('id'),
  map(propertyOf(prices))
])

const prices = {"i_1":"cost_i_1","i_2":"cost_i_2","i_3":"cost_i_3","i_4":"cost_i_4","b_1":"cost_b_1"};
const features = {"improvements":[{"id":"i_1"},{"id":"i_2"},{"id":"i_3"},{"id":"i_4"}],"building":{"id":"b_1"}};

const result = fn(prices)(features);

console.log(result);
<script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>

拉姆达

  1. 使用R.identity 获取值、展平和过滤undefineds。
  2. 使用R.map 获取id 道具。
  3. 使用翻转的R.propsprices 获取ids 值

const { pipe, values, flatten, filter, identity, map, prop, flip, props } = R;

const propsOf = flip(props);

const fn = prices => pipe(
  values,
  flatten,
  filter(identity),
  map(prop('id')),
  propsOf(prices)
);

const prices = {"i_1":"cost_i_1","i_2":"cost_i_2","i_3":"cost_i_3","i_4":"cost_i_4","b_1":"cost_b_1"};
const features = {"improvements":[{"id":"i_1"},{"id":"i_2"},{"id":"i_3"},{"id":"i_4"}],"building":{"id":"b_1"}};

const result = fn(prices)(features);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

【讨论】:

    【解决方案2】:

    这是我使用 Ramda 的建议。

    我建议将问题分解为更小的函数:

    1. 获取所有改进的价格 ID:getImprovementIds
    2. 获取建筑物的价格 ID:getBuildingId
    3. 获取所有商品的价格 ID:getPriceIds
    4. 获取给定价格 ID 的价格:getPrice

    示例

    getImprovementIds(features); //=> ['id_1', 'id_2', 'id_3']
    getBuildingIds(features);    //=> ['id_5']
    getPriceIds(features);       //=> ['id_1', 'id_2', 'id_3', 'id_5']
    getPrice(prices, 'id_2');    //=> 20
    getPrice(prices, 'foo');     //=> 0
    

    一旦您有了价格 ID 列表,就很容易将该列表转换为价格列表:

    map(getPrice(prices), ['id_1', 'id_2', 'id_3', 'id_5']); //=> [10, 20, 0, 50]
    

    完整示例

    const {propOr, ifElse, hasPath, path, always, compose, sum, map, flip, converge, of, concat} = R;
    
    const features = {
      improvements: ['id_1', 'id_2', 'id_3'],
      building: {
        id: 'id_5'
      }
    };
    
    const prices = {
      id_1: 10,
      id_2: 20,
      id_5: 50
    };
    
    /**
     * Take a features object and return the price id of all improvements.
     * @param {object} features
     * @return {array} array of ids
     */
    const getImprovementIds = propOr([], 'improvements');
    
    /**
     * Take a features object and return the price id of the building (if any)
     * @param {object} features
     * @return {array} array of ids
     */
    const getBuildingId =
      ifElse(hasPath(['building', 'id']),
        compose(of, path(['building', 'id'])),
        always([]));
    
    /**
     * Take a features object and returns all price id of all improvements and of the building (if any)
     * @param {object} features
     * @return {array} array of ids
     */
    const getPriceIds = converge(concat, [getImprovementIds, getBuildingId]);
    
    /**
     * Take a prices object and a price id and return the corresponding price
     *
     * @example
     * getPrice(prices, 'id_2'); //=> 20
     *
     * @param {object} prices
     * @param {string} id
     * @return {number}
     */
    const getPrice = flip(propOr(0));
    
    const getPriceList = (prices, features) =>
      map(getPrice(prices), getPriceIds(features));
    
    console.log(
      getPriceList(prices, features)
    )
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-13
      • 2019-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多