Apply delta values on nested fields - rethinkdb

Suppose I have record like this:
{
id: 1,
statistics: {
stat1: 1,
global: {
stat2: 3
},
stat111: 99
}
}
I want to make update on record with object:
{
statistics: {
stat1: 8,
global: {
stat2: 6
},
stat4: 3
}
}
And it should be added to current record as delta. So, the result record should looks like this:
{
id: 1,
statistics: {
stat1: 9,
global: {
stat2: 9
},
stat4: 3,
stat111: 99
}
}
Is it possible to make this with one query?

Do you want something generic or something specific?
Specific is easy, this is the generic case:
const updateValExpr = r.expr(updateVal);
const updateStats = (stats, val) => val
.keys()
.map(key => r.branch(
stats.hasFields(key),
[key, stats(key).add(val(key))],
[key, val(key)]
))
.coerceTo('object')
r.table(...)
.update(stats =>
updateStats(stats.without('global'), updateValExpr.without('global'))
.merge({ global: updateStats(stats('global'), updateValExpr('global'))
)
There might be some bugs here sincce it's untested but the solution key point is the updateStats function, the fact that you can get all the keys with .keys() and that coerceTo('object') transforms this array: [['a',1],['b',2]] to this object: { a: 1, b: 2 },
Edit:
You can do it recursively, although with limited stack (since you can't send recursive stacks directly, they resolve when the query is actually built:
function updateStats(stats, val, stack = 10) {
return stack === 0
? {}
: val
.keys()
.map(key => r.branch(
stats.hasFields(key).not(),
[key, val(key)],
stats(key).typeOf().eq('OBJECT'),
[key, updateStats(stats(key), val(key), stack - 1)],
[key, stats(key).add(val(key))]
)).coerceTo('object')
}
r.table(...).update(row => updateStats(row, r(updateVal)).run(conn)
// test in admin panel
updateStats(r({
id: 1,
statistics: {
stat1: 1,
global: {
stat2: 3
},
stat111: 99
}
}), r({
statistics: {
stat1: 8,
global: {
stat2: 6
},
stat4: 3
}
}))

Related

adding function to loops through

I need to search in a big json nested collection which have unique IDs recursively. The collection contains key values or nested arrays which contains keys. Keys can be anywhere in the object. Keys can be number or string.
Please note: Key values are unique if they are not in array. If they are in array, the key duplicates per items in array. For example,
"WebData": {
WA1: 3, //not in array so unique
WA3: 2, so unique
WA3: "NEO",
WebGroup : [
{ Web1: 1, //duplicate Web1
Web2: 2
},
{ Web1: 2, //duplicate Web2
Web2: 2
}]
}
What I want:
I will pass an array of keys in different variations for example
Not in Arrays: I will pass key return either their values or sum for example:
function(["WA1",""WA3", "RAE1"],"notsum")
If I pass (not sum)
["WA1",""WA3", "RAE1"]
and the operation is not "sum", it should return an array of their values from the collection
[3,2,1]
If I pass the same but operation is sum)
function(["WA1",""WA3", "RAE1"],"sum")
["WA1",""WA3", "RAE1"]
it should return sum from the collection
return 6
If in Array: If the value to search are in the array means they duplicate, then it should return me sum or their individual values again For example
["WEB1","Web2"]
. It could either return me,
[7,1] //Again total of 3+4, 0+1 //see in example
or
[[3,4],[0,1]] //Because values are duplicate and in array, just collect them
I need to do in an elegant way:
Full example of JSON:
{
version: "1.0"
submission : "editing"
"WebData": {
WA1: 3,
WA3: 2,
WA3: "NEO",
WebGroup : [
{ Web1: 3,
Web2: 0
},
{ Web1: 4,
Web2: 1
}]
},
"NonWebData": {
NWA1: 3,
NWA2: "INP",
NWA3: 2,
},
"FormInputs": {
FM11: 3,
FM12: 1,
FM13: 2,
"RawData" : {
"RawOverview": {
"RAE1" : 1,
"RAE2" : 1,
},
"RawGroups":[
{
"name": "A1",
"id": "1",
"data":{
"AD1": 'period',
"AD2": 2,
"AD3": 2,
"transfers": [
{
"type": "in",
"TT1": 1,
"TT2": 2,
},
{
"type": "out",
"TT1": 1,
"TT2": 2,
}
]
}
},
{
"name": "A2",
"id": "2",
"data":{
"AD1": 'period',
"AD2": 2,
"AD3": 2,
"transfers": [
{
"type": "in",
"TT1": 1,
"TT2": 2,
},
{
"type": "out",
"TT1": 1,
"TT2": 2,
}
]
}
}
]
},
"Other":
{ O1: 1,
O2: 2,
O3: "hello"
},
"AddedBy": "name"
"AddedDate": "11/02/2019"
}
I am not able to write a function here, which can do this for me, my code is simply searching in this array, and I loop through to find it, which is I am sure not the correct way.
My code is not elegant, and I am using somehow repetitive functions. This is just one snippet, to find out the keys in one level. I want only 1 or 2 functions to do all this
function Search(paramKey, formDataArray) {
var varParams = [];
for (var key in formDataArray) {
if (formDataArray.hasOwnProperty(key)) {
var val = formDataArray[key];
for (var ikey in val) {
if (val.hasOwnProperty(ikey)) {
if (ikey == paramKey)
varParams.push(val[ikey]);
}
}
}
}
return varParams;
}
One more test case if in Array: to Return only single array of values, without adding. (Update - I achieved this through editing the code following part)
notsumsingle: function (target, key, value) {
if (target[key] === undefined) {
target[key] = value;
return;
}
target.push(value);
},
"groupData": [
{
"A1G1": 1,
"A1G2": 22,
"AIG3": 4,
"AIG4": "Rob"
},
{
"A1G1": 1,
"A1G2": 41,
"AIG3": 3,
"AIG4": "John"
},
{
"A1G1": 1,
"A1G2": 3,
"AIG3": 1,
"AIG4": "Andy"
}
],
perform(["AIG2",""AIG4"], "notsum")
It is returning me
[
[
22,
41,
3
]
],
[
[
"",
"Ron",
"Andy"
]
]
Instead, can I add one more variation "SingleArray" like "sum" and "notsum" and get the result as single Array.
[
22,
41,
3
]
[
"",
"Ron",
"Andy"
]
4th one, I asked, is it possible the function intelligent enough to pick up the sum of arrays or sum of individual fields automatically. for example, in your example, you have used "sum" and "total" to identify that.
console.log(perform(["WA1", "WA3", "RAE1"], "total")); // 6
console.log(perform(["Web1", "Web2"], "sum")); // [7, 1]
Can the function, just use "sum" and returns single or array based on if it finds array, return [7,1] if not return 6
5th : I found an issue in the code, if the json collection is added this way
perform(["RAE1"], "notsum") //[[1,1]]
perform(["RAE1"], "sum") //2
It returns [1, 1], or 2 although there is only one RAE1 defined and please note it is not an array [] so it should not be encoded into [[]] array, just the object key
"RawData" : {
"RawOverview": {
"RAE1" : 1,
"RAE2" : 1,
}
For making it easier, and to take the same interface for getting sums or not sums and a total, without any array, you could introduce another operation string total for getting the sum of all keys.
This approach takes an object for getting a function which either add an value to an array at the same index or stores the value at an specified index, which match the given keys array of the function.
For iterating the object, you could take the key/value pairs and iterate until no more object is found.
As result, you get an array, or the total sum of all items.
BTW, the keys of an object are case sensitive, for example 'WEB1' does not match 'Web1'.
function perform(keys, operation) {
function visit(object) {
Object
.entries(object)
.forEach(([k, v]) => {
if (k in indices) return fn(result, indices[k], v);
if (v && typeof v === 'object') visit(v);
});
}
var result = [],
indices = Object.assign({}, ...keys.map((k, i) => ({ [k]: i }))),
fn = {
notsum: function (target, key, value) {
if (target[key] === undefined) {
target[key] = value;
return;
}
if (!Array.isArray(target[key])) {
target[key] = [target[key]];
}
target[key].push(value);
},
sum: function (target, key, value) {
target[key] = (target[key] || 0) + value;
}
}[operation === 'total' ? 'sum' : operation];
visit(data);
return operation === 'total'
? result.reduce((a, b) => a + b)
: result;
}
var data = { version: "1.0", submission: "editing", WebData: { WA1: 3, WA3: 2, WAX: "NEO", WebGroup: [{ Web1: 3, Web2: 0 }, { Web1: 4, Web2: 1 }] }, NonWebData: { NWA1: 3, NWA2: "INP", NWA3: 2 }, FormInputs: { FM11: 3, FM12: 1, FM13: 2 }, RawData: { RawOverview: { RAE1: 1, RAE2: 1 }, RawGroups: [{ name: "A1", id: "1", data: { AD1: 'period', AD2: 2, AD3: 2, transfers: [{ type: "in", TT1: 1, TT2: 2 }, { type: "out", TT1: 1, TT2: 2 }] } }, { name: "A2", id: "2", data: { AD1: 'period', AD2: 2, AD3: 2, transfers: [{ type: "in", TT1: 1, TT2: 2 }, { type: "out", TT1: 1, TT2: 2 }] } }] }, Other: { O1: 1, O2: 2, O3: "hello" }, AddedBy: "name", AddedDate: "11/02/2019" };
console.log(perform(["WA1", "WA3", "RAE1"], "notsum")); // [3, 2, 1]
console.log(perform(["WA1", "WA3", "RAE1"], "total")); // 6
console.log(perform(["Web1", "Web2"], "sum")); // [7, 1]
console.log(perform(["Web1", "Web2"], "notsum")); // [[3, 4], [0, 1]]
.as-console-wrapper { max-height: 100% !important; top: 0; }

Reduce returns empty array, however scan does not

Code:
const Rx = require('rxjs')
const data = [
{ name: 'Zachary', age: 21 },
{ name: 'John', age: 20 },
{ name: 'Louise', age: 14 },
{ name: 'Borg', age: 15 }
]
const dataSubj$ = new Rx.Subject()
function getDataStream() {
return dataSubj$.asObservable().startWith(data);
}
getDataStream()
.mergeMap(Rx.Observable.from)
.scan((arr, person) => {
arr.push(person)
return arr
}, [])
.subscribe(val => console.log('val: ', val));
Using .reduce(...) instead of .scan(...) returns an empty array and nothing is printed. The observer of dataSub$ should receive an array.
Why does scan allow elements of data to pass through, but reduce does not?
Note: I am using mergeMap because I will filter the elements of the array before reducing them back into a single array.
scan emits the accumulated value on every source item.
reduce emits only the last accumulated value. It waits until the source Observable is completed and only then emits the accumulated value.
In your case the source Observable, which relies on a subject, never completes. Thus, the reduce would never emit any value.
You may want to apply the reduce on the inner Observable of the mergeMap. For each array, the inner Observable would complete when all the array items are emitted:
const data = [
{ name: 'Zachary', age: 21 },
{ name: 'John', age: 20 },
{ name: 'Louise', age: 14 },
{ name: 'Borg', age: 15 }
]
const dataSubj$ = new Rx.Subject()
function getDataStream() {
return dataSubj$.asObservable().startWith(data);
}
getDataStream()
.mergeMap(arr => Rx.Observable.from(arr)
.reduce((agg, person) => {
agg.push(person)
return agg
}, [])
)
.subscribe(val => console.log('val: ', val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>

how to switch the "order" property of 2 items in this array/list?

class item {
name: string;
order: number;
}
let onUp = new Rx.Subject<item>();
let list = new Rx.BehaviorSubject<item>([
{ name: "7", order: 70 },
{ name: "2", order: 20 },
{ name: "5", order: 50 },
{ name: "3", order: 30 },
{ name: "4", order: 40 },
{ name: "6", order: 60 },
{ name: "1", order: 10 }
]);
list.subscribe(console.log);
onUp.subscribe(anItem => {
let numberList: item[];
list
.take(1)
.map(x => x.sort((a, b) => a.order - b.order))
.subscribe(ddList => (numberList = ddList));
let index = numberList.indexOf(numberList.find(num => num.order == anItem));
let ddvalue = numberList[index];
let preddvalue = numberList[index - 1];
let preddvalueOrder = preddvalue.order;
preddvalue.order = ddvalue.order;
ddvalue.order = preddvalue.order;
list.next(numberList);
});
onUp.next(30);
whats the reactive way to exchange values of "order" with another object from the list?
I have a bunch of items that I should be able to reorder.
The order of an item is based on the order property of the item object in the item list.
I reorder the items by switching the order property of two items.
In the sample code, the onUp Subject, emits an orderNumber which should be switched with the upper item(ordered by order property)
The sample code works, its just that its probably bad practice since the second subscribe is asynchronous.
An alternative is to do the reordering inside the subscription inside the outer subscription but that's also bad practice(not sure why aside from the fact that its a messy nest).
I always see flatMap but if I understand correctly the 2nd subscription just uses a data from the first subscription to get a data.
In this case I need both data from the first and second subject to get the two items to switch and the new list to push to list subject.
Ah.... Is maintaining 2 lists ok to you? One is the unsorted, the other one always listen to unsorted changes and sort it.
const list = new Rx.BehaviorSubject([
{ name: "7", order: 70 },
{ name: "2", order: 20 },
{ name: "5", order: 50 },
{ name: "3", order: 30 },
{ name: "4", order: 40 },
{ name: "6", order: 60 },
{ name: "1", order: 10 }
]);
const sortedList = new Rx.BehaviorSubject([]);
const onUp = new Rx.BehaviorSubject(0);
list
.map(x => x.sort((a, b) => a.order - b.order))
.subscribe(x => sortedList.next(x));
sortedList.subscribe(console.log);
onUp.subscribe(x => {
const idx = sortedList.getValue()
.findIndex(itm => itm.order === x);
if (idx < 1) return;
const list = sortedList.getValue();
const prev = list[idx - 1].order;
const curr = list[idx].order;
list[idx].order = prev;
list[idx - 1].order = curr;
sortedList.next(list);
});
onUp.next(30);
onUp.next(70);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
getValue() of BehaviorSubject is bad practice as well(says so in my source by Ben Lesh). readup on withLatestFrom or combineLatest for combining two observables.
class item {
name: string;
order: number;
}
let onUp = new Rx.Subject<item>();
let list = new Rx.BehaviorSubject<item[]>([
{ name: "7", order: 70 },
{ name: "2", order: 20 },
{ name: "5", order: 50 },
{ name: "3", order: 30 },
{ name: "4", order: 40 },
{ name: "6", order: 60 },
{ name: "1", order: 10 }
]);
list.subscribe(console.log);
// onUp.subscribe(anItem => {
onUp
.withLatestFrom(list, (item, itemList) => {
return { item: item, itemList: itemList };
})
.subscribe(itemWithList => {
let numberList: item[] = itemWithList.itemList.sort(
(a, b) => a.order - b.order
);
let orderToUp: number = itemWithList.item;
let index = numberList.findIndex(x => x.order === orderToUp);
let ddvalue = numberList[index];
let preddvalue = numberList[index - 1];
console.log(ddvalue);
let preddvalueOrder = preddvalue.order;
preddvalue.order = ddvalue.order;
ddvalue.order = preddvalueOrder;
list.next(numberList);
});
onUp.next(30);
Paste it here:
https://emeraldwalk.github.io/rxjs-playground/#
Source:
https://stackoverflow.com/a/45227115

How to use debounce stream based on value?

For example, assume that we have stream like following
Stream 1 | -1-2-3-1-2-3--4-----------
after debounce, I would like to have the emitted stream looks like as follows:
Stream 2 | ---------------1-2-3--4------
There are lots of examples how to debounce the stream, but they take all value as the same trigger.
The following is the example code I found in reactitve-extension website,
var Rx = require('rxjs/Rx');
var times = [
{ value: 1, time: 100 },
{ value: 2, time: 200 },
{ value: 3, time: 300 },
{ value: 1, time: 400 },
{ value: 2, time: 500 },
{ value: 3, time: 600 },
{ value: 4, time: 800 }
];
// Delay each item by time and project value;
var source = Rx.Observable.from(times)
.flatMap(function (item) {
return Rx.Observable
.of(item.value)
.delay(item.time);
})
.debounceTime(500 /* ms */);
var subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
The console output would be
Next: 4
Completed
But I would like to get the following output
Next: 1
Next: 2
Next: 3
Next: 4
Completed
Maxime give good answer.
I also try myself. Hope help someone who have the same question.
var Rx = require('rxjs/Rx');
var times = [
{ value: 1, time: 100 },
{ value: 2, time: 200 },
{ value: 3, time: 300 },
{ value: 1, time: 400 },
{ value: 2, time: 500 },
{ value: 3, time: 600 },
{ value: 4, time: 800 },
{ value: 5, time: 1500 }
];
// Delay each item by time and project value;
var source = Rx.Observable.from(times)
.flatMap(function (item) {
return Rx.Observable
.of(item.value)
.delay(item.time);
})
.do(obj => console.log('stream 1:', obj, 'at', Date.now() - startTime, `ms`))
.groupBy(obj => obj)
.flatMap(group => group.debounceTime(500))
let startTime = Date.now();
var subscription = source.subscribe(
function (x) {
console.log('stream 2: %s', x, 'at', Date.now() - startTime, 'ms');
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
The console will output
stream 1: 1 at 135 ms
stream 1: 2 at 206 ms
stream 1: 3 at 309 ms
stream 1: 1 at 409 ms
stream 1: 2 at 509 ms
stream 1: 3 at 607 ms
stream 1: 4 at 809 ms
stream 2: 1 at 911 ms
stream 2: 2 at 1015 ms
stream 2: 3 at 1109 ms
stream 2: 4 at 1310 ms
stream 1: 5 at 1510 ms
stream 2: 5 at 1512 ms
Completed
Here's the code I propose :
const { Observable } = Rx
const objs = [
{ value: 1, time: 100 },
{ value: 2, time: 200 },
{ value: 3, time: 300 },
{ value: 1, time: 400 },
{ value: 2, time: 500 },
{ value: 3, time: 600 },
{ value: 4, time: 800 }
];
const tick$ = Observable.interval(100)
const objs$ = Observable.from(objs).zip(tick$).map(x => x[0])
objs$
.groupBy(obj => obj.value)
.mergeMap(group$ =>
group$
.debounceTime(500))
.do(obj => console.log(obj))
.subscribe()
And the output is just as expected :
Here's a working Plunkr with demo
https://plnkr.co/edit/rEI8odCrhp7GxmlcHglx?p=preview
Explanation :
I tried to make a small schema :
The thing is, you cannot use the debounceTime directly on the main observable (that's why you only had one value). You've got to group every values in their own stream with the groupBy operator and apply the debounceTime to the splitted group of values (as I tried to show in the image). Then use flatMap or mergeMap to get one final stream.
Doc :
Here are some pages that might help you understand :
- groupBy
- debounceTime
- mergeMap

mongoose sort by a function

I have a schema that looks like this
var user = new Schema({
preference1: { // preference is a number between 1 - 10
type: Number
},
preference2: { // preference is a number between 1 - 10
type: Number
}
})
how do I find the documents and sort by a function on the preferences fields? Say fn is something like this
fn = Math.abs(preference1 - 3) + preference2 ^ 2
I kind of find a temporary solution. It works but isn't really what I was looking for... the code is really messy and apparently you cannot take arbitrary fn for sorting..
for example, say fn = (a+3) * (b+5)
Media.aggregate()
.project({
"type": 1,
"status": 1,
"newField1": { "$add": [ "$type", 3 ] },
"newField2": { "$add": [ 5, "$status" ] },
})
.project({
"newField3": { "$multiply": [ "$newField1", "$newField2" ] },
})
.sort("newField3")
.exec()

Resources