Compare key values within object for duplicate updated - javascript

This is a follow up post from Compare key values within object for duplicate for a follow up answer.
I have an object:
myObj = {
attendent-0-id:"123",
attendent-0-name:"Bob Smith",
attendent-1-id:"1234",
attendent-1-name:"Alex Smith",
attendent-2-id:"123",
attendent-2-name:"Charlie Smith",
attendent-0-id:"123",
attendent-0-name:"John Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
Thanks to help on here (Rick) I was able to get 90% of the way there.
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
map[value] = [map[value], key];
} else {
map[value] = key
}
}
}
return map;
}
I am getting a return of:
array:[
0:(2) ["attendent-0-name", "attendent-1-name"]
1:"attendent-2-name"
]
but I am looking for:
array:[
0:(2) ["attendent-0-name", "attendent-1-name", "attendent-2-name"]
]
The issue I am having is that while this works if there are two matching keys it will not work (Correctly) if there are three or more.

If you want to have an array of all matches for each key in you map, you need to start by setting an array when you find a key the first time. On subsequent matches, just push into that array:
const myObj = {'attendent-0-id': "1234",'attendent-0-name': "Bob Smith",'attendent-1-id': "123",'attendent-1-name': "Alex Smith",'attendent-2-id': "123",'attendent-2-name': "Charlie Smith",'attendent-maxGuest': 1,'attendent-party-name': "",};
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
map[value].push(key); // push a new value
} else {
map[value] = [key] // set it to an array
}
}
}
/* if you only want lengths > 1
otherwise just return map */
let filtered = Object.entries(map)
.reduce((a, [key, value]) => value.length > 1 ? Object.assign(a, {[key]: value}) : a, {})
return filtered;
}
console.log(errorOnDuplicateIds(myObj));
If you are only interested in values with more than one hit, you can reduce() down to a map with only values of length greater than one, which is what the last bit in the snippet does.

Related

Trying to swap key value pairs of an object. Any advices?

Trying to swap key-value pairs of an object!
// an object through we have to iterate and swap the key value pairs
const product = {
id: "FRT34495",
price: 34.56,
nr: 34456,
};
// A function that actually swap them, but don't delete old properties
const change = () => {
for (let key in product) {
const x = key;
key = product[key];
product[key] = x;
}
return product;
};
console.log(change());
//
{
'34456': 'nr',
id: 'FRT34495',
price: 34.56,
nr: 34456,
FRT34495: 'id',
'34.56': 'price'
}
the problem is that I need the object with key-value pairs swapped, but in the same amount, not twice more as we can see above, I need to delete the old ones.
Any advice guys?
Most logically straight-forwarding solution:
Turn object into entries array ([[key1, val1], [key2, val2], ...]) using Object.entries
Swap each of the entries ([[val1, key1], [val2, key2], ...]) using map
Turn back into object using Object.fromEntries
function swapKV (obj) {
const entries = Object.entries(obj)
const swappedEntries = entries.map([k, v] => [v, k])
const swappedObj = Object.fromEntries(swappedEntries)
return swappedObj
}
...or more concise:
const swapKV = obj => Object.fromEntries(Object.entries(obj).map([k, v] => [v, k]))
(Of course, another solution would be to just add if (String(x) !== String(key)) delete product[x] to your code. The condition is there to avoid deleting the entry altogether in case key and value are equal when converted to a string.)
This may help!
const swapKV = (obj) => {
const newObj = {};
for (let val in obj) {
newObj[obj[val]] = val;
}
return newObj;
}
You can also try this:
const swap = (obj) => {
return Object.fromEntries(
Object.entries(obj)
.map((key) => key.reverse())`.concat(Object.entries(obj))`
);
};
If the original object is to be also added to the output, concat can be used. The user asks for a function which also keeps the old properties. Otherwise no need.

Convert array of string to nested Object

I have an array of string, I want to convert to nested object where key is value of array. I've try with reduce, but all of the value nested with the last object is last item from the array. Can you help me?
Thanks!
let m = [
'1.',
'1.1.',
'1.2.',
'1.3.',
'1.4.',
'1.1.1.',
'1.1.2.',
'1.1.3.',
'1.2.1.',
'1.2.2.',
'1.3.1.',
'1.3.2.',
'1.3.3.',
'1.3.4.',
'1.4.1.',
'1.4.3.',
];
I want to convert this array to nested object.
Return
{
"1":{
"1":{
"1":"1.1.1.",
"2":"1.1.2.",
"3":"1.1.3."
},
"2":{
"1":"1.2.1.",
"2":"1.2.2."
},
"3":{
"1":"1.3.1.",
"2":"1.3.2.",
"4":"1.3.4."
},
"4":{
"1":"1.4.1.",
"3":"1.4.3."
}
}
}
Here's a working example using reduce().
let m = [
'1.',
'1.1.',
'1.2.',
'1.3.',
'1.4.',
'1.1.1.',
'1.1.2.',
'1.1.3.',
'1.2.1.',
'1.2.2.',
'1.3.1.',
'1.3.2.',
'1.3.3.',
'1.3.4.',
'1.4.1.',
'1.4.3.',
];
const addToObj = (obj_, path, newData) => {
const obj = typeof obj_ === 'string' ? {} : obj_ // Special logic to cause a value at 1.2.3. to override a value at 1.2.
if (path.length === 0) return newData
const [head, ...tail] = path
return {
...obj,
[head]: addToObj(obj[head] || {}, tail, newData),
}
}
const res = m.reduce(
(obj, path) => addToObj(obj, path.split('.').slice(0, -1), path),
{}
)
console.log(res)
It works by using a addToObj function, that'll take an object as a parameter, a path into that object, and a new value wanted at the end of that path, and it'll return a new object with the new value added.
Special logic was added to addToObj() to make sure keys like 1.2.3. always overwrote a string value that might have been placed at 1.2..

How to fetch matching values from a dictionary object in Typescript?

I have a dictionary object which is populated as follows:
const myDictionaryElement = this.myDictionary["abc"];
Here, myDictionaryElement has the values:
ACheckStatus: "PASS"
QVVStatus: "READY"
VVQStatus: "READY"
QTTStatus: "READY"
QBCTTStatus: "READY"
CStatus: "FAIL"
I want to create a object such that all key-value pairs for which the key has matching VV in the middle should be stored in object valuesVV as following:
const valuesVV = { QVVStatus: "READY" };
Similary all key-value pairs for which the key has matching TT in the middle should be stored in object valuesTT as following:
const valuesTT = { QTTStatus: "READY", QBCTTStatus: "READY" } ;
And all key-value pairs for which the key doesn't have matching VV and TT in the middle should be stored in object valuesOther as following:
const valuesOther = { ACheckStatus: "PASS", VVQStatus: "READY", CStatus: "FAIL" } ;
To achieve the about output, I am using hasOwnProperty for dictionary but it is not working
const valuesVV = myDictionaryElement.hasOwnProperty('*VV*'); // It is returning boolean false. The desired output is { QVVStatus: "READY" }
You can generalize it by creating a function that takes the dict and a matches array like the following:
const dict = {
ACheckStatus: "PASS",
QVVStatus: "READY",
VVQStatus: "READY",
QTTStatus: "READY",
QBCTTStatus: "READY",
CStatus: "FAIL"
};
const matching = ['VV', 'TT', 'SS'];
function matchInput(input, matches) {
// will have matched object in the index of the match and those without a match
// at the end
const res = [];
Object.keys(input).forEach(key => {
// flag if the key wasn't matched to add it to no matches object
let matched = false;
matches.forEach((match, i) => {
if (key.indexOf(match) > 0) {
matched = true;
if (!res[i]) res[i] = {};
res[i][key] = input[key];
}
});
if (!matched) {
if (!res[matches.length]) res[matches.length] = {};
res[matches.length][key] = input[key];
}
});
return res;
}
The idea is to go over each key and insert it in the right bucket (object)
You should filter the object keys and select only the ones you need, then use reduce to create a new object:
const vvItems = Object.keys(dictElement)
.filter(key => key.indexOf('VV')>= 1)
.reduce((o, key) => { o[key] = dictElement[key]; return o; }, {})

Find an index of array item from another json array item

I am looking to find out an index and group the item belong to in a parent json group, how can I do it?
I am open to reformat the json as well if need be,
I tried JSON.stringify() but it returns the wrong index as well.
let Content = {
group1: [
[{content:"hello"},{content:"world"}],
[{content:"hello1"},{content:"world"}],
[{content:"hello2"},{content:"world"}],
[{content:"hello3"},{content:"world"}],
[{content:"hello4"},{content:"world"}],
[{content:"hello5"},{content:"world"}],
],
group2: [
[{content:"hello10"},{content:"world"}],
[{content:"hello11"},{content:"world"}],
[{content:"hello12"},{content:"world"}],
[{content:"hello13"},{content:"world"}],
[{content:"hello14"},{content:"world"}],
[{content:"hello15"},{content:"world"}],
],
};
// let currentItem = {type:'group2',index:5};
// let currentItemContent = Content[currentItem.type][currentItem.index];
let obj = [{content:"hello15"},{content:"world"}];
let newIndex = Content["group1"].indexOf(obj);
let type = "group1";
if(newIndex < 0)
{
type="group2"
console.log(Content["group2"]);
newIndex = Content["group2"].indexOf(obj);
}
console.log({"type":type,"index":newIndex});
expected: {type:'group2',index:5}
Loop through the Content object using for...in. Check if the given array is in each group by using findIndex. Since both the objects in the array seem to be in order, you can simply compare the string returned by JSON.stringify
let Content={group1:[[{content:"hello"},{content:"world"}],[{content:"hello1"},{content:"world"}],[{content:"hello2"},{content:"world"}],[{content:"hello3"},{content:"world"}],[{content:"hello4"},{content:"world"}],[{content:"hello5"},{content:"world"}]],group2:[[{content:"hello10"},{content:"world"}],[{content:"hello11"},{content:"world"}],[{content:"hello12"},{content:"world"}],[{content:"hello13"},{content:"world"}],[{content:"hello14"},{content:"world"}],[{content:"hello15"},{content:"world"}]]}
function find(input, search) {
for (const type in input) {
const group = input[type];
const index = group.findIndex(a => JSON.stringify(a) === JSON.stringify(search));
if (index != -1)
return { type, index }
}
return null
}
console.log(find(Content, [{content:"hello15"},{content:"world"}]))
console.log(find(Content, [{content:"hello"},{content:"world"}]))
You could also use Array.find in combination with Object.keys and Array.some. The array comparison you can do via JSON.stringify however remember that if your keys are in different order that would not work:
[{content:"world"},{content:"hello"}] vs [{content:"hello"},{content:"world"}]
would not match as you would expect since you are matching on strings and they are now different.
let Content = { group1: [ [{content:"hello"},{content:"world"}], [{content:"hello1"},{content:"world"}], [{content:"hello2"},{content:"world"}], [{content:"hello3"},{content:"world"}], [{content:"hello4"},{content:"world"}], [{content:"hello5"},{content:"world"}], ], group2: [ [{content:"hello10"},{content:"world"}], [{content:"hello11"},{content:"world"}], [{content:"hello12"},{content:"world"}], [{content:"hello13"},{content:"world"}], [{content:"hello14"},{content:"world"}], [{content:"hello15"},{content:"world"}], ], };
let findArray = (data, obj) => {
let index, group = Object.keys(data).find((k,i) => {
index = i
return data[k].some(x => JSON.stringify(x) === JSON.stringify(obj))
})
return { index, group }
}
console.log(findArray(Content, [{content:"hello"},{content:"world"}]))
console.log(findArray(Content, [{content:"hello10"},{content:"world"}]))

How can I get a key in a JavaScript 'Map' by its value?

I have a JavaScript 'Map' like this one
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
I want some method to return a key by its value.
let jhonKey = people.getKey('jhon'); // jhonKey should be '1'
You can use a for..of loop to loop directly over the map.entries and get the keys.
function getByValue(map, searchValue) {
for (let [key, value] of map.entries()) {
if (value === searchValue)
return key;
}
}
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
console.log(getByValue(people, 'jhon'))
console.log(getByValue(people, 'abdo'))
You could convert it to an array of entries (using [...people.entries()]) and search for it within that array.
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
let jhonKeys = [...people.entries()]
.filter(({ 1: v }) => v === 'jhon')
.map(([k]) => k);
console.log(jhonKeys); // if empty, no key found otherwise all found keys.
Though late and other great answers already exist, still you can give the below "..." and "Array.find" a try:
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
function getKey(value) {
return [...people].find(([key, val]) => val == value)[0]
}
console.log('Jasmein - ', getKey('jasmein'))
console.log('Jhon - ', getKey('jhon'))
JavaScript Map and Object
Given a JavaScript Map, I like Nitish's answer:
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
function getKey(val) {
return [...map].find(([key, value]) => val === value)[0];
}
console.log(getKey('one')); // 1
console.log(getKey('two')); // 2
console.log(getKey('three')); // 3
For a JavaScript object, you could do something like so:
const map = {
1: 'one',
2: 'two',
3: 'three',
};
function getKey(val) {
return Object.keys(map).find(key => map[key] === val);
}
console.log(getKey('one')); // 1
console.log(getKey('two')); // 2
console.log(getKey('three')); // 3
There isn't any direct method for picking out information in this direction, so if all you have is the map you need to loop through the set as suggested by others.
If the map/array/other is large enough that such a loop would be a performance issue and the requirement for a reverse lookup is common within the project, you could implement your own structure using a pair of maps/arrays/other with one as per the current object and the other with the key and value reversed.
That way, the reverse lookup is as efficient as the normal one. Of course, you have more work to do as you need to implement each method that you need as a pass-through to one or both of the underlying objects so if the map is small and/or the reverse lookup is not needed often the scan-via-loop option is likely to be preferable due to being simpler to maintain and possible simpler for the JiT compiler to optimise.
In any case, one thing to be wary of is the possibility that multiple keys could have the same value. If this is possible then when looping through your map you need to decide if you are fine to return one of the possible keys arbitrarily (probably the first one) or if you want to return an array of keys, and if implementing a reverse index for data that could have duplicate values the same issue also needs to be accounted for.
One could invert the Map so that the keys are the values and the values are the keys and then lookup the original value as a key. Here's an example:
let myMap = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let invertedMap = new Map([...myMap.entries()].map(
([key, value]) => ([value, key]))
);
console.log(invertedMap.get('one'))
// => 1
Here is a properly typed Typescript solution that doesn't unnecessarily create an array.
function find_map_value<K, V>(m: Map<K, V>, predicate: (v: V) => boolean): [K, V] | undefined {
for (const [k, v] of m) {
if (predicate(v)) {
return [k, v];
}
}
return undefined;
}
If you want all values you can use a generator:
function* find_all_map_values<K, V>(m: Map<K, V>, predicate: (v: V) => boolean): Generator<[K, V]> {
for (const [k, v] of m) {
if (predicate(v)) {
yield [k, v];
}
}
}
Why not simply make use of map's built in iterator prototype/instance reference looking for the target value? Injection into the prototype chain/polyfill inspired solution of sorts makes it universal to ones code:
Map.prototype.getKey = function(targetValue){
let iterator = this[Symbol.iterator]()
for (const [key, value] of iterator) {
if(value === targetValue)
return key;
}
}
const people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
const jhonKey = people.getKey('jhon');
console.log(`The key for 'jhon' is: ${jhonKey}`);
For anyone curious why I added yet another answer. Most of these answers (exception, I like Rajesh's answer, but I added to the prototype chain) are doing a lot of data duplication in the name of finding a value by using the spread operator or even straight up crafting Arrays. Object.keys() mind you is also terribly nonperformant.
Note, I use for..of which iterates on iterables. One could do short hand simply with for(const [key, value] of this){...} if desired.
Tailing off what Maciej Krawczyk suggested here is a general circular map implementation for that.
class ReferenceMap {
#left = new Map();
#right = new Map();
constructor(iterable = []) {
this.#left = new Map(iterable);
this.#right = new Map(ReferenceMap.swapKeyValues(iterable));
}
has(key) {
return this.#left.has(key) || this.#right.has(key);
}
get(key) {
return this.#left.has(key) ? this.#left.get(key) : this.#right.get(key);
}
set(key, value) {
this.#left.set(key, value);
this.#right.set(value, key);
}
delete(key) {
if (this.#left.has(key)) {
let ref = this.#left.get(key);
this.#left.delete(key);
this.#right.delete(ref);
} else if (this.#right.has(key)) {
let ref = this.#right.get(key);
this.#right.delete(key);
this.#left.delete(ref);
}
}
entries() {
return this.#left.entries();
}
keys() {
return this.#left.keys();
}
values() {
return this.#left.values();
}
[Symbol.iterator]() {
return this.entries();
}
get size() {
return this.#left.size;
}
static * swapKeyValues(entries) {
for (let [key, value] of entries) yield [value, key];
}
}
My TypeScript version:
const getByValue = <A, B>(m: Map<A,B>, searchValue: B):[A, B] | undefined => {
const l:IterableIterator<[A, B]> = m.entries();
const a:[A, B][] = Array.from(l);
return a.find(([_k,v]) => v === searchValue);
}
Cache
The question is a bit wrong because one value can be assigned to many keys. Therefore, the result for a given value should be an array of keys (not a single key). If you want to oftet make such search you can use following cache generator for reverse map
let genRevMapCache = map => [...map.entries()].reduce((a,[k,v]) => {
if(!a.get(v)) a.set(v,[]);
a.get(v).push(k);
return a;
}, new Map() );
let genRevMapCache = map => [...map.entries()].reduce((a,[k,v]) => {
if(!a.get(v)) a.set(v,[]);
a.get(v).push(k);
return a;
}, new Map() );
// TEST
let people = new Map();
people.set('1', 'jhon');
people.set('2', 'jasmein');
people.set('3', 'abdo');
people.set('4', 'jhon');
let cache = genRevMapCache(people);
console.log('jasmein', cache.get('jasmein'));
console.log('jhon', cache.get('jhon'));
JS:
// Returns keys for all instances
function findAll(obj) {
return Array.from(items.keys()).map(k => items.get(k) === obj ? k : undefined).filter(k => k);
}
// Returns keys for the first instances
function findFirst(obj) {
return Array.from(items.keys()).find(k => items.get(k) === obj);
}
Typescript:
protected items = new Map<TKey, TObject>();
public findAll(obj: TObject): Array<TKey> {
return Array.from(this.items.keys()).map(k => this.items.get(k) === obj ? k : undefined).filter(k => !!k);
}
public findFirst(obj: TObject): TKey | undefined {
return Array.from(this.items.keys()).find(k => this.items.get(k) === obj);
}
Explanation:
// Gets the keys as an array
Array.from(this.items.keys())
// Map the keys whose object matches the instance of `obj` to the key itself, undefined otherwise
.map(k => this.items.get(k) === obj ? k : undefined)
// Filter out array elements that are undefined
// (!! is for strict type-checking/readability practices, you can simply use k => k)
.filter(k => !!k)
// Finds the first occurrence of the key for the given object, undefined if not found
.find(k => this.items.get(k) === obj)

Categories

Resources