Ramda: Split a list to segments - ramda.js

If we have a list such as:
[
{
type: 'a',
},
{
type: 'a',
},
{
type: 'b',
},
{
type: 'a',
}
]
... and we want to segment it to create a list, such that the new list is made up of each segment of the initial list, here split by type, looking like:
[
[
{
type: 'a',
},
{
type: 'a',
},
],
[
{
type: 'b',
},
],
[
{
type: 'a',
}
]
]
I'd like to create a general purpose 'segmenting' function, which takes a function to compare two items, and determine whether or not a new segment is required. Here, the 'segmenter' for that function simply compares type.
I can write that in vanilla javascript, but is there a good way to do this with Ramda?
const data = [
{
type: 'a',
},
{
type: 'a',
},
{
type: 'b',
},
{
type: 'a',
}
];
const segmentBy = segmenter => items => {
const segmentReducer = (prev = [], curr) => {
let lastSegment = [];
let lastItem = null;
try {
lastSegment = prev[prev.length - 1];
lastItem = lastSegment[lastSegment.length - 1];
} catch (e) {
return [...prev, [curr]];
}
const requiresNewSegment = segmenter(lastItem, curr);
if (requiresNewSegment) {
return [...prev, [curr]];
}
return [...prev.slice(0, prev.length - 1), [...lastSegment, curr]];
};
return items.reduce(segmentReducer, []);
};
const segmentByType = segmentBy((a, b) => a.type !== b.type);
const segments = segmentByType(data);
console.dir(segments);

With Ramda you can use R.groupWith:
Takes a list and returns a list of lists where each sublist's elements
are all satisfied pairwise comparison according to the provided
function. Only adjacent elements are passed to the comparison
function.
const data = [{"type":"a"},{"type":"a"},{"type":"b"},{"type":"a"}];
const segmentByType = R.groupWith(R.eqBy(R.prop('type')));
const segments = segmentByType(data);
console.dir(segments);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
In vanilla, the main problem is when to add a new subarray to the accumulator. You need to add another subarray when it's the 1st item, or if segmenter returns true.
const data = [{"type":"a"},{"type":"a"},{"type":"b"},{"type":"a"}];
const segmentBy = segmenter => items =>
items.reduce((r, item, i, arr) => {
if(i === 0 || segmenter(item, arr[i - 1])) r.push([]);
r[r.length - 1].push(item);
return r;
}, []);
const segmentByType = segmentBy((a, b) => a.type !== b.type);
const segments = segmentByType(data);
console.dir(segments);

Related

Error: Exception in HostFunction: Attempting to create an object of type 'sets' with an existing primary key value '6' in react native

I'm trying to store history of workout in realm, my addHistory function looks like this
export function addHistory(workout, exercise, sets, _id) {
console.log({
workout,
exercise,
sets,
_id,
});
if (
_id !== undefined &&
workout !== undefined &&
exercise !== undefined &&
sets !== undefined
) {
// return console.log("HISTORY ", { workout, exercise, sets, _id });
return realm.write(() => {
return realm.create("workoutData", {
_id: _id,
exercise,
workout,
sets,
workoutDate: new Date(Date.now()),
});
});
} else {
alert("History is incomplete");
}
}
Schema of the workoutData is as follows:
exports.workoutData = {
name: "workoutData",
primaryKey: "_id",
properties: {
_id: "int",
workout: "workouts",
exercise: "exercise",
workoutDate: "date",
sets: "sets[]",
},
};
Now when I add sets and click on finishWorkoutHandler the logic works fine before the addHistory function but when addHistory is executed it throws the error as stated in the question.
//finish workout handler
const finishWorkoutHandler = () => {
if (sets.length == 0) {
return;
}
let setsFromRealm = realm.objects("sets");
let workoutData = realm.objects("workoutData");
let setsArray = [];
exercises.forEach((exercise) => {
sets
.filter((items) => items.exercise._id == exercise._id)
.forEach((sets) => {
let _id = 0;
if (setsFromRealm.length > 0) {
_id = realm.objects("sets").max("_id") + 1;
}
addSet(
sets.name,
parseInt(sets.weight),
parseInt(sets.reps),
parseInt(sets.rmValue),
sets.isHeighest,
sets.exercise,
_id,
sets.profile,
sets.failedSet,
sets.warmupSet,
sets.notes
);
let indiSet = {
name: sets.name,
weight: parseInt(sets.weight),
reps: parseInt(sets.reps),
rmValue: parseInt(sets.rmValue),
isHeighest: sets.isHeighest,
_id: _id,
profile: sets.profile,
failedSet: sets.failedSet,
warmupSet: sets.warmupSet,
notes: sets.notes,
createdDate: new Date(Date.now()),
};
setsArray.push(indiSet);
});
let workoutDataId = 0;
let setsArrcopy = setsArray;
console.log("SETS ", realm.objects("sets"));
console.log("SETS ", setsArrcopy);
if (workoutData.length > 0) {
workoutDataId = realm.objects("workoutData").max("_id") + 1;
}
**WORKING AS EXPECTED TILL HERE**
// problem lies here
addHistory(params.workout, exercise, setsArrcopy, workoutDataId);
});
dispatch(setsEx([]));
goBack();
};
the structure of setsArrCopy containing sets is as follows
[
({
_id: 6,
createdDate: 2022-09-29T16:27:06.128Z,
failedSet: false,
isHeighest: false,
name: "Thai",
notes: "",
profile: [Object],
reps: 12,
rmValue: 64,
warmupSet: false,
weight: 56,
},
{
_id: 7,
createdDate: 2022-09-29T16:27:06.151Z,
failedSet: false,
isHeighest: false,
name: "Thsi 3",
notes: "",
profile: [Object],
reps: 10,
rmValue: 75,
warmupSet: false,
weight: 66,
})
];
the logic is also working fine in terms of assigning new ids to the sets being added in a loop. But somehow its throwing error when passing setArrCopy to addHistory function. Although its an array of sets not a single object?

Filter array based on each letter in the search string

Take the code below which works fine:
function Test() {
const txt = 'a'
const fruit = [
{ name: 'apple' },
{ name: 'orange' },
{ name: 'kiwi' },
{ name: 'banana' },
{ name: 'lemon' },
]
return (
<FlatList
data={fruit.filter((item) => String(item.name).includes(txt))}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
)
}
export default Test
The search string (txt) is a so the output would be:
apple
orange
banana
So far so good, now I want to be able to type for example ae and get all the items in the array that include both a and e in any order, not just ae together. here is the expected result for const txt = 'ae':
apple
orange
You can do it like this
function Test() {
const txt = 'a'
const fruit = [
{ name: 'apple' },
{ name: 'orange' },
{ name: 'kiwi' },
{ name: 'banana' },
{ name: 'lemon' },
]
const searchFinal = () => {
const newSplitSearch = txt.split("");
const finalArray = []
fruit.map((data) => {
const nameData = data.name;
let shouldBeAdded = true;
for (let i = 0; i < newSplitSearch.length; i++) {
if (nameData.includes(newSplitSearch[i])) {
} else {
shouldBeAdded = false;
}
}
if (shouldBeAdded) {
finalArray.push(data)
}
})
return finalArray
}
return (
<FlatList
data={searchFinal()}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
)
}
export default Test
Hope it helps :)
One way is to split the word characters and then on the filter check all of them with array.prototype.every
const txt = 'ae'
const fruit = [
{ name: 'apple' },
{ name: 'orange' },
{ name: 'kiwi' },
{ name: 'banana' },
{ name: 'lemon' },
]
const search = (arr, str) => {
const chars = str.split('');
return arr.filter(
item => chars.every(char => item.name.includes(char)) );
}
console.log(search(fruit, txt) )
Unfortunately the .includes() method of the String object just accepts a string-type parameter to search for.
What you can do however is using a regular expression instead.
Most basically this would be:
console.log("apple".match(/[a|e]/g));
The above would look for the letters a and e and return an array with the results found for apple ["a", "e"]. Problem is it would also return something if there is three times a and no e like in banana.
To overcome this issue we need to clean the returned array from duplicates first and afterwards check if the number of elements is at least the number of unique letters we want to look for - which is 2 in case of a and e.
Here's an example:
const fruit = [{
name: 'apple'
},
{
name: 'orange'
},
{
name: 'kiwi'
},
{
name: 'banana'
},
{
name: 'lemon'
},
]
let txt = "ae";
let regex = new RegExp("[" + txt.split("").join("|") + "]", "g");
fruit.forEach(item => {
console.log(item.name, [...new Set(item.name.match(regex))].length >= txt.length);
})

Is it possible to update a ion-picker along with its values?

I have asked a similar question about this before, but now the main problem has changed:
I have an ion-picker with two rows, and the values of the 2nd row change completely depending on what you choose in the 1st row. Now I can change the values that are received when an option is selected, but the picker doesn't update, so even though other values are being used, the old ones are still being displayed, and that's a pretty big problem.
In the answer I received in the earlier question, I was told that there's a function called forceUpdate(), but when I tried it, it changed nothing.
I was told that this could still be in development and maybe even get removed in the near future, but I need to know if there's a way to update the ion-picker now or not.
Here's the code for the ion-picker:
async showJetPicker(id) {
if (this.disabledis[id - 6] !== true) {
const opts: PickerOptions = {
cssClass: 'academy-picker',
buttons: [
...
],
columns: [
{
name: '1st row',
options: this.convertColumns(this.pickerbois[id].options[0])
},
{
name: '2nd row',
options: this.convertColumns(this.pickerbois[id].options[1])
}
]
};
const picker = await this.pickerCtrl.create(opts);
picker.addEventListener('ionPickerColChange', async (event: any) => {
const data = event.detail;
const colSelectedIndex = data.selectedIndex;
const colOptions = data.options;
if (colSelectedIndex < 2) {
picker.columns[1].options = this.convertColumns(this.pickerbois[id].options[colSelectedIndex + 1]);
picker.forceUpdate(); //Here it should update the picker
}
});
this.picker_cancer = picker;
picker.present();
}
}
please try below, which fixed mine.
async showJetPicker(id) {
if (this.disabledis[id - 6] !== true) {
const opts: PickerOptions = {
cssClass: 'academy-picker',
buttons: [
...
],
columns: [
{
name: '1st row',
options: this.convertColumns(this.pickerbois[id].options[0])
},
{
name: '2nd row',
options: this.convertColumns(this.pickerbois[id].options[1])
}
]
};
const picker = await this.pickerCtrl.create(opts);
picker.addEventListener('ionPickerColChange', async (event: any) => {
const data = event.detail;
const colSelectedIndex = data.selectedIndex;
const colOptions = data.options;
let temColumns;
if (colSelectedIndex < 2) {
//picker.columns[1].options = this.convertColumns(this.pickerbois[id].options[colSelectedIndex + 1]);
//picker.columns[1].options = this.convertColumns(this.pickerbois[id].options[colSelectedIndex + 1]);
temColumns = this.convertColumns(this.pickerbois[id].options[colSelectedIndex + 1]);
picker.columns[1].options = JSON.parse(JSON.stringify(temColumns));
picker.forceUpdate(); //Here it should update the picker
}
});
this.picker_cancer = picker;
picker.present();
}
}

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.

How to chain get and map the result with lodash?

I've got a list I'm trying to pull an object from using _.get but following that selection I need to loop over the object to create a new property. So far I've been successful using a combination of _.get and _.map as shown below but I'm hoping I can use _.chain in some way.
var selected = _.get(results, selectedId);
return _.map([selected], result => {
var reviews = result.reviews.map(review => {
var reviewed = review.userId === authenticatedUserId;
return _.extend({}, review, {reviewed: reviewed});
});
return _.extend({}, result, {reviews: reviews});
})[0];
Is it possible to do a transform like this using something other than map (as map required me to break this up/ creating an array with a solo item inside it). Thank you in advance!
I can see that you're creating unnecessary map() calls, you can simply reduce all those work into something like this:
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
The defaults() method is similar to extend() except once a property is set, additional values of the same property are ignored.
var selectedId = 1;
var authenticatedUserId = 1;
var results = {
1: [
{ userId: 1, text: 'hello' },
{ userId: 2, text: 'hey' },
{ userId: 1, text: 'world?' },
{ userId: 2, text: 'nah' },
]
};
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
document.body.innerHTML = '<pre>' + JSON.stringify(output, 0, 4) + '</pre>';
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>