使用lodash(或ramda)拆分/嵌套_.flow

时间:2019-01-27 07:31:34

标签: functional-programming lodash ramda.js

我有两个对象,一个描述一个位置的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-Programmer将如何处理这个问题?

我愿意接受用lodash-fprambda(或任何我能理解的有好的文档)编写的解决方案,因为这些解决方案可能会提供更简洁的代码,因为它们更注重功能/更易于使用比标准lodash

2 个答案:

答案 0 :(得分:2)

Lodash

以下是使用_.flow()的解决方案:

  1. 使用_.values()_.flatten()_.compact()将要素转换为数组(在building时忽略undefined)。
  2. 使用id转换为_.map()个数组。
  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. 使用undefined获取值,展平并过滤R.identity
  2. id获得R.map道具。
  3. 使用翻转的R.propsprices获取id值

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>

答案 1 :(得分:1)

这是我使用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>