Convert Array to Map Typescript - javascript

I'm trying to covert the following array to a Map:
const arr = [
{ key: 'user1', value: { num: 0, letter: 'a' } },
{ key: 'user2', value: { num: 0, letter: 'b' } },
{ key: 'user3', value: { num: 0, letter: 'c' } },
];
What I have so far:
const arr = [
{ key: 'user1', value: { num: 0, letter: 'a' } },
{ key: 'user2', value: { num: 0, letter: 'b' } },
{ key: 'user3', value: { num: 0, letter: 'c' } },
];
const b = arr.map(obj => [obj.key, obj.value]);
const map = new Map<string, { num: number; letter: string }>(b);
console.log(map.get('user1'));
Do you know if this is something achievable?
PS: You can find Typescript playground here and the error that I'm getting

obj.key is a string, and obj.value is a {num: number, letter: string}, so if you make an array of them ([obj.key, obj.value]), then you get an Array<string | {num: number, letter: string}>.
You need to tell TypeScript that you're creating a specific tuple, as expected by the Map constructor, not a generic array. There are a few ways of doing this:
// The simplest: Tell TypeScript "I mean exactly what I said."
// (This makes it readonly, which isn't always desirable.)
const b = arr.map(obj => [obj.key, obj.value] as const);
// Or be explicit in a return type. You can do this at a few levels.
const b = arr.map<[string, { num: number, letter: string}]>(obj => [obj.key, obj.value]);
const b = arr.map((obj): [string, { num: number, letter: string}] => [obj.key, obj.value]);
// Or, as explained in the comments, do it in one line, and TypeScript
// can infer tuple vs. array itself.
const map = new Map(arr.map(obj => [obj.key, obj.value]));

Related

Sort 2 objects with the same key Javascript

I have 2 objects where I want the second one to be in the same order as the first one.
Ex:
const obj1 = [
{ key: 1, id: 1, name: "John" },
{ key: 2, id: 2, name: "Ann" },
{ key: 3, id: 3, name: "Kate" }
];
const obj2 = [
{ key: 2, id: 2, name: "Ann" },
{ key: 1, id: 1, name: "John" },
{ key: 3, id: 3, name: "Kate" }
];
The purpose is to have obj2 in same order as obj1, but only sort with keys. I'm trying to make an helper function which will pass 3 argument:
function helper(obj1, obj2, key) {
// return new object with sorted array do not modify existing
}
I can sort one of this, but cant combine 2object together
My idea is to use hash map to store key obj2, then loop through key obj1 to sort obj2
const obj1 = [
{key: 1, id: 5, name: 'John'},
{key: 2, id: 5, name: 'John'},
{key: 3, id: 5, name: 'John'}
]
const obj2 = [
{key: 2, id: 5, name: 'John'},
{key: 1, id: 5, name: 'John'},
{key: 3, id: 5, name: 'John'}
]
function help(obj1, obj2, key) {
const hashKeyObj2 = obj2.reduce((val, item) => {
val[item[key]] = item;
return val;
}, {});
return obj1.map((item) => hashKeyObj2[item[key]]);
}
console.log(help(obj1, obj2, 'key'))
One-liner
let sorted = obj1.map(a => obj2.find(b => a.key === b.key))
This snippet will sort obj2 from obj1 order:
const obj1 = [
{ key: 1, id: 5, name: 'John' },
{ key: 3, id: 5, name: 'John' },
{ key: 2, id: 5, name: 'John' },
]
const obj2 = [
{ key: 2, id: 5, name: 'John' },
{ key: 1, id: 5, name: 'Jane' },
{ key: 3, id: 5, name: 'Tom' },
]
function helper(obj1, obj2, key) {
const findIndex = (refValue) => obj1.findIndex((candidate) => candidate[key] === refValue)
const comparator = (item1, item2) => findIndex(item1[key]) - findIndex(item2[key])
return [...obj2].sort(comparator)
}
const result = helper(obj1, obj2, 'key')
console.log('result :', result)
An Approach using lodash
function sortArrayBasedOnKeys(
array1: Record<string, number | string>[],
array2: Record<string, number | string>[],
key: string
) {
const order = _.map(array1, key);
return _.sortBy(array2, (item) => _.indexOf(order, item[key]));
}
First we are getting order in which objects in the array1 are placed and then using that order to sort the next array.

Typescript const with custom type as const

We have:
type test = {
id: number;
name: string;
};
const arr: test[] = [{ id: 1, name: 'test-1' }, { id: 2, name: 'test-2' }];
And I want to create a type from an array of objects:
type other = typeof arr[number]['name'];
const another: other = 'test-1'; // Should be 'test-1' | 'test-2' but it's string
If I remove the custom type of arr and use const assertion:
const arr = [{ id: 1, name: 'test-1' }, { id: 2, name: 'test-2' }] as const;
type other = typeof arr[number]['name'];
const another: other = 'test-2'; // Works!
It'll work, but I want to have a type for my arr too.
How can I create something like this:
type test = {
id: number;
name: string;
};
const arr: test[] = [
{ id: 1, name: 'test-1' },
{ id: 2, name: 'test-2' }
] as const;
type other = typeof arr[number]['name'];
const another: other = 'test-1';
I tried to use ReadonlyArray<test> but no luck!

Creating a reduced set and nested array within an array of objects

I'm trying to wrap my head around transforming an array of "flat" objects, into a condensed but nested version:
const startingArray = [
{ name: 'one', id: 100, thing: 1 },
{ name: 'one', id: 100, thing: 2 },
{ name: 'one', id: 100, thing: 4 },
{ name: 'two', id: 200, thing: 5 }
];
/*
desiredResult = [
{name: 'one', id:100, things: [
{thing: 1}, {thing: 2}, {thing:4}
]},
{name: 'two', id:200, things: [
{thing: 5}
]}
]
*/
// THIS DOES NOT WORK
const result = startingArray.reduce((acc, curr) => {
if (acc.name) {
acc.things.push(curr.thing)
}
return { name: curr.name, id: curr.id, things: [{thing: curr.thing}] };
}, {});
What am I not understanding?!
In your reduce callback, acc is not "each element of the array" (that's what curr is) or "the matching element in the results" (you have to determine this yourself), it's the accumulated object being transformed with each call to the function.
That is to say, when you return { name: curr.name, id: curr.id, things: [{thing: curr.thing}] };, it sets acc to that object for the next iteration, discarding whatever data was contained in it before - acc.name will only ever hold the last name iterated over and the results will never accumulate into anything meaningful.
What you want to do is accumulate the results in an array (because it's your desired output), making sure to return that array each iteration:
const startingArray = [
{ name: 'one', id: 100, thing: 1 },
{ name: 'one', id: 100, thing: 2 },
{ name: 'one', id: 100, thing: 4 },
{ name: 'two', id: 200, thing: 5 }
];
const result = startingArray.reduce((acc, curr) => {
let existing = acc.find(o => o.id == curr.id);
if(!existing) acc.push(existing = { name: curr.name, id: curr.id, things: [] });
existing.things.push({thing: curr.thing});
return acc;
}, []);
console.log(result);
Because of your desired result format this involves quite a few acc.find() calls, which is expensive - you can get around this in a concise way with this trick, using the first element of the accumulator array as a mapping of ids to references (also featuring ES6 destructuring):
const startingArray = [
{ name: 'one', id: 100, thing: 1 },
{ name: 'one', id: 100, thing: 2 },
{ name: 'one', id: 100, thing: 4 },
{ name: 'two', id: 200, thing: 5 }
];
const result = startingArray.reduce((acc, {name, id, thing}) => {
if(!acc[0][id])
acc.push(acc[0][id] = { name, id, things: [] });
acc[0][id].things.push({thing});
return acc;
}, [{}]).slice(1); //slice off the mapping as it's no longer needed
console.log(result);
Ok, providing alternative way. Key point is that you accumulate an Object and later take only the values, so get an Array:
const startingArray = [
{ name: 'one', id: 100, thing: 1 },
{ name: 'one', id: 100, thing: 2 },
{ name: 'one', id: 100, thing: 4 },
{ name: 'two', id: 200, thing: 5 }
];
const res = startingArray.reduce((acc, curr) => {
if (acc[curr.name]) {
acc[curr.name].things.push({thing: curr.thing})
} else {
acc[curr.name] = {
name: curr.name,
id: curr.id,
things: [{thing: curr.thing}]
}
}
return acc
}, {})
console.log(Object.values(res))

Ramda GroupBy - How to group by any prop

given:
interface Dict {
[key: string]: any
}
const data: Dict[] = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
where the keys of these Dict objects aren't specified...
How can I get this result?
const result = {
id: ['a', 'b', 'c', 'd', 'e'],
b: ['something', 'else'],
extra: ['hello world'],
// ... and any other possible key
}
You can flatten the object into a list of pairs, group it, and convert the pairs back to values:
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
let z = R.pipe(
R.chain(R.toPairs),
R.groupBy(R.head),
R.map(R.map(R.last))
)
console.log(z(data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Slight variation from Ori Drori answer:
(assuming that no properties in your objects are contained in arrays already)
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' }
];
const run = reduce(useWith(mergeWith(concat), [identity, map(of)]), {});
console.log(
run(data)
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {reduce, useWith, mergeWith, concat, identity, map, of} = R;</script>
Use R.reduce with R.mergeWith and concat all items:
const { mergeWith, reduce } = R
const fn = reduce(mergeWith((a, b) => [].concat(a, b)), {})
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
If you need the single value (extra) in array as well, map the items, and wrap with an array, just the values that are not an array already:
const { pipe, mergeWith, reduce, map, unless, is, of } = R
const fn = pipe(
reduce(mergeWith((a, b) => [].concat(a, b)), {}),
map(unless(is(Array), of))
)
const data = [
{ id: 'a' },
{ id: 'b', b: 'something' },
{ id: 'c', b: 'else' },
{ id: 'd', extra: 'hello world' },
{ id: 'e' },
];
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

add an object to an array if it doesn't have the same key with one of the objects in the array

I'm using Lodash. I have the array below:
const array = [{id:1,name:a},{id:2,name:b},{id:3,name:c},{id:4,name:d},{id:5,name:e}];
and I'm about to add another object to this array but before that, I need to check if the new object's name is already in the array or not and if there is one with the name I won't add the new object anymore.
I know some ways to do it, for instance, a loop with _.map, but want to make sure if there is an easier way.
You could use Lodash's some which if provided with an appropriate predicate e.g. (item => item.name === newName) will return a boolean indicating whether or not the item already exists (in this case, true would mean the name already exists). The benefit of using this over other iterating methods is that it will stop as soon as it finds one that returns true resulting in better performance.
With native javascript , you can use findIndex, this will return the index of the object where the name matches. If it returns -1 then there is no such object with same name. In that case update the array.
const array = [{
id: 1,
name: 'a'
}, {
id: 2,
name: 'b'
}, {
id: 3,
name: 'c'
}, {
id: 4,
name: 'd'
}, {
id: 5,
name: 'e'
}];
let newObjToAdd = {
id: 1,
name: 'z'
};
let newObjNotToAdd = {
id: 1,
name: 'a'
}
function updateArray(obj) {
let k = array.findIndex((item) => {
return item.name === obj.name;
})
if (k === -1) {
array.push(obj)
} else {
console.log('Array contains object with this name')
}
}
updateArray(newObjToAdd);
console.log(array)
updateArray(newObjNotToAdd);
You don't need lodash for some. You get that with native JS too (ES6):
const array = [{id:1,name:'a'},{id:2,name:'b'},{id:3,name:'c'},{id:4,name:'d'},{id:5,name:'e'}];
console.log(array.some(e => e.name === 'a'));
if (!array.some(e => e.name === 'z')) {
array.push({id: 5, name: 'z'});
}
console.log(array);
Doing this with lodash is few chars shorter but here is how you could do it with ES6 and Array.some:
const array = [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, { id: 4, name: "D" }, { id: 5, name: "C" }];
const maybeUpdate = (arr, obj) => {
if(!array.some(x => x.id == obj.id))
array.push(obj)
}
maybeUpdate(array, {id: 2, name: "F"}) // id exists wont insert
maybeUpdate(array, {id: 12, name: "F"}) // will insert
console.log(array)
Same idea with lodash and _.some would be:
const array = [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, { id: 4, name: "D" }, { id: 5, name: "C" }];
const maybeUpdate = (arr, obj) => {
if(!_.some(array, {id: obj.id}))
array.push(obj)
}
maybeUpdate(array, {id: 2, name: "F"}) // id exists wont insert
maybeUpdate(array, {id: 12, name: "F"}) // will insert
console.log(array)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Note that you could also use various other ways to get the same result. Array.find or _.find would work as well since all you have to do is to check if there was a hit:
const maybeUpdate = (arr, obj) => {
if(!_.find(array, {id: obj.id})) // or if(!array.find(x => x.id == obj.id))
array.push(obj)
}

Categories

Resources