Is there a better way to replace my double forEach method? - javascript

I have two list, as bellow:
var a = ["a", "b"]
var b = [{name:"a1", belong_type:"a" }, {name:"a2", belong_type:"a" }, {name:"b1", belong_type:"b" },]
I want to put them like this:
var data = {}
a.forEach(a_item => {
data[a_item] = []
b.forEach(b_item => {
if (a_item === b_item.belong_type){
data[a_item].push(b_item)
}
})
})
console.log(data)
the result is :
{ a:
[ { name: 'a1', belong_task_type: 'a' },
{ name: 'a2', belong_task_type: 'a' } ],
b: [ { name: 'b1', belong_task_type: 'b' } ] }
I think my method use two forEach, I don't know whether there is a better way to realize the result, who can tell me if there is a better way?

You could use reduce method on a array and inside use filter method on b array to return objects where belong_type is equal to current element in reduce.
var a = ["a", "b"]
var b = [{name:"a1", belong_type:"a" }, {name:"a2", belong_type:"a" }, {name:"b1", belong_type:"b" }]
const result = a.reduce((r, e) => {
r[e] = b.filter(({belong_type}) => belong_type == e)
return r;
}, {})
console.log(result)
You could also use Object.assign method inside reduce to write it as a one-liner.
var a = ["a", "b"]
var b = [{name:"a1", belong_type:"a" }, {name:"a2", belong_type:"a" }, {name:"b1", belong_type:"b" }]
const result = a.reduce((r, e) => Object.assign(r, {[e]: b.filter(({belong_type}) => belong_type == e)}), {})
console.log(result)

Related

Filter only unique values from an array of object javascript

I have an array of objects i want to filter only the unique style and is not repeated .
const arrayOfObj = [ {name:'a' , style:'p'} , {name:'b' , style:'q'} , {name:'c' , style:'q'}]
result expected : [ {name:'a' , style:'p'}]
Here is a solution in O(n) time complexity. You can iterate all entries to track how often an entry occurs. And then use the filter() function to filter the ones that occur only once.
const arrayOfObj = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" },
]
const styleCount = {}
arrayOfObj.forEach((obj) => {
styleCount[obj.style] = (styleCount[obj.style] || 0) + 1
})
const res = arrayOfObj.filter((obj) => styleCount[obj.style] === 1)
console.log(res)
On of the possible solutions depending on your performance / readability needs can be:
arrayOfObj.filter(a => arrayOfObj.filter(obj => obj.style === a.style).length === 1)
Use splice when you find the existing item and remove it
const arrayOfObj = [{
name: 'a',
style: 'p'
}, {
name: 'b',
style: 'q'
}, {
name: 'c',
style: 'q'
}]
const result = arrayOfObj.reduce((acc, x) => {
const index = acc.findIndex(y => y.style === x.style);
if (index >= 0) {
acc.splice(index, 1);
} else {
acc.push(x);
}
return acc;
}, [])
console.log(result)
Here is a solution in O(n) time complexity. You can iterate all entries to track how often an entry occurs. And then use the filter() function to filter the ones that occur only once.
const arrayOfObj = [ {name:'a' , style:'p'} , {name:'b' , style:'q'} , {name:'c' , style:'q'}];
let count = {};
arrayOfObj.forEach(({style}) => {
count[style] = (count[style] || 0) + 1;
});
let result = arrayOfObj.filter(({style}) => count[style] === 1);
console.log(result);
You reduce it. Check if in the array already an element with the same style exists and remove it from the accumulator otherwise push it to the accumulator
const arr = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" }
];
let result = arr.reduce((a,v) => {
let i = a.findIndex(el => el.style === v.style);
if(i !== -1) {
a.splice(i,1);
return a;
}
a.push(v)
return a;
},[])
console.log(result);
There is a one liner answer too if you are using lodash library
(uniqBy(array, iteratee))
const arr = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" }
];
let result = _.uniqBy(arrayOfObj,'style')
console.log(result)

JS array of arrays to object

I have a JS array (shown 4 examples actual has 66 )
[["A","Example1"],["A","Example2"],["B","Example3"],["B","Example4"]]
that I am trying to get into an object for a multi select drop down menu:
var opt = [{
label: 'A', children:[
{"label":"Example1","value":"Example1","selected":"TRUE"},
{"label":"Example2","value":"Example2","selected":"TRUE"}
]
},
{
label: 'B', children:[
{"label":"Example3","value":"Example3","selected":"TRUE"},
{"label":"Example4","value":"Example4","selected":"TRUE"}
]
}
]
Is there a easy way to do this ?
Updated:
Using reduce() and filter() to get expected results.
const result = [['A', 'Example1'], ['A', 'Example2'], ['B', 'Example3'], ['B', 'Example4']].reduce((acc, cur) => {
const objFromAccumulator = acc.filter((row) => row.label === cur[0]);
const newChild = {label: cur[1], value: cur[1], selected: 'TRUE'};
if (objFromAccumulator.length) {
objFromAccumulator[0].children.push(newChild);
} else {
acc.push({label: cur[0], children: [newChild]});
}
return acc;
}, []);
console.log(result);
Something like this should work:
const raw = [["A","Example1"],["A","Example2"],["B","Example3"],["B","Example4"]];
const seen = new Map();
const processed = raw.reduce((arr, [key, label]) => {
if (!seen.has(key)) {
const item = {
label: key,
children: []
};
seen.set(key, item);
arr.push(item);
}
seen.get(key).children.push({
label,
value: label,
selected: "TRUE"
})
return arr;
}, []);
console.log(processed);
Here's a rather efficient and concise take on the problem using an object as a map:
const data = [["A","Example1"],["A","Example2"],["B","Example3"],["B","Example4"]];
const opt = data.reduce((results,[key,val]) => {
if(!results[0][key]) //first element of results is lookup map of other elements
results.push(results[0][key] = { label: key, children: [] });
results[0][key].children.push({ label: val, value: val, selected:"TRUE" });
return results;
}, [{}]).slice(1); //slice off map as it's no longer needed
console.log(opt);

Converting two lists to json

I'm attempting to convert two lists to json.
For example :
l1 = ['a','b','a']
l2 = ['q','r','s']
should be converted to :
[{
"name": "g",
"children": [{
"name": "a",
"children": [{
"name": "q"
}, {
"name": "s"
}]
},
{
"name": "b",
"children": [{
"name": "r"
}]
}
]
}]
Closest I have is :
l1 = ['a','b','a']
l2 = ['q','r','s']
nameDict = {}
childrenDict = {}
l1 = l1.map(x => {
return({name: x});
});
console.log(l1);
l2 = l2.map(x => {
return({children: x});
});
console.log(l2);
var c = l1.map(function(e, i) {
return [e, l2[i]];
});
console.log(JSON.stringify(c))
which produces :
[[{"name":"a"},{"children":"q"}],
[{"name":"b"},{"children":"r"}],
[{"name":"a"},{"children":"s"}]]
How to combine the elements produce ? :
[{
"name": "g",
"children": [{
"name": "a",
"children": [{
"name": "q"
}, {
"name": "s"
}]
},
{
"name": "b",
"children": [{
"name": "r"
}]
}
]
}]
Disclaimer: Since we don't know where the g comes from, I will only build the root children array.
Since your arrays have the same length, you can use a plain for and use with the index to play with both arrays. Just build an array and check each iteration if the "child" already exists. If not, create it.
l1 = ['a','b','a']
l2 = ['q','r','s']
let gChildren = []
for(let i = 0; i < l1.length; i++){
let group = gChildren.find(c => c.name === l1[i])
if(!group){
group = { name: l1[i], children: [] }
gChildren.push(group)
}
group.children.push({ name: l2[i] })
}
console.log(gChildren)
Here is working code that accounts for your pre-existing structure that accomplishes the result you are looking for.
let data1 = ["a","b","a"];
let data2 = ["q","r","s"];
let outputData = [{name: "g", children: []}];
for (let i=0;i < data1.length;i++) {
let found = false;
for (let j=0;j < outputData[0].children.length;j++) {
if (outputData[0].children[j].name === data1[i]) {
outputData[0].children[j].children.push({name: data2[i]});
found = true;
}
}
if (found === false) {
outputData[0].children.push({name: data1[i], children: [{name: data2[i]}]});
}
}
console.log(JSON.stringify(outputData));
This is a good use case for a Array.prototype.reduce, where you want to iterate over an array but end up with a single value.
l1.reduce((acc, val, i) => {
const l2Val = l2[i]
const foundObj = acc.find(o => o.name === val)
if (foundObj) {
foundObj.children.push({name: l2Val})
} else {
acc.push({
name: val,
children: [{name: l2Val}]
})
}
return acc
}, [])
Here, on each iteration I'm just adding the child to the children array for that item, or creating the value for the item if it doesn't already exist.
I have no idea what g corresponds to so I've left it out, but you can add the array created from reduce to another object or array if you want.
You could transpose the array and use the information as path to the final child object
l1 = ['a', 'b', 'a']
l2 = ['q', 'r', 's']
transposes to
[
['a', 'q'], // a -> q
['b', 'r'], // b -> r
['a', 's'] // a -> s
]
which is now works with reduce.
The advantage is to use it with longer pathes to the final children, like
[
['a', 'x', 'y', 'z'],
...
]
which returns a nested object with the given relation to each other.
const
transpose = array => array.reduce((r, a) => a.map((v, i) => [...(r[i] || []), v]), []);
var l1 = ['a', 'b', 'a'],
l2 = ['q', 'r', 's'],
result = transpose([l1, l2]).reduce((r, a) => {
a.reduce((q, name) => {
var temp = (q.children = q.children || []).find(o => o.name === name);
if (!temp) q.children.push(temp = { name });
return temp
}, r);
return r;
}, { name: 'g' });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A bit shorter with filter on the distinct keys :
var l1 = ['a','b','a'], l2 = ['q','r','s']
var children = [...new Set(l1)].map(k => ({ name: k, children:
l2.filter((v, i) => l1[i] == k).map(v => ({ name: v })) }))
console.log( [{ name: 'g', children }] )
Or more efficient with intermediate object of the groups :
var l1 = ['a','b','a'], l2 = ['q','r','s']
var children = Object.entries(
l1.reduce((o, k, i) => ((o[k] = o[k] || []).push(l2[i]), o), {})
).map(([k, v]) => ({ name: k, children: v.map(v => ({ name: v})) }))
console.log( [{ name: 'g', children }] )

JavaScript array to dictionary conversation based on count

I have a array like below
data = [A,B,B,B,C,C,A,B]
How can I convert into dictionary like below format.
data = [
{
name: "A",
y: 2
},
{
name: "B",
y: 4
},
{
name: "C",
y: 2
}
]
Have to convert elements as names and count of the elements as value to y.
I've a library which accepts only in that format.
Not able to do, stuck in the middle
Any suggestions are welcome.
data = ['A','B','B','B','C','C','A','B'];
var res = Array.from(new Set(data)).map(a =>
({name:a, y: data.filter(f => f === a).length}));
console.log(res);
use array.reduce:
data = ['A','B','B','B','C','C','A','B'];
var res = data.reduce((m, o) => {
var found = m.find(e => e.name === o);
found ? found.y++ : m.push({name: o, y: 1});
return m;
}, []);
console.log(res);
function convert(data){
var myMap = {}
data.forEach(el => myMap[el] = myMap[el] != undefined ? myMap[el] + 1 : 1);
return Object.keys(myMap).map(k => {return {name: k, y: myMap[k]}})
}
console.log(convert(['A','B','B','B','C','C','A','B']))

Sort a list by property and add an object before each first letter changes in JavaScript

So I am trying to make a UI like this:
And I have an array of users
[{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
What I am trying to do is to sort the array by first letter of the name property, and add a header object before each. For example in the picture, you can see the letter A, B, I, and J as the headers.
For now, I got it working like this:
let final = []
// sort by first letter
const sortedUsers = state.test_list.sort((a, b) => a.name.localeCompare(b.name))
for (let x = 0; x < sortedUsers.length; x++) {
const user = sortedUsers[x].name
if (user.charAt(0) === 'A') {
const checkIfExists = final.findIndex((f) => f.header === 'A')
// add the header A if it doesn't exist
if (checkIfExists < 0) final.push({header: 'A'})
}
else if (user.charAt(0) === 'B') {
const checkIfExists = final.findIndex((f) => f.header === 'B')
// add the header B if it doesn't exist
if (checkIfExists < 0) final.push({header: 'B'})
}
// else if up to the letter Z
final.push(user)
}
and if I log the final array, I get:
which is correct.
My concern is that the code is very long, and I have no idea if it can be optimized or make the code smaller.
Is there any other option to do something like this? Any help would be much appreciated.
Why don't you create a collection of names, which is grouped by the first letter? You can then loop on it, and create your list. Use Array#reduce to create the grouped collection.
And then use Object#keys to iterate over the grouped collection and render your results:
let data = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Alison'
}, {
name: 'Andrea'
}, {
name: 'Betty'
}];
let combined = data.reduce((result, item) => {
let letter = item.name[0].toUpperCase();
if (!result[letter]) {
result[letter] = [];
}
result[letter].push(item);
return result;
}, {});
console.log(combined);
// Iterate over the result
Object.keys(combined).forEach(key => {
// key will be the first letter of the user names and
// combined[key] will be an array of user objects
console.log(key, combined[key]);
});
One thing still to do is to sort the user arrays by user name, which you can do easily using Array#sort.
Simple enough, try sorting them and then using .reduce:
const unsortedPeople = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
const sortedUsers = unsortedPeople.sort((a, b) => a.name.localeCompare(b.name))
const final = sortedUsers.reduce((finalSoFar, user) => {
const thisUserFirstChar = user.name[0];
if (finalSoFar.length === 0) addHeader();
else {
const lastUserFirstChar = finalSoFar[finalSoFar.length - 1].name[0];
if (lastUserFirstChar !== thisUserFirstChar) addHeader();
}
finalSoFar.push(user);
return finalSoFar;
function addHeader() {
finalSoFar.push({ header: thisUserFirstChar });
}
}, []);
console.log(final);
Why don't you just keep track of the current abbreviation as you loop. Then you can add a head when it changes:
var users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
const sortedUsers = users.sort((a, b) => a.name.localeCompare(b.name))
var currentHeader
let final = sortedUsers.reduce((a, user) => {
if (currentHeader !== user.name[0]) {
currentHeader = user.name[0]
a.push({header: currentHeader})
}
a.push(user)
return a
},[])
console.log(final)
Here's one way to do it:
const users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
let lastIndex;
let result = [];
users.sort((a, b) => {
return a.name > b.name;
}).forEach((user) => {
const index = user.name.charAt(0);
if (index !== lastIndex) {
result.push({
header: index
});
}
lastIndex = index;
result.push(user.name);
}, []);
console.log(result);
You can use _.orderBy(collection, [iteratees=[_.identity]], [orders]) and _.groupBy(collection, [iteratee=_.identity]) method of lodash.
This orderBy is like _.sortBy except that it allows specifying the sort orders of the iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. Otherwise, specify an order of "desc" for descending or "asc" for ascending sort order of corresponding values.
groupBy will creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value).
example
// The `_.property` iteratee shorthand.
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
// Sort by `user` in ascending order and by `age` in descending order.
_.orderBy(users, ['user', 'age'], ['asc', 'desc']);
With lodash
let myArr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
myArr = _.orderBy(myArr, ['name'], ['asc']);
let r = _.groupBy(myArr, o => {
return o.name.charAt(0).toUpperCase();
})
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
IN ES5
var arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}],
fChar = '';
arr = arr.sort(function(a, b) {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce(function(r, o) {
fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(arr);
IN ES6
const arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
let result = arr.sort((a, b) => {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce((r, o) => {
let fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(result);

Categories