How to group javascript array based on some property? - javascript

I have a JS array of objects like this:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
lines are unique in entire myArray , each line has some texts (which are not unique).
How to match each text to its corresponding lines?
The final result should be like this:
var myNewArray = [
{ text: 31, line: [20] },
{ text: 80, line: [20, 10] },
{ text: 22, line: [10] }
]

Some approaches with Map.
As result you get a temporary map which collects all text, grouped by line. To get an array of objects, map the key/values pairs as eanted properties.
Because of having nested array of the data, you need eiter to normalize the data to get single line/text values and then add a grouping by text,
const
data = [{ line: 20, text: [31, 80] }, { line: 10, text: [80, 22] }],
result = Array.from(
data
.flatMap(({ line, text }) => text.map(text => ({ text, line })))
.reduce((m, { text, line }) => m.set(text, [...(m.get(text) || []), line]), new Map),
([text, line]) => ({ text, line })
);
console.log(result);
Or do it in a single step but with a nested approach of reducing the outer (line) and inner arrays (text arrays).
const
data = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
],
result = Array.from(
data.reduce(
(m, { line, text }) =>
text.reduce(
(n, text) => n.set(text, [...(n.get(text) || []), line]),
m
),
new Map
),
([text, line]) => ({ text, line })
);
console.log(result);

Here's how:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
var newArray = myArray.reduce((acc, {line, text}) => {
for( let t of text ){
const match = acc.find(({text}) => text == t) // check if the text already exists in newArray
if( match ) match.lines.push(line) // if exists, add the line to that text
else acc.push({text:t, lines:[line]}) // it not, create a new object with that line
}
return acc
}, [])
console.log( newArray )
Or by first generating an Object instead of an Array, which is faster if your dataset is huge, and then convert that to an Array at the end:
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
// generate a key/value pairs for text/lines
var newArray = myArray.reduce((acc, {line, text}) => {
for( let t of text )
acc[t] = [...(acc[t] || []), line]
return acc
}, {})
// convert the above Object to an Array of Objects (AKA Collection)
newArray = Object.entries(newArray).map(([text,lines]) => ({text, lines}))
console.log( newArray )

Probably easiest by first building an intermediate Map that indexes lines by text:
const data = [
{line: 20, text: [31,80]},
{line: 10, text: [80,22]}
];
const result = [...data.reduce((map, {line, text}) => {
text.forEach(t => {
map.has(t) || map.set(t, []);
map.get(t).push(line);
});
return map;
}, new Map()).entries()].map(([text, line]) => ({text, line}));
console.log(result);

Here is a simple solution to your problem.
var myArray = [
{ line: 20, text: [31, 80] },
{ line: 10, text: [80, 22] }
]
var myNewArray = []
myArray.forEach((item) => {
item.text.forEach((t) => {
const index = myNewArray.findIndex((e => e.text === t))
index === -1
? myNewArray.push({text: t, line: [item.line]})
: myNewArray[index].line.push(item.line)
})
})
console.log(myNewArray)

Related

How can we print value from an array if two array matches with some value in JavaScript?

I have to compare two array of objects and check if the same item includes in the other array.
const array1 = [
{ item: "orange", id: 11 },
{ item: "apple", id: 12 },
];
const array2 = [10, 11, 12];
If I am checking with a value from array2 in array1.How can I get output like below when mapping array2?
Item does'nt exists
orange
apple
Here's a way using Map, though with a bit of tweaking it could use a normal object instead of Map. The map indexes the items in array1 by id and then allows you to look for the item in the map (using has() or get()); if it doesn't exist you can fall back to the default string. ?? is the nullish coalescing operator and it allows you to give a term for the expression should the value before ?? be null or undefined.
const array1 = [{ item: "orange", id: 11 }, { item: "apple", id: 12 }];
const array2 = [10, 11, 12];
const default_string = "Item does'nt exists";
const array1_map = new Map(array1.map((o) => [o.id, o]));
const res = array2.map( (id) => array1_map.get(id)?.item ?? default_string );
console.log(res);
Try this
const array1 = [{
item: 'orange',
id: 11
}, {
item: 'apple',
id: 12
}]
const array2 = [10, 11, 12]
for (const x of array2) {
//find element from array
const searchElem = array1.find((item) => item.id == x)
if (searchElem) {
console.log(searchElem.item + " for " + x)
} else {
console.log("Item doesn't exists for", x)
}
}
You can easily achieve this result using map and find
const array1 = [
{ item: "orange", id: 11 },
{ item: "apple", id: 12 },
];
const array2 = [10, 11, 12];
const result = array2.map((id) => {
const itemFound = array1.find((o) => o.id === id);
if (itemFound) return `${itemFound.item} for ${itemFound.id}`;
else return `Item does'nt exists for 10`;
});
console.log(result);
The easiest way to get common items by comparing two array in JavaScript is using filter() and includes() array functions.
For example:
const array1 = [
{ item: "orange", id: 11 },
{ item: "apple", id: 12 },
];
const array2 = [10, 11, 12];
const matchedArray = array1.filter((item) => array2.includes(item.id));
console.log(matchedArray);
We don't need to use for loop, forEach, map and find functions.
Try this code I think it will solve your problem
I have written some comments on javascript code it may help you.
const array1=[{item:'orange', id:11},{item:'apple', id:12}]
const array2=[10,11,12]
function removeDuplicates(array, matchKey) {
return array.filter((value, index) => {
return array.indexOf(array.find(value_1 => value_1[matchKey] == value[matchKey])) == index
});
}
// send "id" parameter if you want to filter array by id
// ex: removeDuplicates(array1, 'id')
const filteredArray1 = removeDuplicates(array1, 'item');
array2.forEach(id => {
let foundItem = array1.find(itemObj => itemObj.id == id);
if(foundItem == null) {
console.log(`Item doesn't exists for ${id}`);
}else {
console.log(`${foundItem.item} for ${id}`);
}
});
If I understand the question correctly what you want to do is confirm if an object with a certain property exists in Array 1 for each element in Array 2.
If you want to check each and see if it exists you can do this:
const fruits = [
{item: 'orange', id: 11},{item: 'apple', id: 12}
]
const fruitIds = fruits.map((fruit) => fruit.id);
const ids = [10, 11, 12]
ids.forEach((id) => {
console.log(`${id} exists in fruits: ` + fruitIds.includes(id))
})
Or if you wish to check if there is a fruit for each ID in the array and you only care about true / false if all exist or not then you can do:
const fruits = [
{item: 'orange', id: 11},
{item: 'apple', id: 12}
]
const fruitIds = fruits.map((fruit) => fruit.id).sort();
const ids = [10, 11, 12];
console.log("There is a fruit for each ID in ID array: ", JSON.stringify(ids) === JSON.stringify(fruitIds))
If this does not answer your question then please edit and try to make your question clearer and I'll do the same with my answer.
Note that the last snippet is just one way to compare arrays or objects in JavaScript.

Data transformation horizontal to vertical javascript

I need help in transforming data in a particular way to plot a graph. The data which I get from API is a different format. Please guide me on how to transform it
const demo = [
{
label: 'ABC',
vMini: 28,
vMaxi: 56,
dMini: 2,
dMaxi: 50,
},
{
label: 'BCD',
vMini: 2,
vMaxi: 56,
dMini: 3,
dMaxi: 50,
},
];
end result which i want is
[
{
section: "vMini",
"ABC": 28,
"BCD": 2,
},
{
section: "vMaxi",
"ABC": 56,
"BCD": 56
}
{
section: "dMini",
"ABC": 2,
"BCD": 3,
},
{
section: "dMaxi",
"ABC": 50,
"BCD": 50
}
]
I have started working on it and got confused with second loop.
for (let i = 0; i < demo.length; i += 1) {
for (let j in demo[i]) {
if (j === 'label') {
}
}
}
This one is a bit tricky with the way the data is structured, but you should be able to do this with array.reduce, like so:
const demo = [{label:"ABC",vMini:28,vMaxi:56,dMini:2,dMaxi:50},{label:"BCD",vMini:2,vMaxi:56,dMini:3,dMaxi:50}];
// get array of keys, and create a new object for each one except label
// ["label", "vMini", "vMaxi", "dMini", "dMaxi"]
let results = Object.keys(demo[0]).reduce((res, key) => {
if (key === "label") { return res; }
else {
// for each item in demo, create a key for the label and grab the key's value
let newObj = demo.reduce((_res, obj) => {
_res[obj.label] = obj[key];
return _res;
}, {section: key})
// push the new object into the results array
res.push(newObj);
}
return res;
}, [])
console.log(results);
Using reduce() and Map()
const demo = [{label:"ABC",vMini:28,vMaxi:56,dMini:2,dMaxi:50},{label:"BCD",vMini:2,vMaxi:56,dMini:3,dMaxi:50}];
const resMap = demo.reduce((a, v) => {
let label = v.label
for (let k in v) {
if (k == 'label') continue
a.has(k) || a.set(k, { section: k })
let o = a.get(k)
o[label] = v[k]
}
return a
}, new Map())
const resArr = [...resMap.values()]
console.log(resArr)

How to build an inverted map from a one-to-many list with functional programming in JavaScript?

Just to clarify this is what I mean by "inverted map":
const foo =
{ "a": 10
, "b": 20
};
const foo_inverted =
{ "10": "a"
, "20": "b"
};
I have this object representing a file:
const file =
{ id: 100
, tags: [20, 30]
};
Given a list of files I need to build a map that allows me to find all files with a given tag.
From this:
const files =
[ { id: 100
, tags: [20, 30]
}
, { id: 200
, tags: [20, 40]
}
];
To that:
{ "20": { "100": 1, "200": 1 }
, "30": { "100": 1 }
, "40": { "200": 1 }
}
I ended up with this code which does the job:
const tag_file = (tag_id, file_id) => ({[tag_id]: {[file_id]: 1}});
const mergeDeepAll = reduce(mergeDeepRight, {});
const tag_map = compose(mergeDeepAll, lift(tag_file));
const tags_map = compose(mergeDeepAll, map(({id, tags}) => tag_map(tags, [id])));
tags_map(files);
//=> { "20": { "100": 1, "200": 1 }
//=> , "30": { "100": 1 }
//=> , "40": { "200": 1 }
//=> }
Question: am I missing any functional programming concepts that would have allowed me to express this better?
Create an a function that generates pairs [tag, id] for each object, using a Array.map() (idByTags). Using R.chain convert all objects to such pairs and flatten them. Group by the tag (R.head), and then map the object (R.mapObjIndexed) and count by the id (R.last):
const { pipe, chain, groupBy, head, mapObjIndexed, countBy, last } = R
const idByTags = ({ id, tags }) => tags.map(tag => [tag, id])
const fn = pipe(
chain(idByTags),
groupBy(head),
mapObjIndexed(countBy(last))
)
const files = [{"id":100,"tags":[20,30]},{"id":200,"tags":[20,40]}]
const result = fn(files)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
not sure why you would need ramda, can do it with reduce and forEach
const files = [{
id: 100,
tags: [20, 30]
}, {
id: 200,
tags: [20, 40]
}];
// loop over the array to make an object
const result = files.reduce((obj, file) => {
// loop over the tags
file.tags.forEach(
tag =>
obj[tag] ? // have we seen the tag?
obj[tag].push(file.id) : // yes
obj[tag] = [file.id] // no
)
return obj // return the object for reduce
}, {})
console.log(result)
AFTER YOUR EDIT
const files = [{
id: 100,
tags: [20, 30]
}, {
id: 200,
tags: [20, 40]
}];
// loop over the array to make an object
const result = files.reduce((obj, file) => {
// loop over the tags
file.tags.forEach(
tag => {
obj[tag] = obj[tag] || {} // have we seen the tag?
obj[tag][file.id] = 1 //
})
return obj // return the object for reduce
}, {})
console.log(result)

lodash convert array of objects to single array of keys and multiple array of values

I need to transmit some data, that has too many key-value pairs.
As the keys are similar, I dont want to transmit them with each object.
Consider I have the following data:
[
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
];
And I need the final output as
[ [x,y],[[11,12],[21,22],[31,32],[41,42]] ] OR
[ [x,y],[11,12],[21,22],[31,32],[41,42] ]
On the other end, I should be able to convert back to its original form.
It would be great if it can handle an additional key in some of the objects
I think I have seen lodash or underscore function for something close/similar to this, but I'm not able to find it right now.
NOTE: I don't know what the keys will be
Lodash v4.17.1
modify original
var modifiedOriginal = _.chain(original)
.map(_.keys)
.flatten()
.uniq()
.thru(function(header){
return _.concat(
[header],
_.map(original, function(item) {
return _.chain(item)
.defaults(_.zipObject(
header,
_.times(_.size(header), _.constant(undefined))
))
.pick(header)
.values()
.value()
})
);
})
.value();
modified back to original (keys order is not
guarantee)
var backToOriginal = _.map(_.tail(modified), function(item) {
return _.chain(_.head(modified))
.zipObject(item)
.transform(function(result, val, key) {
if (!_.isUndefined(val)) {
result[key] = val;
}
})
.value();
});
JSFiddle code https://jsfiddle.net/wa8kaL5g/1/
Using Array#reduce
var arr = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
var keys = Object.keys(arr[0]);
var op = arr.reduce(function(a, b) {
var arr = keys.reduce(function(x, y) {
return x.concat([b[y]]);
}, [])
return a.concat([arr]);
}, [keys]); //If all the objects are having identical keys!
console.log(JSON.stringify(op));
A little more verbose way of doing it:
[Edit: added the function to convert it back]
function convert(arr) {
var retArr = [ [/* keys (retArr[0]) */], [/* values (retArr[1]) */] ]
arr.forEach(function(obj){
// create new array for new sets of values
retArr[1].push([])
// put all of the keys in the correct array
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// does the key exist in the array yet?
if (retArr[0].indexOf(key) === -1) {
retArr[0].push(key)
}
// get last index of retArr[1] and push on the values
retArr[1][retArr[1].length - 1].push(obj[key])
}
}
})
return retArr
}
function reConvert(arr) {
var retArr = []
var keys = arr[0]
arr[1].forEach(function(itemArr){
var obj = {}
itemArr.forEach(function(item, i){
obj[keys[i]] = item
})
retArr.push(obj)
})
return retArr
}
var objArr = [
{
x:11,
y:12
},{
x:21,
y:22
},{
x:31,
y:32
},{
x:41,
y:42
}
]
var arrFromObj = convert(objArr)
var objFromArr = reConvert(arrFromObj)
console.log(arrFromObj)
console.log(objFromArr)
A solution using Underscore.
First work out what the keys are:
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
Then map across the data to pick out the value for each key:
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
and back again:
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
Lodash's version of object is zipObject and omit using a predicate is omitBy:
var thereAndBackAgain = _.map(result[1], item => _.omitBy(_.zipObject(result[0], item), _.isUndefined));
var data = [
{
x:11,
y:12,
aa: 9
},{
x:21,
y:22
},{
x:31,
y:32,
z: 0
},{
x:41,
y:42
}
];
var keys = _.chain(data)
.map(_.keys)
.flatten()
.uniq()
.value();
var result = [
keys,
_.map(data, item => _.map(keys, key => item[key]))
];
var thereAndBackAgain = _.map(result[1], item => _.omit(_.object(result[0], item), _.isUndefined));
console.log(result)
console.log(thereAndBackAgain)
<script src="
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
In ES6 you can do it by reducing it with Object.values(), and Object.keys(). You can restore it using a combination of Array.prototype.map() and Array.prototype.reduce():
const convertStructure = (data) => data.reduce((s, item) => {
s[1].push(Object.values(item));
return s;
}, [Object.keys(data[0]), []]); // all objects should be the same, so we can take the keys from the 1st object
const restoreStructure = ([keys, data]) => data.map((item) => item.reduce((o, v, i) => {
o[keys[i]] = v;
return o;
}, {}));
const data = [{
x: 11,
y: 12
}, {
x: 21,
y: 22
}, {
x: 31,
y: 32
}, {
x: 41,
y: 42
}];
const convertedStructure = convertStructure(data);
console.log('convertedStructure:\n', convertedStructure);
const restoredStructure = restoreStructure(convertedStructure);
console.log('restoredStructure:\n', restoredStructure);

UnderscoreJs: flatten array of objects

There is an array of objects
[
{a:1,val:[11,12]},
{a:9,val:[21,22]},
{a:7,val:[31,32]},
{a:8,val:[41,42]}
]
I am trying to convert it into:
[ [{a:1,val:11},{a:9,val:21},{a:7,val:31},{a:8,val:41}] ,
[{a:1,val:12},{a:9,val:22},{a:7,val:32},{a:8,val:42}]
]
How can I use underscore.js chain/map/pluck etc... function to get the flatten result in specified format in the cleanest way?
You could use Array#forEach and build the nested parts upon.
var data = [{ a: 1, val: [11, 12] }, { a: 9, val: [21, 22] }, { a: 7, val: [31, 32] }, { a: 8, val: [41, 42] }],
result = [];
data.forEach(function (a, i) {
a.val.forEach(function (b, j) {
result[j] = result[j] || [];
result[j][i] = { a: a.a, val: b };
});
});
console.log(result);
You can use array's reduce like this
var data = [
{a:1,val:[11,12]},
{a:9,val:[21,22]},
{a:7,val:[31,32]},
{a:8,val:[41,42]}
]
var result = data.reduce((res, next) => {
res[0].push({a: next.a, val: next.val[0]});
res[1].push({a: next.a, val: next.val[1]});
return res;
}, [[], []])
console.dir(result)
I have done as you have requested but used plain ES6 instead of underscore.
var restructure = (x)=>
[x.map(({a,val})=>({a,val:val[0]})),x.map(({a,val})=>({a,val:val[1]}))]
var result = restructure([
{a:1,val:[11,12]},
{a:9,val:[21,22]},
{a:7,val:[31,32]},
{a:8,val:[41,42]}
])
//[[{"a":1,"val":11},{"a":9,"val":21},{"a":7,"val":31},{"a":8,"val":41}],[{"a":1,"val":12},{"a":9,"val":22},{"a":7,"val":32},{"a":8,"val":42}]]
Here's a solution using underscore:
var result = _.chain(data)
.map(item => _.map(item.val, val => ({a: item.a, val})))
.unzip()
.value();
var data = [
{a:1,val:[11,12]},
{a:9,val:[21,22]},
{a:7,val:[31,32]},
{a:8,val:[41,42]}
]
var result = _.chain(data)
.map( item => _.map(item.val, val => ({a: item.a, val})))
.unzip()
.value();
document.getElementById('result').textContent = JSON.stringify(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore.js"></script>
<p>
<pre id="result"></pre>
</p>

Categories