问题
I am trying to sum up test scores, my data looks like this
{ testResult:
{ foo:
{foo1: x},
{foo2: y}
},
{ bar:
{bar1: z}
'foo' and 'bar' are meaningful subcategories of questions, child-nodes hold questionIDs and corresponding scores. I need to sum up scores ordered by categories. I query the list like this
let res = this._dbref.database.list('/bmzi-answers',{
query: {
orderByKey: true,
equalTo: id
}
})
and displaying the result with {{ res | async | json }}
shows me, that the query works. I get something like
[{"cat1":3, "cat2":3, "cat3":3, "$key":"cat"}]
How can I reduce the values in my component logic, to get the sum of all questions labeled cat* ?
I understand concepts like filter / map / reduce, but I am new to Reactive Javascript, Angular2 and AngularFire.
Please help and thank you in advance!
Edit: All children have the cat prefix, this is part of the domain / the questionnaire. This code
let res = this.dbRef.database.list(`/bmzi-answers/${id}`)
.do((array) => { console.log(JSON.stringify(array)); })
.map((array) => array
.reduce((acc, element) => acc + element.$value, 0)
)
let score = res.subscribe(sum => {
console.log("score: " + sum)
});
return score;
produces this console output, the first time it's called:
[
{ "$key": "cat1", "$value": 3 },
{ "$key": "cat2", "$value": 3 },
{ "$key": "cat3", "$value": 3 }
]
score: [object Object]
and the second time this:
[]
score: undefined
[{"$value":3,"$key":"fitges1"}]
score: undefined
[{"$value":3,"$key":"fitges1"},{"$value":3,"$key":"fitges2"}]
score: undefined
[{"$value":3,"$key":"fitges1"},{"$value":3,"$key":"fitges2"},{"$value":3,"$key":"fitges3"}]
score: undefined
回答1:
Your example output suggests the following data:
{
"bmzi-answers": {
"cat": {
"cat1": 3,
"cat2": 3,
"cat3": 3
}
}
}
Your query uses orderByKey
and equalTo
to obtain a list observable that emits an array. Provided the key exists, the query will only ever return an array containing a single object. It would be simpler to incorporate the key in the query's path, like this:
let res = this._dbref.database.list(`/bmzi-answers/${id}`);
The observable returned by this query will be an array containing the key's children (represented in the AngularFire way):
[
{ "$key": "cat1", "$value": 3 },
{ "$key": "cat2", "$value": 3 },
{ "$key": "cat3", "$value": 3 }
]
The RxJS map
operator can be used to transform the emitted array to another value and the transformation can be performed using the Array.proptype
functions with which you state you are familiar:
let id = "cat";
let regExp = new RegExp(`${id}\\d+`);
let res = this._dbref.database
.list(`/bmzi-answers/${id}`)
.map((array) => array
.filter((element) => regExp.test(element.$key))
.reduce((acc, element) => acc + element.$value, 0)
);
If all children have the cat
prefix, the filter
is not required, but your question doesn't make clear whether or not that's the case.
回答2:
A stream emits data. An observable is a stream.
For example:
let stream = Rx.Observable.from([3,6,9])
This code creates an observable stream which emits 3 then 6 then 9
Now lets say we wanted to double those values:
let stream = Rx.Observable.from([3,6,9]).map((value) => {return value*2})
Now the stream emits 6, 12, 18.
Finally we could do:
let stream1 = Rx.Observable.from([3,6,9])
let stream2 = stream1.map((value) => {return value*2})
Now we can subscribe to stream1 or stream2 or both.
PS a .reduce()
function is also available the same as the es5 Array.reduce
function. You can do the same with streams as you can with Arrays.
An array is an in memory sequence. A stream is a sequence over time.
This is why we can easily convert an array to a stream. We could put intervals between each emit. etc
来源:https://stackoverflow.com/questions/41044645/how-to-map-and-reduce-child-values-from-firebaselistobservable