Ramda - How to map object values to string - ramda.js

I am trying to use Ramda to get x,y coordinates to draw polyline in svg.
I have edge object which contains all the points I need and it looks like this:
const edge = {
"id": "e1_s0",
"startPoint": {
"x": 212,
"y": 505.33333333333326
},
"endPoint": {
"x": 232,
"y": 302
},
"bendPoints": [
{
"x": 222,
"y": 505.33333333333326
},
{
"x": 222,
"y": 302
}
],
"incomingShape": "n1",
"outgoingShape": "n2"
}
I tried to use Ramda to get x, y from startPoint, endPoint and bendPoints. I came up with this piece of code:
R.pipe(
R.pick(['bendPoints', 'endPoint', 'startPoint']),
R.values,
R.flatten,
R.sortBy(R.prop('x'))
)(edge)
Here you can check it in ramda editor
As a result, I get:
[
{
x: 212,
y: 505.33333333333326
},
{
x: 222,
y: 505.33333333333326
},
{
x: 222,
y: 302
},
{
x: 232,
y: 302
}
]
But I need a string with x and y pairs divided by space and I have no idea how to proceed to get to that point: 212,505.33333333333326 222,505.33333333333326 222,302 232,302
Any help would be appreciated
EDIT: complete solution
R.pipe(
R.pick(['bendPoints', 'endPoint', 'startPoint']),
R.values,
R.flatten,
R.sortBy(R.prop('x')),
R.map(
R.pipe(
R.props(['x', 'y']),
R.join(',')
)
),
R.join(" ")
)(edge)

I think the steps you need are:
For each point,
Take the x and y values, and
Join those in to a single string, separated by ","
Take those strings and join them in to a single string, separated by " "
Which can be translated to ramda quite literally:
const formatPoints = pipe(
map(
pipe(
props(["x", "y"]),
join(",")
)
),
join(" ")
);

Related

Ramda - How to sort array with nested object with string to number conversion

I am trying to sort the array which looks like this:
const testArray = [
{
"id": "1",
"nodeLayout": {
"x": "12.0",
"y": "1.0",
"width": 200,
"height": 87
}
},
{
"id": "2",
"nodeLayout": {
"x": "1.0",
"y": "1.0",
"width": 200,
"height": 87
}
},
{
"id": "3",
"nodeLayout": {
"x": "0.0",
"y": "1.0",
"width": 200,
"height": 87
}
}
]
I was trying to sort it with this:
R.pipe(
R.pluck('nodeLayout'),
R.map(R.pipe(R.props(['x']), R.join(','))),
R.sortWith([R.ascend(parseFloat)])
)(testArray)
Which is working fine but I am getting only x values sorted and I am not able to fit this sort into this one:
R.pipe(
R.filter(
R.allPass([
R.pathEq(['nodeLayout', 'y'], '1.0'),
R.propEq('group', 4)
]
))
// I tried to add it here: R.sortBy(R.path(['nodeLayout', 'x'])) but I need to parse string first and I have no idea how to combine those
)(testArray)
To sum up, I am trying to get the whole object with all properties sorted.
Any help would be appreciated. Thank you
If you combine your two approaches, you should be able to achieve what you're wanting.
The main thing being that you can R.pipe the call to parseFloat after obtaining the value you'd like to sort with R.path. This piped function can be provided as the argument to R.sortBy.
R.pipe(
R.filter(
R.allPass([
R.pathEq(['nodeLayout', 'y'], '1.0'),
R.propEq('group', 4)
]
)),
R.sortBy(R.pipe(R.path(['nodeLayout', 'x']), parseFloat))
)(testArray)

Dataweave 2 - How can i join 2 arrays by index inside object without lose data parent object

How to join 3 arrays by index (lines, pckSlip and linesDt) and generate an arrays object by linesDt after that you have to generate a new field "totalCost" which is to add the "cost" field of all cost elements in the linesDt array, note the "number" field in the original object is preserved for all new elements spawned from the parent.
Input:
{
"lines":[
{
"requestedQuantity":1,
"pendingQuantity":0
},
{
"requestedQuantity":2,
"pendingQuantity":0
}
],
"number":"98",
"pckSlip":[
{
"trackingNumber":"10534",
"boxesNum":1
},
{
"trackingNumber":"00049",
"boxesNum":1
}
],
"linesDt":[
{
"number":"5678",
"cost":7.7
},
{
"number":"1234",
"cost":7.3
}
]
}
Ouput:
[
{
"number":"5678",
"cost":7.7,
"requestedQuantity":1,
"pendingQuantity":0,
"trackingNumber":"10534",
"boxesNum":1,
"totalCost":15,
"order":"98"
},
{
"number":"1234",
"cost":7.3,
"requestedQuantity":2,
"pendingQuantity":0,
"trackingNumber":"00049",
"boxesNum":1,
"totalCost":15,
"order":"98"
}
]
NOTE: We generate 2 new elements because they are the total of indices found in "linesDt" within an array of elements.
Any help would be appreciated. Thank you.
Mapping each element of lines gives you the index to use in the other arrays. The ++ operator can be used to concatenate objects all the objects together. The calculated fields are added just as another object.
%dw 2.0
output application/json
var totalCost = sum(payload.linesDt.*cost)
---
payload.lines map (
$
++ payload.pckSlip[$$]
++ payload.linesDt[$$]
++ {totalCost: totalCost, order: payload.number}
)
Output:
[
{
"requestedQuantity": 1,
"pendingQuantity": 0,
"trackingNumber": "10534",
"boxesNum": 1,
"number": "5678",
"cost": 7.7,
"totalCost": 15.0,
"order": "98"
},
{
"requestedQuantity": 2,
"pendingQuantity": 0,
"trackingNumber": "00049",
"boxesNum": 1,
"number": "1234",
"cost": 7.3,
"totalCost": 15.0,
"order": "98"
}
]
Assuming that the size of each of the arrays is going to be the same.
Script
%dw 2.0
output application/json
---
1 to sizeOf(payload.lines) map {
(payload.linesDt[($$)] ++ payload.lines[($$)] ++ payload.pckSlip[($$)] ++ ("totalCost": sum(payload.linesDt..cost) as String) ++ ("order": payload.number))
}
Output
[
{
"number": "5678",
"cost": 7.7,
"requestedQuantity": 1,
"pendingQuantity": 0,
"trackingNumber": "10534",
"boxesNum": 1,
"totalCost": "15",
"order": "98"
},
{
"number": "1234",
"cost": 7.3,
"requestedQuantity": 2,
"pendingQuantity": 0,
"trackingNumber": "00049",
"boxesNum": 1,
"totalCost": "15",
"order": "98"
}
]

Selection Transform as Text/Title

How can I display a transform over selected points as text within a title or subtitle?
{
"$schema": "https://vega.github.io/schema/vega-lite/v4.json",
"data": {"url": "data/stocks.csv"},
"transform": [{"filter": "datum.symbol==='GOOG'"}],
"width": 800,
"title": {
"text": "Google's stock price over time.",
"subtitle": "selected average: ???"
},
"selection": {
"interval": {
"type": "interval",
"encodings": ["x"]
}
},
"mark": "point",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "price", "type": "quantitative"}
}
}
A sum transformation does not lend itself to any graphical/visual representations. An idea is to have it as a selection tooltip, but that is not possible. So I settle for a subtitle.
I don't think there is an easy way to insert a computed value within a subtitle, but you can achieve the same effect using a layer chart with a text mark that contains a computed value.
For example (open in editor):
{
"data": {"url": "data/stocks.csv"},
"transform": [{"filter": "datum.symbol==='GOOG'"}],
"width": 800,
"title": {"text": "Google's stock price over time."},
"layer": [
{
"transform": [
{"filter": {"selection": "interval"}},
{"aggregate": [{"op": "sum", "field": "price", "as": "total_price"}]}
],
"mark": "text",
"encoding": {
"x": {"value": 400},
"y": {"value": -10},
"text": {"field": "total_price", "type": "quantitative"}
}
},
{
"mark": "point",
"encoding": {
"x": {"field": "date", "type": "temporal"},
"y": {"field": "price", "type": "quantitative"}
},
"selection": {"interval": {"type": "interval", "encodings": ["x"]}}
}
]
}
Note that the text layer has x and y encoding values specified in pixels from the upper left of the chart.
I was going to say there are two possible approaches, but then #jakevdp post his answer, so I guess there are now three.
Use an ExprRef in title.subtitle. While that isn't explicitly supported in the documentation it doesn't make sense that only title.text would support an ExprRef. So I gave it a shot, and it works. Though the Vega editor will still raise schema validation warnings. I have a more involved example below using binned data.
Filter and aggregate the selection. This will modify the current data stream and Vega-Lite doesn't support more than one data stream (unless using layers), so you'll have to patch the generated Vega.
Access the text mark of the title using title.encode.subtitle.update.text. Once again, necessary to patch the generated Vega. (This was on the assumption that a subtitle couldn't be an ExprRef).
The neat thing about (2) is that it allows adding a text mark to the selection mark, so the text could be made to follow the selection. That said that's possible with #jakevdp's answer as well, and his answer is much simpler.
JavaScript loader ensuring that nothing runs out-of-order:
%%javascript
function loadjs(ls, o) {
let i = 0;
let b = JSON.parse(document.querySelector('#jupyter-config-data').text)["baseUrl"];
ls = ls.reduce((a,l) => {
if (!l.path)
return a;
let p = l.path;
if (l.local)
p = "/" + [b,"/files",p].map(s => s.replace(/^\/+|\/+$/, "")).filter(i => i).join("/");
if (document.querySelector("script[src='" + p + "']"))
return a;
return [...a, p];
}, [])
function load() {
if (i >= ls.length)
return o();
let t = document.createElement("script");
[t.type, t.src, t.onload] = ["text/javascript", ls[i], (i+1>=ls.length) ? o : load];
i = i+1;
document.head.appendChild(t);
}
return load();
}
window.loadjs = loadjs
JavaScript loader for the JupyterLab cell as an IPython magic:
import IPython
import IPython.core.magic as ipymagic
#ipymagic.magics_class
class LoadJSMagics(ipymagic.Magics):
#ipymagic.cell_magic
def loadjs(self, line, cell):
js = f"loadjs({line}, () => {{\n{cell}\n}});"
return IPython.display.Javascript(js)
IPython.get_ipython().register_magics(LoadJSMagics)
Jinja templating for JupyterLab cells as an IPython magic:
import jinja2
import IPython
import IPython.core.magic as ipymagic
#ipymagic.magics_class
class JinjaMagics(ipymagic.Magics):
#ipymagic.cell_magic
def jinja(self, line, cell):
t = jinja2.Template(cell)
r = t.render({k:v for k,v in self.shell.user_ns.items() if k not in self.shell.user_ns_hidden})
IPython.get_ipython().run_cell(r)
#d = getattr(IPython.display, line.strip(), IPython.display.display)
#return d(r)
IPython.get_ipython().register_magics(JinjaMagics)
Generate some example temporal data in Pandas:
import pandas as pd
import numpy as np
c1 = np.random.randint(1,6, size=15)
c2 = pd.date_range(start="2021-01-01",end="2021-01-15")
df = pd.DataFrame({"day": c2, "value": c1})
df = df.drop([2, 5,6,7,13])
df
The necessary imports:
# Convert the Pandas dataframe to a format suitable for Vega-Lite.
import altair
# Tag Vega-Embed div's with UUIDs ensuring the correct div is targeted.
import uuid
import json
vega_libs =\
[ {"path": "https://cdn.jsdelivr.net/npm/vega#5"}
, {"path": "/libs/vega-lite#4-fix.js", "local": True}
, {"path": "https://cdn.jsdelivr.net/npm/vega-embed#6"}
]
The Vega-Lite:
s =\
{ "title":
{ "text": "Daily Counts"
, "subtitle": {"expr": "selectionSum(data('interval_store'), data('data_0'))"}
, "subtitleFont": "monospace"
}
, "mark": "bar"
, "encoding":
{ "x":
{ "type": "temporal"
, "bin": "binned"
, "field": "start"
, "axis": { "tickCount": "day" }
}
, "x2": {"field": "end"}
, "y": {"type": "quantitative", "field": "value"}
}
, "selection":
{ "interval":
{ "type": "interval"
, "encodings": ["x"]
}
}
, "transform":
[ # Convert 'day' from 'string' to timestamp ('number')
{"calculate": "toDate(datum.day)", "as": "day"}
# Provide "start" and "end" as Date objects to match the
# type of temporal domain objects
, {"calculate": "timeOffset('hours', datum.day, -12)", "as": "start"}
, {"calculate": "timeOffset('hours', datum.day, 12)", "as": "end"}
]
, "height": 250
, "width": "container"
, "$schema": "https://vega.github.io/schema/vega-lite/v4.json"
, "config": {"customFormatTypes": "True"}
, "data": altair.utils.data.to_values(df)
}
And finally running the Vega-Lite:
%%jinja
%%loadjs {{json.dumps(vega_libs)}}
{% set visid = uuid.uuid4() %}
element.innerHTML = `
<style>.vega-embed.has-actions {width:90%}</style>
<div id="vis-{{visid}}"></div>
`
var spec = {{json.dumps(s)}}
vega.expressionFunction("selectionSum", function(selection, data) {
var view = this.context.dataflow;
function intersects(i1, i2) {
return (i1[1] >= i2[0] && i1[0] <= i2[1]);
}
function cmp_interval_pt(i0, i1, p) {
if (i1 < p)
return -1;
if (i0 > p)
return 1;
return 0;
}
function cmp_primitive(a,b) {
if (a < b)
return -1
if (a > b)
return 1;
return 0;
}
function bisect_left(l, v, fc=cmp_primitive) {
return _bisect_left(l, 0, l.length, v, fc);
}
function _bisect_left(l, l0, l1, v, fc) {
if (l1 <= l0)
return l0;
var i = Math.floor((l0+l1)/2);
var c = fc(l[i], v);
if (c < 0)
l0 = i + 1;
else
l1 = i;
return _bisect_left(l, l0, l1, v, fc);
}
function bisect_right(l, v, fc=cmp_primitive) {
return _bisect_right(l, 0, l.length, v, fc);
}
function _bisect_right(l, l0, l1, v, fc) {
if (l1 <= l0)
return l0;
var i = Math.floor((l0+l1)/2);
var c = fc(l[i], v);
if (c <= 0)
l0 = i + 1;
else
l1 = i;
return _bisect_right(l, l0, l1, v, fc);
}
function cmp_data(lv, v) {
return cmp_interval_pt(lv.start, lv.end, v);
}
function constant_len_digits(s,l) {
return " ".repeat(Math.max(0, l-s.toString().length)) + s
}
if (selection.length) {
var r = selection[0]["values"][0];
var d0 = bisect_left(data, r[0], cmp_data);
var d1 = bisect_right(data, r[1], cmp_data);
var s = data.slice(d0,d1).reduce((a,v)=>a+v.value, 0);
}
else
var s = 0
return `selected: ${constant_len_digits(s,3)}`;
});
vegaEmbed('#vis-{{visid}}', spec).then(function(result) {
}).catch(console.error);
And the result:
Note that when it comes to coloring and selecting binned regions, you'll have to calculate the intersections yourself.

Getting the maximum value from an array in a JSON response in Karate

I have the following Json as a response from a API call
{
"location": {
"name": "London",
"region": "City of London, Greater London",
"country": "United Kingdom",
"lat": 51.52,
"lon": -0.11,
"tz_id": "Europe/London",
"localtime_epoch": 1583594426,
"localtime": "2020-03-07 15:20"
},
"forecast": {
"forecastday": [
{
"date": "2020-03-03",
"day": {
"maxtemp_c": 9,
"mintemp_c": 4
}
},
{
"date": "2020-03-04",
"day": {
"maxtemp_c": 8,
"mintemp_c": 4.1
}
},
{
"date": "2020-03-05",
"day": {
"maxtemp_c": 7,
"mintemp_c": 5.6
}
}
]
}
}
I want to find out which date had the highest temperature amongst the 3 days.
The way I am currently doing feels inefficient as I am checking for the temperature element within my js function and it is as follows
* def hottest =
"""
function(array) {
var greatest;
var indexOfGreatest;
for (var i = 0; i < array.length; i++) {
if (!greatest || array[i].day.maxtemp_c > greatest) {
greatest = array[i].day.maxtemp_c;
indexOfGreatest = i;
}
}
return indexOfGreatest;
}
"""
* def index = call hottest response.forecast.forecastday
* def hottestdate = response.forecast.forecastday[index].date
* print hottestdate
With this I am getting the correct result but can someone kindly suggest a better way of doing this?
Best practice in Karate is to NOT use JS for loops at all. It results in cleaner, more readable code:
* def fun = function(x){ return { max: x.day.maxtemp_c, date: x.date } }
* def list = karate.map(response.forecast.forecastday, fun)
* def max = 0
* def index = 0
* def finder =
"""
function(x, i) {
var max = karate.get('max');
if (x.max > max) {
karate.set('max', x.max);
karate.set('index', i);
}
}
"""
* karate.forEach(list, finder)
* print 'found at index', index
* print 'item:', list[index]
Note how easy it is to re-shape a given JSON, the result of list here would be:
[
{
"max": 9,
"date": "2020-03-03"
},
{
"max": 8,
"date": "2020-03-04"
},
{
"max": 7,
"date": "2020-03-05"
}
]

Modify Array of Object using ramda

I have an array of object like below ,
[
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [],
"selectedValues": []
}
]
also if i iterate the object, the "values" key present in the object, it will convert it into like below,
[
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"selectedValues": ["x", "y"]
}
]
I tried with ramda functional programming but no result,
let findValues = x => {
if(R.has('values')(x)){
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
R.map(R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}))
}
}
R.map(findValues, arrObj);
Immutability
First of all, you need to be careful with your verbs. Ramda will not modify your data structure. Immutability is central to Ramda, and to functional programming in general. If you're actually looking to mutate your data, Ramda will not be your toolkit. However, Ramda will transform it, and it can do so in a relatively memory-friendly way, reusing those parts of your data structure not themselves transformed.
Fixing your approach
Let's first look at cleaning up several problems in your function:
let findValues = x => {
if (R.has('values')(x)) {
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
R.map(R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}))
}
}
The first, most obvious issue is that this function does not return anything. As mentioned above, Ramda does not mutate your data. So a function that does not return something is useless when supplied to Ramda's map. We can fix this by returning the result of the map(evolve) call and returning the original value when the if condition fails:
let findValues = x => {
if (R.has('values')(x)) {
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
return R.map(R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}))
}
return x;
}
Next, the map call makes no sense. evolve already iterates the properties of the object. So we can remove that. But we also need to apply evolve to your input value:
let findValues = x => {
if (R.has('values')(x)){
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
return R.evolve({
availableValues: availableValues,
selectedValues: selectedValues
}, x)
}
return x;
}
There's one more problem. Evolve expects an object containing functions that transform values. For instance,
evolve({
a: n => n * 10,
b: n => n + 5
})({a: 1, b: 2, c: 3}) //=> {a: 10, b: 7, c: 3}
Note that the values of the properties in the object supplied to evolve are functions. This code is supplying values. We can wrap those values with R.always, via availableValues: R.always(availableValues), but I think it simpler to use a lambda with a parameter of "_", which signifies that the parameter is unimportant: availableValues: _ => availableValues. You could also write availableValues: () => available values, but the underscore demonstrates the fact that the usual value is ignored.
Fixing this gets a function that works:
let findValues = x => {
if (R.has('values')(x)){
let availableValues = R.prop('values')(x);
let selectedValues = R.pluck('a')(availableValues);
return R.evolve({
availableValues: _ => availableValues,
selectedValues: _ => selectedValues
}, x)
}
return x;
}
let arrObj = [{myValues: []}, {availableValues: [], myValues: [], selectedValues: [], values: [{a: "x", b: "1"}, {a: "y", b: "2"}]}];
console.log(R.map(findValues, arrObj))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
If you run this snippet here, you will also see an interesting point, that the resulting availableValues property is a reference to the original values one, not a separate copy of it. This helps show that Ramda is reusing what it can of your original data. You can also see this by noting that R.map(findValues, arrObj)[0] === arrObj[0] //=> true.
Other Approaches
I wrote my own versions of this, not starting from yours. Here is one working function:
const findValues = when(
has('values'),
x => evolve({
availableValues: _ => prop('values', x),
selectedValues: _ => pluck('a', prop('values', x))
}, x)
)
Note the use of when, which captures the notion of "when this predicate is true for your value, use the following transformation; otherwise use your original value." We pass the predicate has('values'), essentially the same as above. Our transformation is similar to yours, using evolve, but it skips the temporary variables, at the minor cost of repeating the prop call.
Something still bothers me about this version. Using those lambdas with "_" is really a misuse of evolve. I think this is cleaner:
const findValues = when(
has('values'),
x => merge(x, {
availableValues: prop('values', x),
selectedValues: pluck('a', prop('values', x))
})
)
Here we use merge, which is a pure function version of Object.assign, adding the parameters of the second object to those of the first, replacing when they conflict.
This is what I would choose.
Anther solution
Since I started writing this answer, Scott Christopher posted another one, essentially
const findValues = R.map(R.when(R.has('values'), R.applySpec({
myValues: R.prop('myValues'),
values: R.prop('values'),
availableValues: R.prop('values'),
selectedValues: R.o(R.pluck('a'), R.prop('values'))
})))
This is a great solution. It also is nicely point-free. If your input objects are fixed to those properties, then I would choose this over my merge version. If your actual objects contain many more properties, or if there are dynamic properties that should be included in the output only if they are in the input, then I would choose my merge solution. The point is that Scott's solution is cleaner for this specific structure, but mine scales better to more complex objects.
Step-by-step
This is not a terribly complex transformation, but it is non-trivial. One simple way to build something like this is to build it up in very minor stages. I often do this in the Ramda REPL so I can see the results as I go.
My process looked something like this:
Step 1
Make sure I properly transform only the correct values:
const findValues = when(
has('values'),
always('foo')
)
map(findValues, arrObj) //=> [{myValues: []}, "foo"]
Step 2
Make sure my transformer function is given the proper values.
const findValues = when(
has('values'),
keys
)
map(findValues, arrObj)
//=> [{myValues: []}, ["myValues", "values", "availableValues", "selectedValues"]]
Here identity would work just as well as keys. Anything that demonstrates that the right object is passed would be fine.
Step 3
Test that merge will work properly here.
const findValues = when(
has('values'),
x => merge(x, {foo: 'bar'})
)
map(findValues, arrObj)
//=> [
// {myValues: []},
// {
// myValues: [],
// values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
// availableValues: [],
// selectedValues: [],
// foo: "bar"
// }
// ]
So I can merge two objects properly. Note that this keeps all the existing properties of my original object.
Step 4
Now, actually transform the first value I want:
const findValues = when(
has('values'),
x => merge(x, {
availableValues: prop('values', x)
})
)
map(findValues, arrObj)
//=> [
// {myValues: []},
// {
// myValues: [],
// values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
// availableValues: [{a: "x", b: "1"}, {a: "y", b: "2"}],
// selectedValues: []
// }
// ]
Step 5
This does what I want. So now add the other property:
const findValues = when(
has('values'),
x => merge(x, {
availableValues: prop('values', x),
selectedValues: pluck('a', prop('values', x))
})
)
map(findValues, arrObj)
//=> [
// {myValues: []},
// {
// myValues: [],
// values: [{a: "x", b: "1"}, a: "y", b: "2"}],
// availableValues: [{a: "x", b" "1"}, {a: "y", b: "2"}],
// selectedValues: ["x", "y"]
// }
// ]
And this finally gives the result I want.
This is a quick process. I can run and test very quickly in the REPL, iterating my solution until I get something that does what I want.
R.evolve allows you to update the values of an object by passing them individually to their corresponding function in the supplied object, though it doesn't allow you to reference the values of other properties.
However, R.applySpec similarly takes an object of functions and returns a new function that will pass its arguments to each of the functions supplied in the object to create a new object. This will allow you to reference other properties of your surrounding object.
We can also make use of R.when to apply some transformation only when it satisfies a given predicate.
const input = [
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [],
"selectedValues": []
}
]
const fn = R.map(R.when(R.has('values'), R.applySpec({
myValues: R.prop('myValues'),
values: R.prop('values'),
availableValues: R.prop('values'),
selectedValues: R.o(R.pluck('a'), R.prop('values'))
})))
const expected = [
{
"myValues": []
},
{
"myValues": [],
"values": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"availableValues": [
{"a": "x", "b": "1"},
{"a": "y", "b": "2"}
],
"selectedValues": ["x", "y"]
}
]
console.log(R.equals(fn(input), expected))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>