I have two objects, one describes the features
of a location, the other describes the prices
of those features.
features = {
improvements: [...] // any array of many id's
building: {} // only one id, may be undefined
}
prices = {
id_1: 10,
...
}
I want to iterate over features
and collate all the prices
. Sometimes features.building
will be undefined
, sometimes features.improvements
will be empty.
Additional code/workbench on repl.it
I can do this with lodash
in this manner:
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();
I'm interested in writing this in a more functional manner, and I ended up with:
result = _.flow([
_.partialRight(_.map, feature => prices[feature.id]),
_.partialRight(_.concat, _.cond([
[_.isObject, () => prices[features.building.id]]
])(features.building)),
_.compact,
])(features.improvements)
I still have to almost secretly call features.building
mid stream, which feels awkward to me.
What I'd like to get to is (pseudocoded):
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
Is it possible? How would a more seasoned FP-Programmer approach this?
I'm open to solutions written with lodash-fp
or rambda
(or anything with good docs I can try to understand) since those probably give cleaner code because they're more functionally-orientated/curried than standard lodash
.
Lodash
Here is a solution that uses _.flow()
:
_.values()
, _.flatten()
, and _.compact()
(to ignore building
when undefined
).id
s with _.map()
._.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>
Ramda
undefined
s using R.identity
.id
props with R.map
.R.props
to get the ids values from prices
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>