Alternative for _.invert() using underscore library - javascript

I need to change the existing map swapping keys into values and values into keys. As there is duplicate values in my map for the keys I cannot use _.invert() of underscore library.
function map() {
return {
'eatables': {
apple: 'fruits',
orange: 'fruits',
guava: 'fruits',
brinjal: 'vegetables',
beans: 'vegetables',
rose: 'flowers',
}
}
}
var reverseMap = _.invert(map()['eatables']);
// invert function works for distinct values.
console.log (reverseMap);
// which is giving Object {fruits: "guava", vegetables: "brinjal",flowers:"rose"}
But i am expecting an output as
Object {fruits: ["apple","orange","guava"], vegetables: ["brinjal","beans"], flowers:"rose"}
I tried as below, i just stuck how to find whether map value is distinct or multiple?
var newObj = invert(map()['eatables']);
_.each(newObj, function(key) {
if (Array.isArray(key)) {
_.each( key, function(value) {
console.log(value);
});
} else {
console.log("else:"+key);
}
});
function invert(srcObj) {
var newObj = {};
_.groupBy(srcObj, function(value, key ) {
if (!newObj[value]) newObj[value] = []; //Here every thing is array, can i make it string for values which are unique.
newObj[value].push(key);
});
return newObj;
}
Let me any alternative using underscore library.

You can use this function. This function uses Object.keys to generate an array containing the keys of the object passed in input. Then, it accesses the values of the original object and use them as key in the new object. When two values map to the same key, it pushes them into an array.
function invert(obj) {
var result = {};
var keys = Object.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (result[obj[keys[i]]] instanceof Array) {
result[obj[keys[i]]].push(keys[i])
} else if (result[obj[keys[i]]]) {
var temp = result[obj[keys[i]]];
result[obj[keys[i]]] = [temp, keys[i]];
} else {
result[obj[keys[i]]]=keys[i];
}
}
return result;
}
https://jsfiddle.net/6f2ptxgg/1/

You can use the underscore each to iterate through your data and push the result in an array. It should give you your expected output.
function customInvert(data) {
var result = {};
_.each(data, function (value, key) {
if (_.isUndefined(result[value])) {
result[value] = key;
} else if(_.isString(result[value])) {
result[value] = [result[value], key];
} else {
result[value].push(key)
}
});
return result;
}
customInvert({
apple: 'fruits',
orange: 'fruits',
guava: 'fruits',
brinjal: 'vegetables',
beans: 'vegetables',
rose: 'flowers',
})

Related

How to get a key from array of objects by its value Javascript

I have a collection which looks like this:
var array = [
{ 'key1': 'val1'},
{ 'key2': 'val2'}
];
I want a function which takes array and value as parameters and returns key of that value (values are unique)
getKey(array, 'val1');
>>>>> output: 'key1'
I tried the following solution but it says key property is not defined:
getKey(array, value) {
var keys = [];
array.forEach(function(element) {
for (key in element) {
if(value == element[key]) {
keys.push(key);
}
}
})
return keys[0];
},
var arr = [{ 'key1': 'val1'}, { 'key2': 'val2'}];
function getKey(data,value) {
let keys=[];
data.forEach(function(element) {
for (key in element) {
if(element[key]==value)
keys.push(key);
}
});
return keys
}
console.log(getKey(arr, 'val1'))
Create an inverse object with value as key and key as value. And then finally access the value from object as key.
var array = [{ 'key1': 'val1'},{ 'key2': 'val2'}];
function getKey(arr, val) {
let obj = arr.reduce((a,c) => {
Object.entries(c).forEach(([k,v]) => a[v]=k);
return a;
}, {})
return obj[val];
}
console.log(getKey(array, 'val1'));
In pure JS you can take all the entries, flatten them and simply search the value in all couples and if found then just take the first entry of that couple.
[].concat(...array.map(Object.entries)).find(a=>a[1]=='val1')[0]
Here is an working example:
var array = [{'key1': 'val1'}, {'key2': 'val2'}, {'key3': 'val3'}],
getKey = (arr,v)=>[].concat(...arr.map(Object.entries)).find(a=>a[1]==v)[0];
console.log(getKey(array, 'val2'));
console.log(getKey(array, 'val3'));
However, I will suggest you to use lodash (or underscore, or lazy) to make it more cleaner and simple.
Here is a lodash approach:
_.findKey(_.merge({}, ...array), s=>s=='val1')
Here is a woking example:
var array = [{'key1': 'val1'}, {'key2': 'val2'}, {'key3': 'val3'}],
getKey = (arr,v)=>_.findKey(_.merge({}, ...arr), s=>s==v);
console.log(getKey(array, 'val2'));
console.log(getKey(array, 'val3'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
You can also try this:
var arr = [{ 'key1': 'val1'}, { 'key2': 'val2'}];
function getKey(arr, val) {
var result = 'not found';
arr.forEach(obj => {
for (var key in obj) {
if(obj[key] === val) result = key;
}
});
return result;
}
console.log(getKey(arr, 'val1'));
console.log(getKey(arr, 'value'));

How to get all the names inside a nested object?

I'm currently learning JavaScript and my teacher asked me to do an exercise that would return an array with all the names of this object:
{
name: 'grandma',
daughter: {
name: 'mother',
daughter: {
name: 'daughter',
daughter: {
name: 'granddaughter'
}
}
}
}
my question is similar to this one but the solution does not work for me because my object does not contain any arrays. The code I have so far:
function toArray(obj) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(toArray(value));
}
else {
result.push(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target);
}
which prints out this : [ 'grandma', [ 'mother', [ 'daughter', [Array] ] ] ]
but what my teacher wants is: ['grandma', 'mother', 'daughter', 'granddaughter']
codepen
Obviously you push an array to an array, where all nested children appears as an array.
To solve this problem, you could iterate the array and push only single items to the result set.
A different method is, to use some built-in techniques, which works with an array, and returns a single array without a nested array.
Some methods:
Array#concat, creates a new array. It works with older Javascript versions as well.
result = result.concat(toArray(value));
Array#push with an array and Function#apply for taking an array as parameter list. It works in situ and with older versions of JS.
Array.prototype.push.apply(result, toArray(value));
[].push.apply(result, toArray(value)); // needs extra empty array
Spread syntax ... for spreading an array as parameters. ES6
result.push(...toArray(value));
Spread syntax is a powerful replacement for apply with a greater use. Please the the examples as well.
Finally an example with spread syntax.
function toArray(obj) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (value && typeof value === 'object') { // exclude null
result.push(...toArray(value));
// ^^^ spread the array
}
else {
result.push(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target);
}
var object = { name: 'grandma', daughter: { name: 'mother', daughter: { name: 'daughter', daughter: { name: 'granddaughter' } } } };
console.log(nameMatrioska(object));
You need .concat instead of .push. Push adds one item to an array; concat joins two arrays together.
['grandmother'].concat(['mother', 'daughter'])
-> ['grandmother', 'mother', 'daughter']
Unlike push, which modifies the array you call it on, concat creates a new array.
var a1 = [ 'grandmother' ];
a1.push( 'mother' );
console.log( a1 );
-> ['grandmother', 'mother']
var a2 = [ 'steve' ];
var result = a2.concat(['Jesus', 'Pedro']);
console.log( a1 );
-> ['steve']
console.log( result );
-> ['steve', 'Jesus', 'Pedro']
Try this
function toArray(obj) {
var result = "";
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result = result.concat(" " + toArray(value));
}
else {
result = result.concat(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target).split(" ");
}
function toArray(obj) {
var result = [];
for (var prop in obj) {
var value = obj[prop];
if (typeof value === 'object') {
result = result.concat(toArray(value))
} else {
result.push(value);
}
}
return result;
}
function nameMatrioska(target) {
return toArray(target);
}
//USER
var names = {
name: 'grandma',
daughter: {
name: 'mother',
daughter: {
name: 'daughter',
daughter: {
name: 'granddaughter'
}
}
}
};
console.log(nameMatrioska(names));
//Output: ["grandma", "mother", "daughter", "granddaughter"]
You are really close.
You have to flatten your array in your last step.
Tip: In general be careful when checking for type object because e.g. null, undefined are also objects in JavaScript world!
function isObject(value) {
if(value === undefined) return "Undefined";
if(value === null) return "Null";
const string = Object.prototype.toString.call(value);
return string.slice(8, -1);
}
function collectPropertiesRec(object, propertyName) {
const result = [ ];
for(const currentPropertyName in object) {
const value = object[currentPropertyName];
if(isObject(value) === 'Object') {
result.push(collectPropertiesRec(value, propertyName));
}
else if(currentPropertyName === propertyName) {
result.push(value);
}
}
return result;
}
function flattenDeep(arr1) {
return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), [ ]);
}
//USER
const names = {
name: 'grandma',
daughter: {
name: 'mother',
daughter: {
name: 'daughter',
daughter: {
name: 'granddaughter'
}
}
}
};
var result = collectPropertiesRec(names, "name");
alert(flattenDeep(result).join(", "));

how to grep this Javascript object array?

I have the following object structure:
var mapData =
{
Summary:
{
ReportName: 'Month End Report'
},
NortheastRegion:
{
Property1: 123,
RegionName: 'Northeast'
},
SoutheastRegion:
{
Property1: 456,
RegionName: 'Southeast'
},
}
I want to write a grep function that returns an array of region names. The following function is not returning any values:
var regions = $.grep(mapData, function(n,i)
{
return n.RegionName;
});
What am I missing here?
$.grep is for filtering arrays. Your structure isn't an array. $.grep is also just for filtering, but you're talking about both filtering (leaving out Summary) and mapping (getting just the region names).
Instead, you can use
Object.keys and push:
var regions = [];
Object.keys(mapData).forEach(function(key) {
var entry = mapData[key];
if (entry && entry.RegionName) {
regions.push(entry.RegionName);
}
});
Object.keys, filter, and map:
var regions = Object.keys(mapData)
.filter(function(key) {
return !!mapData[key].RegionName;
})
.map(function(key) {
return mapData[key].RegionName;
});
A for-in loop and push:
var regions = [];
for (var key in mapData) {
if (mapData.hasOwnProperty(key)) {
var entry = mapData[key];
if (entry && entry.RegionName) {
regions.push(entry.RegionName);
}
}
}
...probably others.
That's an object, not an array. According to the jQuery docs, your above example would work if mapData were an array.
You can use lodash's mapValues for this type of thing:
var regions = _.mapValues(mapData, function(o) {
return o.RegionName;
});
ES6:
const regions = _.mapValues(mapData, o => o.RegionName)
As stated in jQuery.grep() docs, you should pass an array as data to be searched, but mapData is an object. However, you can loop through the object keys with Object.keys(), but AFAIK you'll have to use function specific for your case, like:
var mapData =
{
Summary:
{
ReportName: 'Month End Report'
},
NortheastRegion:
{
Property1: 123,
RegionName: 'Northeast'
},
SoutheastRegion:
{
Property1: 456,
RegionName: 'Southeast'
},
};
var keys = Object.keys(mapData),
result = [];
console.log(keys);
keys.forEach(function(key) {
var region = mapData[key].RegionName;
if (region && result.indexOf(region) == -1) {
result.push(region);
}
});
console.log(result);
// Short version - based on #KooiInc answer
console.log(
Object.keys(mapData).map(m => mapData[m].RegionName).filter(m => m)
);
$.grep is used for arrays. mapData is an object. You could try using map/filter for the keys of mapData, something like:
var mapData =
{
Summary:
{
ReportName: 'Month End Report'
},
NortheastRegion:
{
Property1: 123,
RegionName: 'Northeast'
},
SoutheastRegion:
{
Property1: 456,
RegionName: 'Southeast'
},
};
var regionNames = Object.keys(mapData)
.map( function (key) { return mapData[key].RegionName; } )
.filter( function (name) { return name; } );
console.dir(regionNames);
// es2105
var regionNames2 = Object.keys(mapData)
.map( key => mapData[key].RegionName )
.filter( name => name );
console.dir(regionNames2);
Just turn $.grep to $.map and you would good to go.
var regions = $.map(mapData, function(n,i)
{
return n.RegionName;
});

filter JSON by two different values

I'm using a function to filter a JSON file based on the value of the year key, like so:
function filterYr(json, key, value) {
var result = [];
for (var year in json) {
if (json[year][key] === value) {
result.push(json[year]);
}
}
return result;
}
I'm then setting a default:
var chooseYear = filterYr(json, 'year', "2000");
However there's also a dropdown, so the JSON file can be filtered onchange of the dropdown select option.
My question is, can I use this same function to filter the same JSON file by another value, too?
So for instance, I also want to filter by the key 'type.'
If it were a new function it'd be:
function filterType(json, key, value) {
var result = [];
for (var type in json) {
if (json[type][key] === value) {
result.push(json[type]);
}
}
return result;
}
But how do I combine that into one function?
And then how do I set a default that passes both the 'type' and the 'year' to the function?
Is that possible?
Thank you and let me know if I can provide more detail if this isn't clear.
PS- I'd prefer to just use javascript and not a library, if possible.
If your data structure is like below, your current function just works well
var items = [
{
year: 2000,
type: 'type1'
},
{
year: 2001,
type: 'type2'
}
];
function filterYr(json, key, value) {
var result = [];
for (var year in json) {
if (json[year][key] === value) {
result.push(json[year]);
}
}
return result;
}
filterYr(items, 'type', 'type2'); //[ { year: 2001, type: 'type2' } ]
filterYr(items, 'year', 2000); //[ { year: 2000, type: 'type1' } ]
You just need to use a more general name for your function and year variable
You can modify the function so it accepts an object as criterion for filtering. The following function accepts an object with n number of properties:
function findWhere(collection, props) {
var keys = Object.keys(props), // get the object's keys
klen = keys.length; // cache the length of returned array
return collection.filter(function(el) {
// compare the length of the matching properties
// against the length of the passed parameters
// if both are equal, return true
return keys.filter(function(key) {
return el[key] === props[key];
}).length === klen;
})
}
var filteredCollection = findWhere(arrayOfObjects, {
type: 'foo',
anotherProp: 'aValue'
});

how to merge objects in javascript without override values

how can i merge duplicate key in objects and concat values in objects in one object
i have objects like this
var object1 = {
role: "os_and_type",
value: "windows"
};
var object2 = {
role: "os_and_type",
value: "Android"
};
var object3 = {
role: "features",
value: "GSM"
};
how can i achieve this object
new_object = [{
role: "os_and_type",
value: ["windows", "android"]
}, {
role: "features",
value: ["GSM"]
}];
Here you go:
var object1 = {
role: "os_and_type",
value: "windows"
};
var object2 = {
role: "os_and_type",
value: "Android"
};
var object3 = {
role: "features",
value: "GSM"
};
function convert_objects(){
var output = [];
var temp = [];
for(var i = 0; i < arguments.length; i++){ // Loop through all passed arguments (Objects, in this case)
var obj = arguments[i]; // Save the current object to a temporary variable.
if(obj.role && obj.value){ // If the object has a role and a value property
if(temp.indexOf(obj.role) === -1){ // If the current object's role hasn't been seen before
temp.push(obj.role); // Save the index for the current role
output.push({ // push a new object to the output,
'role':obj.role,
'value':[obj.value] // but change the value from a string to a array.
});
}else{ // If the current role has been seen before
output[temp.indexOf(obj.role)].value.push(obj.value); // Save add the value to the array at the proper index
}
}
}
return output;
}
Call it like this:
convert_objects(object1, object2, object3);
You can add as many objects to the function as you'd like.
Too bad that we haven't seen any attempt.
function merge(array) {
var temp = {},
groups = [],
l = array.length,
i = 0,
item;
while (item = array[i++]) {
if (!temp[item.role]) {
temp[item.role] = {
role: item.role,
value: [item.value]
};
} else if (temp[item.role].value.indexOf(item.value) === -1) {
temp[item.role].value.push(item.value);
}
}
for (var k in temp) {
groups.push(temp[k]);
}
return groups;
}
Usage :
var groups = merge([object1, object2, object3]);
Here's a version using maps to avoid scanning for duplicates over and over. Also using some cool methods
Object.keys
Array.prototype.map
Array.prototype.forEach
It ended up being slightly smaller too.
function merge(objects) {
var roles = {};
objects.forEach(function(obj){
roles[obj.role] = roles[obj.role] || {};
roles[obj.role][obj.value] = {};
});
return Object.keys(roles).map(function(role){
return {
role: role,
value: Object.keys(roles[role])
};
});
}
http://jsfiddle.net/mendesjuan/cD7uu/1/

Categories