Array of numbers gets converted to an array of key-value pairs; - javascript

I created this function in an Angular4 app:
enrollmentCheck() {
this.allCourses.forEach(course => {
this._courses.getCurrentEnrolment(course.slug).subscribe(res => {
if(res.length > 0){
this.enrolledCourses.push(res[0].course_id);
console.log(this.enrolledCourses);
}
})
});
console.log(this.enrolledCourses);
}
It is supposed to iterate through an array of objects and check if the user is enrolled to any of them.
The first bit works well, the subscribtion gives me the right data (res). I then need to store the property course_id into an array.
The first log (inside the loop), seems to work fine. I get
[1]
[1,2]
[1,2,5]
[1,2,5,7]
as outputs, one for each time the loop is executed.
Problem is that the second log (outside the loop), will output something like:
[
0: 1
1: 2
2: 5
3: 7
]
rather than
[1,2,5,7]
as I would like, for I will need to iterate through this array, and I cannot find a way to do it with the one I get.
Can anyone help? I apologise if this may seem a silly question to someone, but any help would be really appreciated.
Thanks,
M.

There are a few problems with your method. First of all you're creating subscriptions inside a loop, that's a bad idea because you're never completing them. Second you're doing asyc operations inside the loop therefore at the time the second console log appears the data might not be there yet.
A better solution would be to use Observable.forkJoin to wait for all async requests and then map the data.
For example
enrollmentCheck() {
Observable.forkJoin(
this.allCourses.map(course => {
return this._courses.getCurrentEnrollment(course.slug);
}
).map(res => {
return res
.filter(enrollment => enrollment.length > 0)
.map(enrollment => enrollment[0].course_id)
}).subscribe(data => console.log(data))
}

Related

Full array present in console, yet array.length = 0

I'm trying to understand why, when I assign the results from an axios call to a variable, console logging said variable will show the complete object, yet consoling its length returns zero.
As such, when I try to run a forEach on the results, there is no love to be had.
getNumberOfCollections() {
let results = queries.getTable("Quality"); // imported function to grab an Airtable table.
console.log(results); // full array, i.e. ['bing', 'bong', 'boom']
console.log(results.length); // 0
results.forEach((result) =>{ // no love });
}
It is quite likely that when you console.log the array, the array is still empty.
console.log(results); // full array, i.e. ['bing', 'bong', 'boom']
console.log(results.length); // 0
when console.log(results.length) is run, it is doing the console.log(0) and that's why 0 is printed out.
When console.log(results) is run, it is going to print out the results array later. That array is populated later when console.log() finally runs. (so console.log is not synchronous -- it will print something out a little bit later on.)
You can try
console.log(JSON.stringify(results));
and you are likely to see an empty array, because JSON.stringify(results) immediately evaluates what it is and make it into a string at that current time, not later.
It looks like you are fetching some data. The correct way usually is by a callback or a promise's fulfillment handler:
fetch(" some url here ")
.then(response => response.json())
.then(data => console.log(data));
so you won't have the data until the callback or the "fulfillment handler" is invoked. If you console.log(results.length) at that time, you should get the correct length. (and the data is there).

Nested Object.keys() are printing properties multiple times instead of only once

I have two objects that I need to loop through so I can use their properties later on. However if I print the variables each of them is printed twice. I understand that because I have the Object.keys() inside other Object.keys(). Is there any way to loop through these two objects and only get each variable one time?
My code:
Object.keys(newData.name).map(async key => {
Object.keys(newData._temp.images).map(async keyImage => {
console.log(newData.name[key].name,'printed 2x instead of once');
console.log(newData._temp.images[keyImage].rawFile.preview, 'printed 2x instead of once');
});
});
Thank you in advance.
your logic here of nesting the loops is wrong.
these 2 object does not seem to be connected to one another, meaning you do not need the data from the first loop in order to perform the other loops. just split it into 2 seperate loops, would save you both time and repititions:
let nameKeys = Object.keys(newData.name).map(key => newData.name[key].name);
let imagesKeys = Object.keys(newData._temp.images).map(keyImage =>
newData._temp.images[keyImage].rawFile.preview);
now you can access nameKeys and imageKeys whenever you want, and they will contain the values you previously logged. My naming might be a bit off tho, feel free to change that :D
Also, as others mentioned- no need for the async keyword... you do not perform any async operation inside (yet, at least. if thats what you're planning then go ahead and keep it).
These iterators do not need to be nested. The second iterator is not looping through an item of the first iterator.
Object.keys(newData.name).forEach(key => {
console.log(newData.name[key].name);
});
Object.keys(newData._temp.images).forEach(keyImage => {
console.log(keyImage[keyImage].rawFile.preview);
});
If you are only iterested in outputting data, then .map() is not the right function to use because this is used when you care about the return value. Use .forEach() if you just want to loop through things.
Also, the async keyword is not needed here.. unless you plan to do some async/await stuff in the loops later!
You could iterate over the indices once and then access the values in both arrays:
const names = Object.keys(newData.name);
const images = Object.keys(newData._temp.images);
for(let i = 0; i < Math.min(names.length, images.length); i++) {
const name = names[i];
const image = images[i];
//...
}

Why rxjs print data one single character every time?

This is my code in RxJs6:
const observable$ = interval(1000).pipe(
take(2),
map(x => interval(1500).pipe(
map(y => x+':'+y),
take(2),
concatAll()
)),
);
observable$.subscribe(obs => {
obs.subscribe(x => console.log(x));
});
I expect my code show the result like this:
0:0
1:0
0:1
1:1
But it actually shows:
why my code print data only one character every time ? And I think it should work like what i expected above not the actual result. anything wrong i understand about rxjs ?
This is because of concatAll(). It's typically used to flatten nested Observables but it can work with Promises and arrays (array-like objects) as well. Ant this is exactly what you're seeing here.
It thinks you want to flatten an array even when you have a string so it takes each item in the array (character in your case) and reemits it separately.
However, another question is what you wanted to achieve with concatAll.

Complex challenge about complexity and intersection

Preface
Notice: This question is about complexity. I use here a complex design pattern, which you don't need to understand in order to understand the question. I could have simplified it more, but I chose to keep it relatively untouched for the sake of preventing mistakes. The code is written in TypeScript which is a super-set of JavaScript.
The code
Regard the following class:
export class ConcreteFilter implements Filter {
interpret() {
// rows is a very large array
return (rows: ReportRow[], filterColumn: string) => {
return rows.filter(row => {
// I've hidden the implementation for simplicity,
// but it usually returns either an empty array or a very short one.
}
}).map(row => <string>row[filterColumn]);
}
}
}
It receives an array of report row, then it filters the array by some logic that I've hidden. Finally it does not return the whole row, but only one stringy column that is mentioned in filterColumn.
Now, take a look at the following function:
function interpretAnd (filters: Filter[]) {
return (rows: ReportRow[], filterColumn: string) => {
var runFilter = filters[0].interpret();
var intersectionResults = runFilter(rows, filterColumn);
for (var i=1; i<filters.length; i++) {
runFilter = filters[i].interpret();
var results = runFilter(rows, filterColumn);
intersectionResults = _.intersection(intersectionResults, results);
}
return intersectionResults;
}
}
It receives an array of filters, and returns a distinct array of all the "filterColumn"s that the filters returned.
In the for loop, I get the results (string array) from every filter, and then make an intersection operation.
The problem
The report row array is large so every runFilter operation is expensive (while on the other hand the filter array is pretty short). I want to iterate the report row array as fewer times as possible. Additionally, the runFilter operation is very likely to return zero results or very few.
Explanation
Let's say that I have 3 filters, and 1 billion report rows. the internal iterration, i.e. the iteration in ConcreteFilter, will happen 3 billion times, even if the first execution of runFilter returned 0 results, so I have 2 billion redundant iterations.
So, I could, for example, check if intersectionResults is empty in the beginning of every iteration, and if so, then break the loop. But I'm sure that there are better solutions mathematically.
Also if the first runFIlter exectuion returned say 15 results, I would expect the next exectuion to receive an array of only 15 report rows, meaning I want the intersection operation to influence the input of the next call to runFilter.
I can modify the report row array after each iteration, but I don't see how to do it in an efficient way that won't be even more expensive than now.
A good solution would be to remove the map operation, and then passing the already filtered array in each operation instead of the entire array, but I'm not allowed to do it because I must not change the results format of Filter interface.
My question
I'd like to get the best solution you could think of as well as an explanation.
Thanks a lot in advance to every one who would spend his time trying to help me.
Not sure how effective this will be, but here's one possible approach you can take. If you preprocess the rows by the filter column you'll have a way to retrieve the matched rows. If you typically have more than 2 filters then this approach may be more beneficial, however it will be more memory intensive. You could branch the approach depending on the number of filters. There may be some TS constructs that are more useful, not very familiar with it. There are some comments in the code below:
var map = {};
// Loop over every row, keep a map of rows with a particular filter value.
allRows.forEach(row => {
const v = row[filterColumn];
let items;
items = map[v] = map[v] || [];
items.push(row)
});
let rows = allRows;
filters.forEach(f => {
// Run the filter and return the unique set of matched strings
const matches = unique(f.execute(rows, filterColumn));
// For each of the matched strings, go and look up the remaining rows and concat them for the next filter.
rows = [].concat(...matches.reduce(m => map[v]));
});
// Loop over the rows that made it all the way through, extract the value and then unique() the collection
return unique(rows.map(row => row[filterColumn]));
Thinking about it some more, you could use a similar approach but just do it on a per filter basis:
let rows = allRows;
filters.forEach(f => {
const matches = f.execute(rows, filterColumn);
let map = {};
matches.forEach(m => {
map[m] = true;
});
rows = rows.filter(row => !!map[row[filterColumn]]);
});
return distinctify(rows.map(row => row[filterColumn]));

Creating a filterable list with RxJS

I'm trying to get into reactive programming. I use array-functions like map, filter and reduce all the time and love that I can do array manipulation without creating state.
As an exercise, I'm trying to create a filterable list with RxJS without introducing state variables. In the end it should work similar to this:
I would know how to accomplish this with naive JavaScript or AngularJS/ReactJS but I'm trying to do this with nothing but RxJS and without creating state variables:
var list = [
'John',
'Marie',
'Max',
'Eduard',
'Collin'
];
Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
.map(function(e) { return e.target.value; });
// i need to get the search value in here somehow:
Rx.Observable.from(list).filter(function() {});
Now how do I get the search value into my filter function on the observable that I created from my list?
Thanks a lot for your help!
You'll need to wrap the from(list) as it will need to restart the list observable again every time the filter is changed. Since that could happen a lot, you'll also probably want to prevent filtering when the filter is too short, or if there is another key stroke within a small time frame.
//This is a cold observable we'll go ahead and make this here
var reactiveList = Rx.Observable.from(list);
//This will actually perform our filtering
function filterList(filterValue) {
return reactiveList.filter(function(e) {
return /*do filtering with filterValue*/;
}).toArray();
}
var source = Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
.map(function(e) { return e.target.value;})
//The next two operators are primarily to stop us from filtering before
//the user is done typing or if the input is too small
.filter(function(value) { return value.length > 2; })
.debounce(750 /*ms*/)
//Cancel inflight operations if a new item comes in.
//Then flatten everything into one sequence
.flatMapLatest(filterList);
//Nothing will happen until you've subscribed
source.subscribe(function() {/*Do something with that list*/});
This is all adapted from one of the standard examples for RxJS here
You can create a new stream, that takes the list of people and the keyups stream, merge them and scans to filter the latter.
const keyup$ = Rx.Observable.fromEvent(_input, 'keyup')
.map(ev => ev.target.value)
.debounce(500);
const people$ = Rx.Observable.of(people)
.merge(keyup$)
.scan((list, value) => people.filter(item => item.includes(value)));
This way you will have:
-L------------------ people list
------k-----k--k---- keyups stream
-L----k-----k--k---- merged stream
Then you can scan it. As docs says:
Rx.Observable.prototype.scan(accumulator, [seed])
Applies an accumulator function over an observable sequence and returns each
intermediate result.
That means you will be able to filter the list, storing the new list on the accumulator.
Once you subscribe, the data will be the new list.
people$.subscribe(data => console.log(data) ); //this will print your filtered list on console
Hope it helps/was clear enough
You can look how I did it here:
https://github.com/erykpiast/autocompleted-select/
It's end to end solution, with grabbing user interactions and rendering filtered list to DOM.
You could take a look at WebRx's List-Projections as well.
Live-Demo
Disclosure: I am the author of the Framework.

Categories

Resources