Rxjs GroupBy, Reduce in order to Pivot on ID - rxjs

I'm looking for a bit of help understanding this example taken from the rxjs docs.
Observable.of<Obj>({id: 1, name: 'aze1'},
{id: 2, name: 'sf2'},
{id: 2, name: 'dg2'},
{id: 1, name: 'erg1'},
{id: 1, name: 'df1'},
{id: 2, name: 'sfqfb2'},
{id: 3, name: 'qfs1'},
{id: 2, name: 'qsgqsfg2'}
)
.groupBy(p => p.id, p => p.name)
.flatMap( (group$) => group$.reduce((acc, cur) => [...acc, cur], ["" + group$.key]))
.map(arr => ({'id': parseInt(arr[0]), 'values': arr.slice(1)}))
.subscribe(p => console.log(p));
So the aim here is to group all the items by id and produce an object with a single ID and a values property which includes all the emitted names with matching IDs.
The second parameter to the groupBy operator identifies the return value. Effectively filtering the emitted object's properties down to the name. I suppose the same thing could be achieved by mapping the observable beforehand. Is it possible to pass more than one value to the return value parameter?
The line I am finding very confusing is this one:
.flatMap( (group$) => group$.reduce((acc, cur) => [...acc, cur], ["" + group$.key]))
I get that we now have three grouped observables (for the 3 ids) that are effectively arrays of emitted objects. With each grouped observable the aim of this code is to reduce it an array, where the first entry in the array is the key and subsequent entries in the array are the names.
But why is the reduce function initialized with ["" + group$.key], rather than just [group$.key]?
And why is this three dot notation [...acc, cur] used when returning the reduced array on each iteration?

But why is the reduce function initialized with ["" + group$.key], rather than just [group$.key]?
The clue to answer this question is in the .map() function a bit further down in the code.
.map(arr => ({'id': parseInt(arr[0]), 'values': arr.slice(1)}))
^^^^^^^^
Note the use parseInt. Without the "" + in the flatMap this simply wouldn't compile since you'd be passing a number type to a function that expects a string. Remove the parseInt and just use arr[0] and you can remove "" + as well.
And why is this three dot notation [...acc, cur] used when returning
the reduced array on each iteration?
The spread operator here is used to add to the array without mutating the array. But what does it do? It will copy the original array, take all the existing elements out of the array, and deposit the elements in the new array. In simpler words, take all elements in acc, copy them to a new array with cur in the end. Here is a nice blog post about object mutation in general.

Related

How can i mutate a list in elixir which I am iterating using Enum.map ? or need opinion on using nested recursion

I have two lists in elixir. One list (list1) has values which get consumed in another list (list2). So I need to iterate over list2 and update values in list1 as well as list2.
list1 = [
%{
reg_no: 10,
to_assign: 100,
allocated: 50
},
%{
reg_no: 11,
to_assign: 100,
allocated: 30
},
%{
reg_no: 12,
to_assign: 100,
allocated: 20
}
]
list2 = [
%{
student: student1,
quantity: 60,
reg_nos: [reg_no_10, reg_no_11]
},
%{
student: student2,
quantity: 40,
reg_nos: [reg_no_11, reg_no_12]
},
%{
student: student3,
quantity: 30,
reg_nos: nil
}
]
I need to assign values from list1 to quantity field of list2 till quantity is fulfilled. e.g. student1 quantity is 60 which will need reg_no 10 and reg_no 11.
With Enum.map I cannot pass updated list1 for 2nd iteration of list2 and assign value reg_nos: reg_no_11, reg_no_12for student2.
So, my question is how can I send updated list1 to 2nd iteration in list2?
I am using recursion to get quantity correct for each element in list2. But again, should I use recursion only to send updated list1 in list2? With this approach, there will be 2 nested recursions. Is that a good approach?
If I understand your question correctly, you want to change values in a given list x, based on a list of values in another list y.
What you describe is not possible in a functional language due to immutability, but you can use a reduce operation where x is the state or so-called "accumulator".
Below is an example where I have a ledger with bank accounts, and a list with transactions. If I want to update the ledger based on the transactions I need to reduce over the transactions and update the ledger per transaction, and pass the updated ledger on to the next transaction. This is the problem you are seeing as well.
As you can see in the example, in contrast to map you have a second parameter in the user-defined function (ledger). This is the "state" you build up while traversing the list of transactions. Each time you process a transaction you have a change to return a modified version of the state. This state is then used to process the second transaction, which in turn can change it as well.
The final result of a reduce call is the accumulator. In this case, the updated ledger.
def example do
# A ledger, where we assume the id's are unique!
ledger = [%{:id => 1, :amount => 100}, %{:id => 2, :amount => 50}]
transactions = [{:transaction, 1, 2, 10}]
transactions
|> Enum.reduce(ledger, fn transaction, ledger ->
{:transaction, from, to, amount} = transaction
# Update the ledger.
Enum.map(ledger, fn entry ->
cond do
entry.id == from -> %{entry | amount: entry.amount - amount}
entry.id == to -> %{entry | amount: entry.amount + amount}
end
end)
end)
end

Rxjs6 - filter array of objects

I would filter array of objects using RXJS operator filter
I've array of objects like this single one:
{
id: string,
count: number
}
I would get objects which count > 20
I tried:
getVotes2(): Observable<Vote> {
return this._http.get<Vote>(url)
.pipe(
map( results => results ),
filter( result => result.count>20 )
);
}
next, without map and I always get all records.
Any ideas?
---------CORRECT CODE------------
getVotes2(): Observable<Vote[]> {
return this._http.get<Vote[]>(url)
.pipe(
map( results => results.filter( r => r.count < 20) )
)
}
You're confused on the use of the rx filter operator.
The filter rx operator is NOT the same as the array filter operator. The rx filter operates on a stream and excludes things from THE STREAM that meet a condition, the array filter operator operates on an array and removes items from an array based on a condition.
What you're currently doing is filtering the stream on some undefined "count" property of the array itself, so you're saying "if undefined > 20, then don't let the item through the stream", and one of the quirks of javascript, undefined is not greater than 20 despite being an invalid comparison.
What you need to do is this:
getVotes2(): Observable<Vote[]> {
return this._http.get<Vote[]>(url)
.pipe(
map( results => results.filter(r => r.count > 20) )
);
}
This way, you use rx Map to perform an operation on the item IN the stream and use the array filter on the item to filter the array.
Edit: as pointed out, the typing also needs to be correct to let typescript know that you're expecting an array of vote objects rather than a single vote object.
If http response you are getting is something like
{
data: {
results: [ {id: 'dd5144s', count: 14}, {id: 'dd51s4s', count: 22}, {id: 'dd5sa44s', count: 8} ]
}
}
Then try this:
return this._http.get<Vote>(url)
.pipe(
switchMap( results => results ),
filter( result => result.count>20 )
);
Hope this helps.

Bar Chart on Dimension-1 and Stacked by Dimension-2

Summary
I want to display a bar chart whose dimension is days and is stacked by a different category (i.e. x-axis = days and stack = category-1). I can do this "manually" in that I can write if-then's to zero or display the quantity, but I'm wondering if there's a systematic way to do this.
JSFiddle https://jsfiddle.net/wostoj/rum53tn2/
Details
I have data with dates, quantities, and other classifiers. For the purpose of this question I can simplify it to this:
data = [
{day: 1, cat: 'a', quantity: 25},
{day: 1, cat: 'b', quantity: 15},
{day: 1, cat: 'b', quantity: 10},
{day: 2, cat: 'a', quantity: 90},
{day: 2, cat: 'a', quantity: 45},
{day: 2, cat: 'b', quantity: 15},
]
I can set up a bar chart, by day, that shows total units and I can manually add the stacks for 'a' and 'b' as follows.
var dayDim = xf.dimension(_ => _.day);
var bar = dc.barChart("#chart");
bar
.dimension(dayDim)
.group(dayDim.group().reduceSum(
_ => _.cat === 'a' ? _.quantity : 0
))
.stack(dayDim.group().reduceSum(
_ => _.cat === 'b' ? _.quantity : 0
));
However, this is easy when my data has only 2 categories, but I'm wondering how I'd scale this to 10 or an unknown number of categories. I'd imagine the pseudo-code I'm trying to do is something like
dc.barChart("#chart")
.dimension(xf.dimension(_ => _.day))
.stackDim(xf.dimension(_ => _.cat))
.stackGroup(xf.dimension(_ => _.cat).group().reduceSum(_ => _.quantity));
I mentioned this in my answer to your other question, but why not expand on it a little bit here.
In the dc.js FAQ there is a standard pattern for custom reductions to reduce more than one value at once.
Say that you have a field named type which determines which type of value is in the row, and the value is in a field named value (in your case these are cat and quantity). Then
var group = dimension.group().reduce(
function(p, v) { // add
p[v.type] = (p[v.type] || 0) + v.value;
return p;
},
function(p, v) { // remove
p[v.type] -= v.value;
return p;
},
function() { // initial
return {};
});
will reduce all the rows for each bin to an object where the keys are the types and the values are the sum of values with that type.
The way this works is that when crossfilter encounters a new key, it first uses the "initial" function to produce a new value. Here that value is an empty object.
Then for each row it encounters which falls into the bin labelled with that key, it calls the "add" function. p is the previous value of the bin, and v is the current row. Since we started with a blank object, we have to make sure we initialize each value; (p[v.type] || 0) will make sure that we start from 0 instead of undefined, because undefined + 1 is NaN and we hate NaNs.
We don't have to be as careful in the "remove" function, because the only way a row will be removed from a bin is if it was once added to it, so there must be a number in p[v.type].
Now that each bin contains an object with all the reduced values, the stack mixin has helpful extra parameters for .group() and .stack() which allow us to specify the name of the group/stack, and the accessor.
For example, if we want to pull items a and b from the objects for our stacks, we can use:
.group(group, 'a', kv => kv.value.a)
.stack(group, 'b', kv => kv.value.b)
It's not as convenient as it could be, but you can use these techniques to add stacks to a chart programmatically (see source).

combineAll does not emit on empty array

JSBIN Sample
I have a changeable set of child components (POJO object) that each have its own state stream. Each time a user triggers addChild/removeChild/clearChildren, a new set of children state streams is emitted with #switchMap. So far so good! (And so amazed by RxJS!)
With Rx.Observable.from(arrayOfStateStreams).combineAll() I get a good result as long as the arrayOfStateStreams isn't an empty array.
Since this is a partial state that is combined(Latest) on a higher level, I need to get an empty array emitted or the global state tree will contain old state data that is no longer true!
I can emit some reserved token like ['EMPTY-ARRAY-PLACEHOLDER-TOKEN'], but that's just weird.
A better way would be to always append one last stream into the array so the last index can be considered trash. Still confusing code and state though.
Using [null] is not OK, since we could have a child state of 'null'.
Anyone who can solve this in a good way? Can't this be supported since there should be no other representation of an empty array after #combineAll?
Credits go to github user trxcllnt who provided the following answer:
combineAll won't emit unless the combined Observables emit at least
one value, but you could check to ensure the collection you're
combining is empty or not, and either combine or emit an empty Array:
var arrayOfStreamsStream = Rx.Observable
.of(
[], [
Rx.Observable.of('blah-1'), // component state.
Rx.Observable.of('blah-2'),
Rx.Observable.of('blah-3')
], [], [
Rx.Observable.of('foo-1'),
Rx.Observable.of('qux-2')
]
)
.switchMap(function onMap(coll) {
return coll.length === 0 ?
Observable.of(coll) :
Observable.combineLatest(...coll);
})
.subscribe(function onSubscribe(data) {
console.log('onSubscribe START')
console.dir(data)
console.log('onSubscribe END')
})
This has nothing to do with combineAll. The problem is that Observable.from results in nothing (an empty observable) when passed an empty array.
The only viable solution that I can think of if you have to get a result from an empty array is to return something else in that case.
Ann example to illustrate the problem and a possible solution.
var data = [1, 2, 3, 4, 5];
log('With data: ');
Rx.Observable.from(data)
.subscribe(function (d) { log('data: ' + d); });
// Prints:
// With data:
// data: 1
// data: 2
// data: 3
// data: 4
// data: 5
var data = [];
log('Without data: ');
var nullDataObject = { msg: 'my null data object' };
Rx.Observable.from(data.length == 0 ? [nullDataObject] : data)
.subscribe(function (d) { log('data: ' + d); });
// Prints:
// With data:
// data: [object Object]
Runnable example on jsfiddle.
When consuming this you simply filter away the object representing an empty array where appropriate.
a possible workaround is to just pipe it with startWith();
combineLatest(potentiallyEmptyArray).pipe(
startWith<any>([])
);
Note: Similar issues exist with combineLatest() (the static version) which can be solved using defaultIfEmpty() - which works, but it screws up the typing of the output.
// array of Observables
const animals: Observable<{ species: 'dog' | 'cat' }>[] = [];
// Type '{ species: "dog" | "cat"; }[]' is not assignable to type 'never[]'.
combineLatest(animals).pipe(defaultIfEmpty([]));
In TypeScript you need to either know the type of the object or use <any>[] which means you then lose typing completely.
If you have a concrete type you can use one of these:
defaultIfEmpty<Animal[]>([])
defaultIfEmpty([] as Animal[])
I often don't have a concrete type for the return value of an observable. So I came up with an operator:
export const emptyArrayIfEmpty = () => <T>(observable: Observable<T[]>) =>
observable.pipe(defaultIfEmpty([] as T[]));
Then I can add the following and get out an empty array if animals === [] without losing any typing information:
combineLatest(animals).pipe(emptyArrayIfEmpty());

RxJS modeling if else control structures with Observables operators

Is it possible to model if/else control structures via RxJS operators. As far as I understood we could use Observable.filter() to simulate an IF branch, but I am not sure if we simulate an ELSE branch via any of the Observable operator.
There are a couple operators that you could use to emulate this:
In order from most likely what you are asking for
partition
//Returns an array containing two Observables
//One whose elements pass the filter, and another whose elements don't
var items = observableSource.partition((x) => x % 2 == 0);
var evens = items[0];
var odds = items[1];
//Only even numbers
evens.subscribe();
//Only odd numbers
odds.subscribe();
// Using RxJS >= 6
const [evens, odds] = partition(observableSource, x => x % 2 == 0);
//Only even numbers
evens.subscribe();
//Only odd numbers
odds.subscribe();
groupBy
//Uses a key selector and equality comparer to generate an Observable of GroupedObservables
observableSource.groupBy((value) => value % 2, (value) => value)
.subscribe(groupedObservable => {
groupedObservable.subscribe(groupedObservable.key ? oddObserver : evenObserver);
});
if edit renamed to iif in v6
//Propagates one of the sources based on a particular condition
//!!Only one Observable will be subscribed to!!
Rx.Observable.if(() => value > 5, Rx.Observable.just(5), Rx.Observable.from([1,2, 3]))
// Using RxJS >= 6
iif(() => value > 5, of(5), from([1, 2, 3]))
case (Only available in RxJS 4)
//Similar to `if` but it takes an object and only propagates based on key matching
//It takes an optional argument if none of the items match
//!!Only one Observable will be subscribed to!!
Rx.Observable.case(() => "blah",
{
blah : //..Observable,
foo : //..Another Observable,
bar : //..Yet another
}, Rx.Observable.throw("Should have matched!"))

Resources