If I have a immutable list like:
List([
Map({ something: 1 }),
Map({ something: 2 }),
])
How can I do set something = 5 where something = 1?
You can use map + set in order to achieve that
console.clear();
const list = Immutable.List([
Immutable.Map({ something: 1 }),
Immutable.Map({ something: 2 }),
Immutable.Map({ something: 4 }),
Immutable.Map({ something: 1 })
])
const newList = list.map(item =>
item.get("something") === 1 ? item.set("something", 5) : item
);
console.log('Old',JSON.stringify(list.toArray(),null, ""));
console.log('New',JSON.stringify(newList.toArray(),null, ""));
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.2/immutable.min.js"></script>
#Qop's answer will work great if you are looking for a specific key.
If you want to update multiple values in each Map, you could use .mapEntries() inside the list.map call.
things
.map(thing => {
return thing.mapEntries(([ k, v ]) => {
const val = (v === 5) ? 1 : v;
return [ k, val ]
})
});
here is a link to working plunk
Related
I am trying to convert a string like "sheep" into an object like this:
{
"s":{
"initial":1,
"final":1
},
"h":{
"initial":1,
"final":1
},
"e":{
"initial":2,
"final":2
},
"p":{
"initial":1,
"final":1
}
}
Currently I can use reduce method in javascript and achive this:
const names = 'sheep'.split('');
const count = (names) =>
names.reduce((acc, name) => ({ ...acc, [name]: (acc[name] || 0) + 1 }), {});
console.log(count(names)) //{ s: 1, h: 1, e: 2, p: 1 }
I have tried to read similar posts but I am pretty new to JS. Can anyone please help me? Thanks.
Try like this
const names = "sheep".split("");
const count = (names) =>
names.reduce(
(acc, name) => ({
...acc,
[name]: {
initial: (acc?.[name]?.initial ?? 0) + 1,
final: (acc?.[name]?.final ?? 0) + 1,
},
}),
{}
);
console.log(count(names));
This works printing your desired output but I'm not sure why you want the initial and final counts to be the same
const letters = 'sheep'.split('');
const count = (letters) =>
letters.reduce((obj, letter) => ({ ...obj, [letter]: {initial: (obj[letter]?.initial || 0) + 1, final: (obj[letter]?.final || 0) + 1} }), {});
console.log(count(letters))
Alternatively, you could expand a function there using braces and do some if's in order to have the code more readable instead of the ternaries
I have a request, which returns an array of objects. Each object includes an id, with which I send another request. Based on this result I want to filter the array. Simplified example:
function getAllObjects(): Observable<{ id: number }[]> {
return of([
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
]);
}
function checkObject(obj): Observable<boolean> {
return of(obj.id % 2 === 0);
}
getAllObjects().pipe(
// TODO
).subscribe(console.log); // I only want to see objects here which passed the async check
Does that work for you?
getAllObjects().pipe(
flatMap((ar) => ar),
concatMap((obj) => combineLatest([of(obj), checkObject(obj)])),
filter(([_, checkResult]) => checkResult),
map(([obj]) => obj),
toArray(),
).subscribe(console.log);
Edit, I see you already found a solution, mine isn't much simpler, and I thought you wanted a stream of objects rather than return them as an array. So I added toArray in my Edit.
A solution that may not be the simplest but it also works
getAllObjects()
.pipe(
switchMap(array =>
combineLatest(array
.map(obj =>
checkObject(obj)
.pipe(
distinctUntilChanged(),
map(boolean => boolean ? obj : null)
)
)
)
),
map(array => array.filter(obj => obj))
)
.subscribe(console.log);
Taking into account possible changes in real time
function getAllObjects(): Observable<{ id: number }[]> {
return timer(0, 10000)
.pipe(
map(() => [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
])
);
}
function checkObject(obj): Observable<boolean> {
return timer(1000, 5000)
.pipe(
map(() => obj.id % 2 === 0)
);
}
const objects$ = getAllObjects().pipe(concatAll());
const objectValidations$ = objects$.pipe(concatMap(checkObject));
zip(objects$, objectValidations$).pipe(
filter(([, validated]) => validated),
map(([obj,]) => obj),
toArray()
).subscribe(console.log);
UPDATE:
We can improve the above solution performance-wise by parallelizing the "checks":
getAllObjects().pipe(
concatAll(),
mergeMap(obj =>
checkObject(obj).pipe(
map(isValid => isValid? obj : undefined),
filter(Boolean),
)
),
toArray()
)
That's better because if we assume the following implementation of checkObject (added a delay):
function checkObject(obj) {
return of(obj.id % 2 === 0).pipe(delay(1000));
}
Then for n objects, the previous solution takes n seconds, as opposed to 1 second with the updated solution
import { map, filter } from 'rxjs/operators';
map(items => items.filter(item => item.id % 2 === 0)),
filter(items => items && items.length > 0)
First use the map function and filter the array like normal.
Then to make sure you don't get null or empty arrays, use the filter function which won't call the subscription if the map is null or empty.
To simplify the problem i have used numbers and strings here. The code:
const numbers$:Observable<number[]> = of([1,2,3]);
const strings: string[] = ["a","b"];
function getStrings(): Observable<string>[]{
return numbers$.pipe(
map((numbers: number[]) => {
const strings$: Observable<string>[] = strings.map(s => of(s));
return strings$;
}),
)
}
getStrings().subscribe(x => console.log(x))
The error i am getting is:
Type 'Observable<Observable<string>[]>' is missing the following properties from type 'Observable<string>[]
How can i get Observable<string>[] from getStrings function? I have tried to use flatMap, switchMap but unable to get the perfect combination.
Stackblitz
You'll need to use mergeMap() and forkJoin():
function getStrings(): Observable<string[]>{
return numbers$.pipe(
mergeMap((numbers: number[]) => {
const strings$: Observable<string>[] = strings.map(s => of(s));
return forkJoin(strings$);
}),
)
}
getStrings().subscribe(x => console.log(x))
https://stackblitz.com/edit/rxjs-dzvsbh?file=index.ts
So as I understand you want to "zip" two lists together, via observable ?
I can offer you this one
const numbers$: Observable<number[]> = of([1, 2, 3]);
const strings$: Observable<string[]> = of(['a', 'b']);
const combined$: Observable<any> = zip(numbers$, strings$)
.pipe(
map(([numbers, strings]) =>
numbers.length > strings.length ?
numbers.map((value, index) => [value, strings[index]]) :
strings.map((value, index) => [numbers[index], value]))
);
combined$.subscribe(value => console.log(value));
This will log:
[
[
1,
"a"
],
[
2,
"b"
],
[
3,
null
]
]
I would like to map one array of object into another in a more functional style, I am using typescript.
Basically I am using delete to remove a property on a object, I would like to know if there is a better way to write it.
const data = props.data.map(d => ({
order: d.position,
logs: d.batches.map(b => {
let log= {
amount: b.scrap,
batchNumber: '', // NO GOOD
}
if (!b.batch || b.batch.length === 0) {
delete log.batchNumber // NO GOOD
}
return log
}),
}))
example input data:
const data = [
position: 1,
batches: [
{batchNumber: '', ammount: 3}
]
]
result:
const data = [{
order: 1,
logs:[ {ammount:3}]
}
]
You can do another map on the batches to return a new array of objects, and attach that to your returned object instead:
const out = data.map(({ position: order, batches }) => {
const logs = batches.map(({ batchNumber, ammount }) => {
if (batchNumber) return { batchNumber, ammount };
return { ammount };
});
return { order, logs }
});
DEMO
One approach would be to make a shallow copy of the target omitting keys you want to delete, for example:
let drop = key => obj => Object.keys(obj).reduce((r, k) =>
k === key ? r : {...r, [k]: obj[k]}, {});
let test = [
{foo:11, bar:2, baz: 3},
{foo:22, bar:2, baz: 3},
{foo:33, bar:2, baz: 3},
];
console.log(test.map(drop('bar')));
To add another option to the mix: it is possible to use Object.assign to optionally assign the property:
const data = [{
position: 1,
batches: [{batchNumber: '',ammount: 3}, {batchNumber: 'withNr',ammount: 4}]
}];
const res = data.map(d =>
({
order: d.position,
logs : d.batches.map(({ammount, batchNumber}) => Object.assign({ammount}, batchNumber ? {batchNumber} : null ))
})
);
console.log(res);
I'm building a small application in Vuejs where I'm getting a response data and I'm mapping it to a variable, I've got few elements which has empty array, so while mapping I want to check the condition and map accordingly. Here is my code:
this.model = a.map(i => Object.assign({
'id': i.id,
'meeting_date': i.schedule,
'meeting_call': i.type,
'event_type': i.event_type,
'venue': i.venue,
'with_client': i.with_client
},{
if(i.meeting.meeting_summaries)
{
'meeting_summaries': i.meeting_summaries.map(ms => ({
client_name: ms.client_name,
nature: ms.nature,
action: ms.action,
mention: ms.user_id,
feedback: ms.feedback
}))
}
},
map is purely functional, it doesn't modify the elements instead return a newly formed array, so you can do like this:
this.model = a.map(i => {
var item = {}
item['id']= i.id,
item['meeting_date']= i.schedule,
item['meeting_call']= i.type,
item['event_type']= i.event_type,
item['venue']= i.venue,
item['with_client']= i.with_client
if(i.meeting && i.meeting.meeting_summaries) {
item['meeting_summaries']= i.meeting.meeting_summaries.map(ms =>({
client_name: ms.client_name,
nature: ms.nature,
action: ms.action,
mention: ms.user_id,
feedback: ms.feedback
}))
}else {
item['meeting_summaries'] = []
}
return item
}
In your case you can just swap to ternary expression:
this.model = a.map(i => Object.assign({
'id': i.id,
'meeting_date': i.schedule,
'meeting_call': i.type,
'event_type': i.event_type,
'venue': i.venue,
'with_client': i.with_client
}, (i.meeting.meeting_summaries) ? { // if condition is met
'meeting_summaries': i.meeting_summaries.map(ms => ({
client_name: ms.client_name,
nature: ms.nature,
action: ms.action,
mention: ms.user_id,
feedback: ms.feedback
}))
} : {} // otherwise don't do anything
The idea is the following:
const a = [1, 2, 3, 4, 5];
const b = a.map(number => Object.assign({a: number},
(number > 2) ? {b: ++number} : {}, // add 'b' for numbers > 2
(number % 2 === 0) ? {c: number + ' is even'} : {} // add 'c' for even numbers
))
console.log(b)