Using Ramda to accumulate sum of values in objects - ramda.js

I have an object like this:
obj = {
'key1': {'prop1': 123,'prop2':345},
'key2': {'prop1': 673,'prop3':642}
}
I would like to have the following result:
result = {'prop1': 796, 'prop2':345, 'prop3':642}
That is, I want a single object that has all properties. I have a function like this:
Object.values(obj).reduce((acc,currentObj) => {
const props = Object.keys(currentObj);
props.forEach(prop => {
if(prop in acc){
acc[prop] += currentObj[prop]
} else {
acc[prop] = currentObj[prop]
}
})
return acc
}, {})
I believe this works fine, but is there any way to do the same with RamdaJS?

A simple reduce function would do that.
const fn = R.pipe(
R.values,
R.reduce(R.mergeWith(R.add), {}),
);
//--
const data = {
key1: { prop1: 123, prop2: 345 },
key2: { prop1: 673, prop3: 642 },
};
console.log(
fn(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>

Related

Ramda: filtering an array against an array of items

I have a working function to filter the following array:
const arrayOne = [
{
node: {
caseStudyFields: {
filterTags: [
"Temperature control"
]
}
}
},
{
node: {
caseStudyFields: {
filterTags: null
}
}
},
{
node: {
caseStudyFields: {
filterTags: [
"Specialist manufacturing",
"Pharmaceuticals"
]
}
}
},
]
const arrayTwo = [
'Pharmaceuticals',
'Specialist manufacturing',
'Temperature control'
]
const fn = n => n.node.caseStudyFields.filterTags &&
n.node.caseStudyFields.filterTags.some(r => arrayTwo.includes(r))
return arrayOne.filter(fn)
This code works fine, but I wanted to convert it to Ramda (for fun). I've got so far in finding the path but I've become confused with the some and includes (any in Ramda?)
const filter = R.filter(
R.pipe(
R.path(['node', 'caseStudyFields', 'filterTags']),
)
);
return filter(arrayOne)
Use R.pathOr to get the value at the path, and return and empty array if it's null. This will prevent filter from erroring when encountering the null.
Use R.any (Ramda's equivalent to Array.some()) with R.includes, curried with the array of tags, to find matching items:
const { curry, filter, pipe, pathOr, any, includes, __ } = R
const filterByTags = curry((tags, arr) =>
filter(pipe(
pathOr([], ['node', 'caseStudyFields', 'filterTags']),
any(includes(__, tags))
))
(arr))
const arrayOne = [{"node":{"caseStudyFields":{"filterTags":["Temperature control"]}}},{"node":{"caseStudyFields":{"filterTags":null}}},{"node":{"caseStudyFields":{"filterTags":["Specialist manufacturing","Pharmaceuticals"]}}}]
const arrayTwo = ["Pharmaceuticals","Specialist manufacturing","Temperature control"]
const result = filterByTags(arrayTwo, arrayOne)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>

React-Native AsyncStorage: I retrieve an array, but then it becomes only a single object of the array

I'm using AsyncStorage to store and retrieve an array of objects. The structure of the array is like this:
const tracks = [
{
title: title1,
exercises: [
{
object1Info...
},
{
object2Info...
}
]
},
{
title: title2,
exercises: [
{
object1Info...
}
]
},
{
title: title3,
exercises: [
{
object1Info...
}
]
}
]
As you can see, the objects in the array do themselves contain arrays, which again contain objects.
I'm storing the array like this:
const storeData = async (array) => {
try {
const stringifiedArray = JSON.stringify(array)
await AsyncStorage.setItem('#tracks_array', stringifiedArray)
} catch (e) {
console.log("Error saving data")
}
}
This seems to work fine. I then retrieve the data like this:
const retrieveData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('#tracks_array');
console.log('Parsed value: ' + JSON.parse(jsonValue)); //This prints 'Parsed value: [object Object],[object Object],[object Object],[object Object]'
return jsonValue !== null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log("Error retrieving data")
}
}
This seems to work fine as well.
I have the array stored also as state. So what I want to do is add an object to the array in state, store that new array in the AsyncStorage, and then retrieve the array and set this new array back to state. Storing the object seems to have no problems.
When I retrieve the new array, and console.log(JSON.parse(jsonValue)) inside retrieveData, it prints [object Object],[object Object],[object Object],[object Object]. However after I call const newData = retrieveData(), console.log(newData) prints just [object Object]. This is my first time using AsyncStorage so I must be misunderstanding something. Why does it only return one object, instead of the whole array?
EDIT: Sharing the whole component code:
import {
StyleSheet,
ScrollView,
View,
Text
} from 'react-native';
import Modal from 'react-native-modal';
import AsyncStorage from '#react-native-community/async-storage'
import Track from './Track.js';
import New from './New.js';
class Main extends Component {
constructor(props) {
super(props);
this.state = {
tracksData: tracks,
newTrack: false,
newExercise: false
}
storeData(this.state.tracksData);
}
renderTracks(data) {
console.log('Main data = ' + data)
return data.map((item, i) => {
console.log('Item = ' + item)
return (
<Track key={i} data={item} />
)
});
}
render() {
return (
<ScrollView horizontal={true} style={styles.Main}>
{this.renderTracks(this.state.tracksData)}
<Track data={{title: 'NewTrack', exercises: 'NewTrack'}} newTrackBox={this.toggleTrackBox} />
<Modal isVisible={this.state.newTrack} coverScreen={true}>
<New type={'track'} visible={this.toggleTrackBox} add={(name) => this.addTrack(name)}/>
</Modal>
</ScrollView>
);
}
toggleTrackBox = () => {
this.setState({
newTrack: !this.state.newTrack
})
}
addTrack = (name) => {
this.setState({
newTrack: false
});
var newTracks = this.state.tracksData;
newTracks.push({title: name, exercises: []})
console.log('newTracks = ' + newTracks)
storeData(newTracks);
this.updateData();
}
updateData() {
var newData = retrieveData();
console.log('newData = ' + newData)
setTimeout(() => {
console.log('Retrieved data = ' + newData);
if (newData) {
this.setState({
tracksData: newData
});
console.log("Data updated");
return true;
} else {
console.log("Data couldn't be retrieved");
return false;
}
}, 5000)
}
}
const storeData = async (value) => {
try {
const stringifiedArray = JSON.stringify(value)
console.log('Value to store: ' + value)
console.log('Stringified value to store: ' + stringifiedArray)
await AsyncStorage.setItem('#tracks_array', stringifiedArray)
//alert("Success saving data!")
} catch (e) {
console.log("Error saving data")
alert("Error saving data");
}
}
const retrieveData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('#tracks_array');
console.log('Stringified value retrieved: ' + jsonValue)
console.log('Parsed value: ' + JSON.parse(jsonValue))
return jsonValue !== null ? JSON.parse(jsonValue) : null;
} catch (e) {
console.log("Error retrieving data")
alert("Error retrieving data");
}
}
const tracks = [ //each member of this array is sent to a Track
{
title: 'Pull-up', // used in Track
exercises: [ // each member of this array is sent to an Exercise by Track
{
name: 'Pull-up', // used in Exercise
setStart: 2, // this and below used to calculate no of tiles and their contents, which are then sent to Tile
setEnd: 3,
repStart: 5,
repEnd: 8,
isInSeconds: false,
inProgress: null,
completed: true
},
{
name: 'Weighted Pull-up',
setStart: 3,
setEnd: 3,
repStart: 5,
repEnd: 8,
isInSeconds: false,
inProgress: [3, 5],
completed: false
}
]
},
{
title: 'Dip',
exercises: [
{
name: 'Dip',
setStart: 2,
setEnd: 3,
repStart: 5,
repEnd: 8,
isInSeconds: false,
inProgress: null,
completed: true
}
]
},
{
title: 'Squat',
exercises: [
{
name: 'Pistol squat',
setStart: 2,
setEnd: 3,
repStart: 5,
repEnd: 8,
isInSeconds: false,
inProgress: [2, 8],
completed: false
}
]
}
]
const styles = StyleSheet.create({
Main: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#022763'
}
})
export default Main;
Also, I should have mentioned, the actual error I'm getting is:
TypeError: undefined is not a function (near '...data.map...')
"retrieveData" is async function and hence returns a Promise.
What happened was it didn't finish retrieving the data and hence newData got 1 object out of all the array.
Try changing updateData like this:
updateData() {
var newData = retrieveData().then(data => {
console.log('newData = ' + newData)
setTimeout(() => {
console.log('Retrieved data = ' + newData);
if (newData) {
this.setState({
tracksData: newData
});
console.log("Data updated");
return true;
} else {
console.log("Data couldn't be retrieved");
return false;
}
}, 5000)
};
}
I've figured out the issue. I was retrieving data with AsyncStorage, then setting that data to the state something like this:
var newData = asyncRetrieveDataFunction();
this.setState({state1: newData})
However, because I declared the retrieveData() function as async, it was setting the state before the data had finished retrieving. The solution was to use the then keyword and change it to something like this:
asyncRetrieveDataFunction().then(data => this.setState({state1: data}));
This ensures that the data has been returned BEFORE assigning it to a state.

updating the state in componentWillMount

i want to create a object with multiple object. the data is something like this
dataList = [{inputFieldId: 1, dataField:{...}, data: '120'}, {inputFieldId: 2, dataField:{...}, data: '120'} ]
what is want like this.
res = [{1: '120'}, {2: '120'}]
i write a code for this but its giving me the last object data only.
constructor(){
super()
this.state = {
inputValue:{},
datalist = [],
}
}
componentDidMount() {
console.log(this.state.inputValue)
this.props.navigation.setParams({ sendDataToServer:
this._sendDataToServer });
}
async componentWillMount(){
for(var key in dataList){
this.setState({
inputValue: {
...this.state.inputValue,
[dataList[key].inputFieldId]: dataList[key].data
}
})
}
}
code output = { 2: '120'}
thanks in advance
setState work asynchronously. Instead of this
this.setState({
inputValue: {
...this.state.inputValue,
[dataList[key].inputFieldId]: dataList[key].data
}
})
Try to change to this
this.setState((previousState) => ({
inputValue: {
...previousState.inputValue,
[dataList[key].inputFieldId]: dataList[key].data
}
}))

Vue.js with iView Table - accessing Elasticsearch search response object

I am using the iView UI kit table in my Vue.js application that consumes an Elasticsearch API with axios. My problem is that I just can't seem to get to access the nested search response object, which is an array list object. I only get to access the 1st level fields, but not the nested ones. I don't know how to set the table row key in the iView table.
This is how my axios call and mapper methods look like:
listObjects(pageNumber){
const self = this
self.$Loading.start()
self.axios.get("/api/elasticsearch/")
.then(response => {
self.ajaxTableData = self.mapObjectToArray(response.data);
self.dataCount = self.ajaxTableData.length;
if(self.ajaxTableData.length < self.pageSize){
self.tableData = self.ajaxTableData;
} else {
self.tableData = self.ajaxTableData.slice(0,self.pageSize);
}
self.$Loading.finish()
})
.catch(e => {
self.tableData = []
self.$Loading.error()
self.errors.push(e)
})
},
mapObjectToArray(data){
var mappedData = Object.keys(data).map(key => {
return data[key];
})
return mappedData
},
The iView table columns look like this:
tableColumns: [
{
title: 'Study Date',
key: 'patientStudy.studyDate',
width: 140,
sortable: true,
sortType: 'desc'
},
{
title: 'Modality',
key: "generalSeries.modality",
width: 140,
sortable: true
},
...
]
The (raw) Elasticsearch documents look like this:
[
{ "score":1, "id":"3a710fa2c1b3f6125fc168c9308531b59e21d6b3",
"type":"dicom", "nestedIdentity":null, "version":-1, "fields":{
"highlightFields":{
},
"sortValues":[
],
"matchedQueries":[
],
"explanation":null,
"shard":null,
"index":"dicomdata",
"clusterAlias":null,
"sourceAsMap":{
"generalSeries":[
{
"seriesInstanceUid":"999.999.2.19960619.163000.1",
"modality":"MR",
"studyInstanceUid":"999.999.2.19960619.163000",
"seriesNumber":"1"
}
],
"patientStudy":[
{
"studyDate":"19990608"
}
]
}
}
]
And this is how the consumed object looks like:
As you can see, the fields I need to access are within the "sourceAsMap" object, and then nested in arrays.
How can I provide the iView table cell key to access them?
UPDATE:
I now "remapped" my Elasticsearch object before displaying it in the Vue.js table, and it works now. However, I don't think that the way I did it is very elegant or clean....maybe you can help me to do it in a better way. This is my method to remap the object:
getData(data){
let jsonMapped = []
for(let i = 0; i < data.length; i++){
let id = {}
id['id'] = data[i].id
let generalData = data[i]['sourceAsMap']['generalData'][0]
let generalSeries = data[i]['sourceAsMap']['generalSeries'][0]
let generalImage = data[i]['sourceAsMap']['generalImage'][0]
let generalEquipment = data[i]['sourceAsMap']['generalEquipment'][0]
let patient = data[i]['sourceAsMap']['patient'][0]
let patientStudy = data[i]['sourceAsMap']['patientStudy'][0]
let contrastBolus = data[i]['sourceAsMap']['contrastBolus'][0]
let specimen = data[i]['sourceAsMap']['specimen'][0]
jsonMapped[i] = Object.assign({}, id, generalData, generalSeries, generalImage, generalEquipment, patient,
patientStudy, contrastBolus, specimen)
}
return jsonMapped
},
The result is this:
Even though it now works, but how can I optimize this code?
A few functions can help you with your situation
let data = [{
key1: ['k1'],
key2: ['k2'],
key3: [{
subKey1: 'sk1',
subKey2: ['sk2'],
subObject: [{ name: 'John', surname: 'Doe' }],
items: [1, 2, 3, 5, 7]
}]
}];
function mapIt(data) {
if (isSingletonArray(data)) {
data = data[0];
}
for(const key in data) {
if (isSingletonArray(data[key])) {
data[key] = mapIt(data[key][0]);
} else {
data[key] = data[key];
}
}
return data;
}
function isSingletonArray(obj) {
return typeof obj === 'object' && Array.isArray(obj) && obj.length === 1;
}
console.log(mapIt(data));
Outputs:
{
key1: 'k1',
key2: 'k2',
key3: {
subKey1: 'sk1',
subKey2: 'sk2',
subObject: { name: 'John', surname: 'Doe' },
items: [ 1, 2, 3, 5, 7 ]
}
}
You can check it in your browser. mapIt unwraps the singleton arrays into objects or primitives.
But I recommend you to watch out special elastic client libraries for javascript. It could save you from writing extra code.

lodash transform object with array to array of objects

Using a lodash, I want to transform an object that contains array into array of objects. here is an example:
Original object :
[
{
name:"name1"
params: [{param: "value1"}, {param: "value2"}]
},
{
name:"name2"
params: [{param: "value3"}, {param: "value4"}]
}
]
After transformation:
[
{name:"name1", param: "value1"},
{name:"name1", param: "value2"},
{name:"name2", param: "value3"},
{name:"name2", param: "value4"}
]
Whats the easiest way to achieve this ? Thanks
[EDIT]
Till now I implemented the function below, but I'm almost sure there must be more elegant solution for my problem.
transform (res) {
const data = [];
_.each(res, (obj) => {
const params = _.pick(obj, ['params']);
const withoutParams = _.omit(obj, 'params');
_.each(params.params, (param) => {
data.push(_.assign(param, withoutParams));
});
});
console.log('data', data);
return data
}
You can _.map() the params and the name of each object into an array of objects, and then _.flatMap() all objects arrays into one array:
var arr = [
{
name:"name1",
params: [{param: "value1"}, {param: "value2"}]
},
{
name:"name2",
params: [{param: "value3"}, {param: "value4"}]
}
];
var newArr = _.flatMap(arr, function(obj) {
return _.map(obj.params, function(param) {
return {
name: obj.name,
param: param.param
};
});
});
console.log(newArr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
And this is the ES6 version using Array.prototype.map(), arrow functions, and destructuring:
const arr = [
{
name:"name1",
params: [{param: "value1"}, {param: "value2"}]
},
{
name:"name2",
params: [{param: "value3"}, {param: "value4"}]
}
];
const newArr = _.flatMap(arr, ({
name, params
}) => params.map(({
param
}) => ({
name, param
})));
console.log(newArr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>