React multidimensonal array filter/find - reactjs

I have a multidimensional array (list of matchs with players) and i need to filter this array to only get unique player object.
The following code works but the result i expected is different.
Const aPlayer=matchs
.filter(match => match.players.find(player => player.id
===id))
The variable aPlayer contains all matchs for this player.
But i only need player object data.

I'm not sure if your data structured like this:
Matches: List<Match>
Match: {
...,
players: List<Player>
}
Player: {
id: Index,
...
}
If so, you can do it like this:
// still contains duplicates, but that's not the issue here
const allPlayers = matchs
.map(x => x.players)
.reduce(x => (dest, val) => dest.concat(val), []);
const player = allPlayers.find(x => x.id === id);

Related

What is best way of finding array element intersection on rxjs

I know There is includes Operator on JavaScript Array.
So, Finding common elements from two other Arrays is no Problem. (https://stackoverflow.com/a/1885569/11455650)
Then, What is best way for this on rxjs?
const obs1$ = from(["foo", "bar", "baz", "qux"]);
const obs2$ = from(["bar", "garply", "fred", "foo"];
// const commonIntersection$ = functions or operators...
// result must be ["foo", "bar"]
I think there are two way for implementing this.
Which one is computationally efficient and How can I implement this with rxjs Operator?
merge Two Array and emit only Second value (ignore First Value)
filter each of elements
I assume you want a running intersection from each emission? If so you can either use the scan operator, or roll your own specific intersection operator.
The scan operator is like the reduce method on arrays. In this case, for each element (which is an array of strings) the intersection is returned. Each following emission will work on that last result.
merge(from([["foo", "bar", "baz", "qux"], ["bar", "garply", "fred", "foo"]])).pipe(
map(x => x),
scan((acc, cur) => {
return (!acc)
? next
: acc.filter(x => cur.includes(x));
}, undefined as string[] | undefined),
).subscribe(x => console.log(x));
A custom operator will look cleaner, so if you need it multiple times, then go for it! As you can see the logic below is essentially the same as the scan version. It keeps track of the running intersection state in the acc variable, and the current array is used to update it.
merge(from([["foo", "bar", "baz", "qux"], ["bar", "garply", "fred", "foo"]])).pipe(
map(x => x),
intersection(),
).subscribe(x => console.log(x));
/*...*/
function intersection<T>(): OperatorFunction<T[], T[]> {
return (observable: Observable<T[]>) => {
return new Observable<T[]>((subscriber) => {
let acc: T[] | undefined; // keeps track of the state.
const subscription = observable.subscribe({
next: (cur) => {
acc = (!acc)
? cur
: acc.filter(x => cur.includes(x));
subscriber.next(acc);
},
error: (err) => subscriber.error(err),
complete: () => subscriber.complete(),
});
return () => subscription.unsubscribe();
})
}
}
If you only want the last result then change the scan operator to reduce, or only emit the value of acc in the complete callback of the inner subscription in the operator version.
import { forkJoin, from } from 'rxjs';
import { toArray } from 'rxjs/operators';
import { intersection } from 'lodash';
const obs1$ = from(['foo', 'bar', 'baz', 'qux']);
const obs2$ = from(['bar', 'garply', 'fred', 'foo']);
forkJoin([obs1$.pipe(toArray()), obs2$.pipe(toArray())]).subscribe(
([arr1, arr2]) => console.log(intersection(arr1, arr2))
);
Try here: https://stackblitz.com/edit/rxjs-6jdyzy?file=index.ts
This is not rxjs used codes but used JavaScript Native Array methods (filter, includes).
It seems faster than rxjs operator.
const intersectionArray = combineLatest({
firstArray: [["foo", "bar", "baz", "qux"]],
secondArray: [["bar", "garply", "fred", "foo"]],
}).pipe(
map((x) => x.firstArray.filter((value) => x.secondArray.includes(value)))
);
intersectionArray.subscribe(console.log);

How to create array of all channels under a group of category IDs?

I've done what I believe produces an array of IDs of categories, and this is my code to try and use their IDs to return the keys of their children channels.
var filtered_category_ids = [""];
var filtered_category_ids = filtered_category_ids.reduce((acc, id) =>
acc.push(filtered_category_names.findKey((c) => c.name === name)));
var filtered_channel_ids = [];
const children = this.children;
filtered_category_ids.forEach(element => filtered_channel_ids.push((children.keyArray())));
console.log(filtered_channel_ids);
However, on running it, I get the TypeError "filtered_category_ids.forEach is not a function"
The second argument of Array.prototype.reduce is very important. It's the value that acc will take on at the beginning. If there is no second argument, it will take on the value of the first element in the array.
console.log([1, 2, 3].reduce((acc, curr) => acc + curr)) // acc starts as `1`
console.log([1, 2, 3].reduce((acc, curr) => acc + curr, 10)) // but what if we wanted it to start at `10`
This parameter is basically required when working with arrays, objects, etc.
console.log([1, 2, 3].reduce((acc, curr) => acc.push(curr))) // right now, `acc` is `1` (not an array; does not have the .push() method)
console.log([1, 2, 3].reduce((acc, curr) => acc.push(curr), [])); // start with an empty array
However, there is a second problem (as you can see from the above snippet). Array.prototype.push() actually returns the array's length, not the array itself.
console.log(
[1, 2, 3].reduce((acc, curr) => {
acc.push(curr); // push element to array
return acc; // but return the arr itself
}, [])
);
// second (simplified) method
console.log([1, 2, 3].reduce((acc, curr) => [...acc, curr], []))
You can also use Array.from(), which would be simpler in this situation.
console.log(Array.from([1, 2, 3], (num) => num);
There are a few other wonky things in your code, so here's what I suggest you do:
var filtered_category_names = ['']; // I think you might've meant `category_names` instead of `ids`
// you're trying to get IDs from names, so I'm not sure why you're reducing the ID array (before it's established)
// and using id as a parameter name when you use `name` in the actual function
var filtered_category_ids = Array.from(filtered_category_names, (name) =>
client.channels.cache.findKey((c) => c.name === name)
);
var filtered_channel_ids = [];
filtered_category_ids.forEach((id) =>
// filtered_channel_ids.push(children.keyArray()) // you're adding the same value to the array multiple times?
// get the category's children and add their IDs to the array
filtered.channel.ids.push(client.channels.cache.get(id).children.keyArray())
);
console.log(filtered_channel_ids);

I need to compare two arrays/state and get the data from the first array/state that doesn't match with the data on the second array - Reactjs

So I'm working with states in React, and i want to compare the two arrays/state and get the data from the first array that doesn't match with the data on the second array.
The first array is a state, and the second one is an array.
My first state/array looks like this = state : [ { id : id , productNumber : "productnumber"}... ] and the second array loos like this = array ["productnumber","productnumber"...],
and here what is my code's look like.
let newArray = [];
state.forEach(product=>{
array.forEach(productNumber=>{
if(product.productNumber !== productNumber){
newArray = [...newArray,product.productNumber];
}
})
})
and it doesn't work, it stores all the data from the first array/state and even doubled it. I tried using nested if statements, still doesn't. I don't know what todo.
let newArray = [];
state.forEach(({productNumber}) => {
if (array.indexOf(productNumber) === -1) {
newArray = [...newArray, productNumber];
}
})
Or similarly, you can filter the state array and return the products with productNumber not in your second array.
const newArray = state.filter(({productNumber}) => array.indexOf(productNumber) === -1);

Rxjs Scan method copies content of array property of Observable when new items added

In an Angular component I have a property of type BehaviourSubject<IController>:
controllerData$ = new BehaviourSubject<IController | null>(null);
IController {
users: IUser[];
showDetails: boolean;
}
I need to iterate over the users array of type IUser and execute an intermediate transformation over the property data before returning the results as array.
Below the code I use:
flatMap(): to flatten the IUsers[] items
map(): to iterate over the users and apply the intermediate processing
scan(): to pack the results into an array again
Below my code:
controllerData$
.pipe(
filter((input) => input !== null),
flatMap((p) => {
return p.users;
}),
map((m: IUser) => {
const data = {m,
date: this.getWeekDay(m.occurrence),
nextOccurrence: this.service.getNextDate(m.frequency, m.occurrence),
};
return data;
}),
scan((acc: any, cur) => [...acc, cur], [])
)
When the component loads the first time with two IUser items, everything works well. However when I add a new user to the array, the scan method copies also the previous array values, that is, having two initial items and adding a new one, I get 5 items in the array (the first two twice and the new one) instead of just three elements.
As a workaround I achieved it with the solution below, but I was wondering whether I can skip the foreach loop and obtain everything with just Rxjs operators:
.pipe(
filter((input) => input !== null),
map((m) => {
const results: any[] = [];
m.users.forEach((ctlr: IUser) => {
results.push({
user: ctlr.user,
date: this.getWeekDay(ctlr.occurrence),
nextOccurrence: this.service.getNextDateOccurrence(ctlr.frequency, ctlr.occurrence),
});
});
return results;
}),

ES6: Merge two arrays into an array of objects

I have two arrays that I want to merge together to one array of objects...
The first array is of dates (strings):
let metrodates = [
"2008-01",
"2008-02",
"2008-03",..ect
];
The second array is of numbers:
let figures = [
0,
0.555,
0.293,..ect
]
I want to merge them to make an object like this (so the array items match up by their similar index):
let metrodata = [
{data: 0, date: "2008-01"},
{data: 0.555, date: "2008-02"},
{data: 0.293, date: "2008-03"},..ect
];
So far I do this like so: I create an empty array and then loop through one of the first two arrays to get the index number (the first two arrays are the same length)... But is there an easier way (in ES6)?
let metrodata = [];
for(let index in metrodates){
metrodata.push({data: figures[index], date: metrodates[index]});
}
The easiest way is probably to use map and the index provided to the callback
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = metrodates.map((date,i) => ({date, data: figures[i]}));
console.log(output);
Another option is to make a generic zip function which collates your two input arrays into a single array. This is usually called a "zip" because it interlaces the inputs like teeth on a zipper.
const zip = ([x,...xs], [y,...ys]) => {
if (x === undefined || y === undefined)
return [];
else
return [[x,y], ...zip(xs, ys)];
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = zip(metrodates, figures).map(([date, data]) => ({date, data}));
console.log(output);
Another option is to make a generic map function which accepts more than one source array. The mapping function will receive one value from each source list. See Racket's map procedure for more examples of its use.
This answer might seem the most complicated but it is also the most versatile because it accepts any number of source array inputs.
const isEmpty = xs => xs.length === 0;
const head = ([x,...xs]) => x;
const tail = ([x,...xs]) => xs;
const map = (f, ...xxs) => {
let loop = (acc, xxs) => {
if (xxs.some(isEmpty))
return acc;
else
return loop([...acc, f(...xxs.map(head))], xxs.map(tail));
};
return loop([], xxs);
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = map(
(date, data) => ({date, data}),
metrodates,
figures
);
console.log(output);
If you use lodash, you can use zipWith + ES6 shorthand propery names + ES6 Arrow functions for a one-liner, otherwise see #noami's answer.
const metrodata = _.zipWith(figures, metrodates, (data, date)=> ({ data, date }));

Resources