I want to reverse the mapping of an object (which might have duplicate values). Example:
const city2country = {
'Amsterdam': 'Netherlands',
'Rotterdam': 'Netherlands',
'Paris': 'France'
};
reverseMapping(city2country) Should output:
{
'Netherlands': ['Amsterdam', 'Rotterdam'],
'France': ['Paris']
}
I've come up with the following, naive solution:
const reverseMapping = (obj) => {
const reversed = {};
Object.keys(obj).forEach((key) => {
reversed[obj[key]] = reversed[obj[key]] || [];
reversed[obj[key]].push(key);
});
return reversed;
};
But I'm pretty sure there is a neater, shorter way, preferably prototyped so I could simply do:
const country2cities = city2country.reverse();
You could use Object.assign, while respecting the given array of the inserted values.
const city2country = { Amsterdam: 'Netherlands', Rotterdam: 'Netherlands', Paris: 'France' };
const reverseMapping = o => Object.keys(o).reduce((r, k) =>
Object.assign(r, { [o[k]]: (r[o[k]] || []).concat(k) }), {})
console.log(reverseMapping(city2country));
There is no such built-in function in JavaScript. Your code looks fine, but given that there are so many edge cases here that could wrong, I'd suggesting using invertBy from lodash, which does exactly what you describe.
Example
var object = { 'a': 1, 'b': 2, 'c': 1 };
_.invertBy(object);
// => { '1': ['a', 'c'], '2': ['b'] }
You can use something like this to get raid of duplicates first :
function removeDuplicates(arr, key) {
if (!(arr instanceof Array) || key && typeof key !== 'string') {
return false;
}
if (key && typeof key === 'string') {
return arr.filter((obj, index, arr) => {
return arr.map(mapObj => mapObj[key]).indexOf(obj[key]) === index;
});
} else {
return arr.filter(function(item, index, arr) {
return arr.indexOf(item) == index;
});
}
}
and then use this to make it reverse :
function reverseMapping(obj){
var ret = {};
for(var key in obj){
ret[obj[key]] = key;
}
return ret;
}
You could try getting an array of values and an array of keys from the current object, and setup a new object to hold the result. Then, as you loop through the array of values -
if the object already has this value as the key, like Netherlands, you create a new array, fetch the already existing value (ex: Rotterdam), and add this and the new value (Amsterdam) to the array, and set up this array as the new value for the Netherlands key.
if the current value doesn't exist in the object, set it up as a new string, ex: France is the key and Paris is the value.
Code -
const city2country = {
'Amsterdam': 'Netherlands',
'Rotterdam': 'Netherlands',
'Paris': 'France',
};
function reverseMapping(obj) {
let values = Object.values(obj);
let keys = Object.keys(obj);
let result = {}
values.forEach((value, index) => {
if(!result.hasOwnProperty(value)) {
// create new entry
result[value] = keys[index];
}
else {
// duplicate property, create array
let temp = [];
// get first value
temp.push(result[value]);
// add second value
temp.push(keys[index]);
// set value
result[value] = temp;
}
});
console.log(result);
return result;
}
reverseMapping(city2country)
The benefit here is - it adjusts to the structure of your current object - Netherlands being the repeated values, gets an array as it's value in the new object, while France gets a string value Paris as it's property. Of course, it should be very easy to change this.
Note - Object.values() might not be supported across older browsers.
You could use reduce to save the declaration line reduce.
Abusing && to check if the map[object[key]] is defined first before using Array.concat.
It's shorter, but is it simpler? Probably not, but a bit of fun ;)
const reverseMapping = (object) =>
Object.keys(object).reduce((map, key) => {
map[object[key]] = map[object[key]] && map[object[key]].concat(key) || [key]
return map;
}, {});
#Nina Scholz answer works well for this exact question. :thumbsup:
But if you don't need to keep both values for the Netherlands key ("Netherlands": ["Amsterdam", "Rotterdam"]), then this is a little bit shorter and simpler to read:
const city2country = { Amsterdam: 'Netherlands', Rotterdam: 'Netherlands', Paris: 'France' };
console.log(
Object.entries(city2country).reduce((obj, item) => (obj[item[1]] = item[0]) && obj, {})
);
// outputs `{Netherlands: "Rotterdam", France: "Paris"}`
Related
I've an array of errors, each error has a non-unique param attribute.
I'd like to filter the array based on whether the param has been seen before.
Something like this:
const filteredErrors = [];
let params = [];
for(let x = 0; x < errors.length; x++) {
if(!params.includes(errors[x].param)) {
params.push(errors[x].param);
filteredErrors.push(errors[x]);
}
}
But I've no idea how to do this in ES6.
I can get the unique params const filteredParams = Array.from(new Set(errors.map(error => error.param)));
but not the objects themselves.
Pretty sure this is just a weakness in my understanding of higher order functions, but I just can't grasp it
You could destrucure param, check against params and add the value to params and return true for getting the object as filtering result.
As result you get an array of first found errors of the same type.
const
params = [],
filteredErrors = errors.filter(({ param }) =>
!params.includes(param) && params.push(param));
Instead of an array you can make use of an object to keep a map of existing values and make use of filter function
let params = {};
const filteredErrors = errors.filter(error => {
if(params[error.param]) return false;
params[error.param] = true;
return true;
});
i'd probably do it like this with a reduce and no need for outside parameters:
const filteredErrors = Object.values(
errors.reduce((acc, val) => {
if (!acc[val.param]) {
acc[val.param] = val;
}
return acc;
}, {}))
basically convert it into an object keyed by the param with the object as values, only setting the key if it hasn't been set before, then back into an array of the values.
generalized like so
function uniqueBy(array, prop) {
return Object.values(
array.reduce((acc, val) => {
if (!acc[val[prop]]) {
acc[val[prop]] = val;
}
return acc;
}, {}))
}
then just do:
const filteredErrors = uniqueBy(errors, 'param');
If your param has a flag identifier if this param has been seen before then you can simply do this.
const filteredErrors = errors.filter(({ param }) => param.seen === true);
OR
const filteredErrors = errors.filter((error) => error.param.seen);
errors should be an array of objects.
where param is one of the fields of the element of array errors and seen is one of the fields of param object.
You can do it by using Array.prototype.reduce. You need to iterate through the objects in the array and keep the found params in a Set if it is not already there.
The Set.prototype.has will let you find that out. If it is not present in the Set you add it both in the Set instance and the final accumulated array, so that in the next iteration if the param is present in your Set you don't include that object:
const errors = [{param: 1, val: "err1"}, {param: 2, val: "err2"}, {param: 3, val: "err3"}, {param: 2, val: "err4"}, {param: 1, val: "err5"}];
const { filteredParams } = errors.reduce((acc, e) => {
!acc.foundParams.has(e.param) && (acc.foundParams.add(e.param) &&
acc.filteredParams.push(e));
return acc;
}, {foundParams: new Set(), filteredParams: []});
console.log(filteredParams);
Given the array:
['1', {type:'2'}, ['3', {number: '4'}], '5']
I need to make a clone without using the slice, json.parse and other methods.
At the moment, the code is working, but it will not clone objects:
var myArr =['1',{type:'2'},['3',{number:'4'}],'5'];
var arrClone=[];
for(var i=0;i<myArr.length;i++){
if(typeof(myArr[i])==='object')
{
var arr=[];
for(var j=0;j<myArr[i].length;j++){
arr[j]=myArr[i][j];
}
arrClone[i]=arr;
}else { arrClone[i]=myArr[i]}
}
You could check if an object is supplied and if so, another check is made for an array. Then retrn either the cloned array or the object, or the value itself.
function getClone(value) {
if (value && typeof value === 'object') {
return Array.isArray(value)
? value.map(getClone)
: Object.assign(
...Object.entries(value).map(([k, v]) => ({ [k]: getClone(v) }))
);
}
return value;
}
var data = ['1', { type: '2' }, ['3', { number: '4' }], '5'],
clone = getClone(data);
console.log(getClone(data));
Here is a simple implementation without Array methods:
function deepClone(obj) {
if (typeof obj !== "object" || obj === null) return obj; // primitives
// It could be an array or plain object
const result = obj.constructor.name == "Array" ? [] : {};
for (const key in obj) {
result[key] = deepClone(obj[key]); // recursive call
}
return result;
}
// Demo
var myArr =['1',{type:'2'},['3',{number:'4'}],'5'];
var arrClone = deepClone(myArr);
console.log(arrClone);
Note that this only works for simple data types. As soon as you start to work with Dates, regex objects, Sets, Maps, ...etc, you need much more logic. Also think of self references and functions, and how they should be dealt with.
For more advanced cloning see How to Deep Clone in JavaScript, but expect the use of several methods.
First, sorry if you find the question confusing.
Basically, I have an object like this:
[{"6":6.5},{"4":4.2},{"6":6.3}]
What I want to do, is to remove the duplicated keys but keep there values and push them all into one unique key only, as an array. like this:
[{"6":[6.5, 6.3]}, {"4": 4.2}]
Can anyone suggest a solution?
.reduce() is what you want:
var data = [{"6":6.5},{"4":4.2},{"6":6.3}];
var res = data.reduce((rv, obj) => {
var key = Object.keys(obj)[0];
rv[key] = rv[key] || [];
rv[key].push(obj[key]);
return rv;
}, {});
console.log(res);
Note: This returns data always in the format of arrays (Even if there is one value). If you're looking for the exact format you specified, you just need to add more logic as I've demonstrated below (Although, I wouldn't recommend this approach, as it adds more complication down the line.)
var data = [{"6":6.5},{"4":4.2},{"6":6.3}];
var res = data.reduce((rv, obj) => {
var key = Object.keys(obj)[0];
if (Array.isArray(rv[key])) { // Already is an array
rv[key].push(obj[key]);
} else if (rv[key] !== undefined) { // Has a value. Convert to array
rv[key] = [rv[key], obj[key]];
} else { // Haven't seen this key yet. Set the value
rv[key] = obj[key];
}
return rv;
}, {});
console.log(res);
Probably a dumb question, Assume i have an object like below,
{"Country":"country","Continent":"continent","Province":"","District":"","State":"state","City":""}"
What is the quickest way to check which properties hold the values in the above object without using a loop?
while doing this inside a for loop is working,
if(typeof someUndefVar == whatever) -- works
EXPECTED OUTPUT:
Country, Continent and State
var a = {"Country":"country","Continent":"continent","Province":"","District":"","State":"state","City":""};
Object.keys(a).filter( prop => a[prop] );
It also depends on how you want to handle the 0, null, undefined values.
You might do as follows;
var obj = {"Country":"country","Continent":"continent","Province":"","District":"","State":"state","City":""};
for (var prop in obj) !!obj[prop] && console.log(prop);
You need to use the object.keys() function,
Like this,
var jsonData= {"Country":"country","Continent":"continent","Province":"","District":"","State":"state","City":""};
console.log(Object.keys(jsonData));
Hope this helps!
Sorry you said no loop and I got excited and I was bored and I got functional.
First I shortened some names and I guess they are kind of self explanatory:
head: first element of an array
tail: all the elements but the first
isEmpty: check if an array has length 0
transform does the actual work using a callback, it stops if the keys array is empty if not goes recursive.
var data = {"Country":"country","Continent":"continent","Province":"","District":"","State":"state","City":""}
// plumbing
var head = (a) => a[0]
var tail = (a) => a.slice(1)
var isEmpty = (a) => a.length === 0
// actual stuff happens here
var transform = (obj, callback) => {
var withval = (keys) => {
if(isEmpty(keys)) return
var p = head(keys)
if(!!obj[p]) callback(p)
withval(tail(keys))
}
withval(Object.keys(obj))
}
// Logs
var log = (p) => console.log(p)
transform(data, log)
// Array making
var arr = [];
var toArr = (p) => arr.push(p)
transform(data, toArr)
console.log(arr)
// Object making
var obj = {}
var toObj = (p) => obj[p] = data[p]
transform(data, toObj)
console.log(obj)
Outcome:
Country
Continent
State
[ 'Country', 'Continent', 'State' ]
{ Country: 'country', Continent: 'continent', State: 'state' }
Consider:
var object = {
foo: {},
bar: {},
baz: {}
}
How would I do this:
var first = object[0];
console.log(first);
Obviously, that doesn’t work because the first index is named foo,
not 0.
console.log(object['foo']);
works, but I don’t know it’s named foo. It could be named anything. I just want the first.
Just for fun this works in JS 1.8.5
var obj = {a: 1, b: 2, c: 3};
Object.keys(obj)[0]; // "a"
This matches the same order that you would see doing
for (o in obj) { ... }
If you want something concise try:
for (first in obj) break;
alert(first);
wrapped as a function:
function first(obj) {
for (var a in obj) return a;
}
they're not really ordered, but you can do:
var first;
for (var i in obj) {
if (obj.hasOwnProperty(i) && typeof(i) !== 'function') {
first = obj[i];
break;
}
}
the .hasOwnProperty() is important to ignore prototyped objects.
This will not give you the first one as javascript objects are unordered, however this is fine in some cases.
myObject[Object.keys(myObject)[0]]
If the order of the objects is significant, you should revise your JSON schema to store the objects in an array:
[
{"name":"foo", ...},
{"name":"bar", ...},
{"name":"baz", ...}
]
or maybe:
[
["foo", {}],
["bar", {}],
["baz", {}]
]
As Ben Alpert points out, properties of Javascript objects are unordered, and your code is broken if you expect them to enumerate in the same order that they are specified in the object literal—there is no "first" property.
for first key of object you can use
console.log(Object.keys(object)[0]);//print key's name
for value
console.log(object[Object.keys(object)[0]]);//print key's value
There is no way to get the first element, seeing as "hashes" (objects) in JavaScript have unordered properties. Your best bet is to store the keys in an array:
var keys = ["foo", "bar", "baz"];
Then use that to get the proper value:
object[keys[0]]
ES6
const [first] = Object.keys(obj)
Using underscore you can use _.pairs to get the first object entry as a key value pair as follows:
_.pairs(obj)[0]
Then the key would be available with a further [0] subscript, the value with [1]
I had the same problem yesterday. I solved it like this:
var obj = {
foo:{},
bar:{},
baz:{}
},
first = null,
key = null;
for (var key in obj) {
first = obj[key];
if(typeof(first) !== 'function') {
break;
}
}
// first is the first enumerated property, and key it's corresponding key.
Not the most elegant solution, and I am pretty sure that it may yield different results in different browsers (i.e. the specs says that enumeration is not required to enumerate the properties in the same order as they were defined). However, I only had a single property in my object so that was a non-issue. I just needed the first key.
You could do something like this:
var object = {
foo:{a:'first'},
bar:{},
baz:{}
}
function getAttributeByIndex(obj, index){
var i = 0;
for (var attr in obj){
if (index === i){
return obj[attr];
}
i++;
}
return null;
}
var first = getAttributeByIndex(object, 0); // returns the value of the
// first (0 index) attribute
// of the object ( {a:'first'} )
To get the first key of your object
const myObject = {
'foo1': { name: 'myNam1' },
'foo2': { name: 'myNam2' }
}
const result = Object.keys(myObject)[0];
// result will return 'foo1'
Based on CMS answer. I don't get the value directly, instead I take the key at its index and use this to get the value:
Object.keyAt = function(obj, index) {
var i = 0;
for (var key in obj) {
if ((index || 0) === i++) return key;
}
};
var obj = {
foo: '1st',
bar: '2nd',
baz: '3rd'
};
var key = Object.keyAt(obj, 1);
var val = obj[key];
console.log(key); // => 'bar'
console.log(val); // => '2nd'
My solution:
Object.prototype.__index = function(index)
{
var i = -1;
for (var key in this)
{
if (this.hasOwnProperty(key) && typeof(this[key])!=='function')
++i;
if (i >= index)
return this[key];
}
return null;
}
aObj = {'jack':3, 'peter':4, '5':'col', 'kk':function(){alert('hell');}, 'till':'ding'};
alert(aObj.__index(4));