I have an array of strings which are tags of blog posts from database.
This is the example end result of a query :
["apple","banana", "apple", "orange","grapes","mango","banana"];
I need to know how many times a string is repeated in that array so I can build a sort of tag cloud.
Final end result should look like: [{name:"apple",count:2}, {name:"banana", count:2}, {name: "orange",count:1} ...];
I am using lodash in my project and would like to use it if possible. plain javascript is also fine.
You can use groupBy to do the heavy lifting then use map to format the result to your liking:
const data = ["apple", "banana", "apple", "orange", "grapes", "mango", "banana"];
const result = _.values(_.groupBy(data)).map(d => ({name: d[0], count: d.length}));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Use reduce and map
var input = ["apple","banana", "apple", "orange","grapes","mango","banana"];
var map = input.reduce( (a,c) => ( a[c] = a[c] || 0, a[c]++, a ) ,{});
var output = Object.keys( map ).map( s => ({name: s, count:map[s]}) );
Demo
var input = ["apple", "banana", "apple", "orange", "grapes", "mango", "banana"];
var map = input.reduce((a, c) => (a[c] = a[c] || 0, a[c]++, a), {});
var output = Object.keys(map).map(s => ({
name: s,
count: map[s]
}));
console.log(output);
Create a map, mapping name to count. Loop over the list, for each item, if its' in the map, increment it's count, else set its count to one.
if (tag in map) { map[tag] += 1 }
else { map[tag] = 1 }
Then you can iterate of the map and convert it to a list of objects.
To count use _.countBy(), then transform to requested form, by using _.entries(), and mapping the tuples to objects:
const data = ["apple", "banana", "apple", "orange", "grapes", "mango", "banana"];
const result = _.entries(_.countBy(data)).map(([name, count]) => ({ name, count }));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Related
I have input array as follows. I want to get the unique occurrences name and frequency of those occurrences. I am able to achieve that as shown below.
let input = ["apple", "orange" , "pear", "orange", "apple", "orange"];
input.reduce(function (acc, curr) {
return acc[curr] ? ++acc[curr] : acc[curr] = 1, acc
}, {});
Result:
{ "apple": 2, "orange": 3, "pear": 1}
But I am expecting the result to be in ascending order of frequencies as shown below. Can someone let me know how to achieve it. Also, I have used function above. Can someone let me know how to use it with arrow operator (ES8 feature)
Expected Result:
{ "orange": 3, "apple": 2, "pear": 1 }
You can convert the unsorted object, which is not sortable, to an array, which is sortable, and then back to an object:
let input = ["apple", "orange" , "pear", "orange", "apple", "orange"];
const unsorted = input.reduce(function (acc, curr) {
return acc[curr] ? ++acc[curr] : acc[curr] = 1, acc
}, {});
const sorted = Object.entries(unsorted) // converts to array of key/value pairs
.sort(([,a],[,b]) => b - a) // sort descending (switch a and b to sort in reverse order)
.reduce((r, [k, v]) => ({ ...r, [k]: v }), {}); // reduce back to object
console.log(sorted)
Let's say I have a data set that looks like below:
const fruits = [
{ fruit: "banana", price: 100 },
{ type: "apple", price: 200 },
{ item: "grape", price: 150 },
// And so on...
];
const rotten = ["banana", "orange", /* And so on...*/];
I need to filter out elements that contain one of the values in 'rotten' array from 'fruits' array.
Some characteristics:
The key names in the 'fruits' objects are inconsistent. We just know that the corresponding string values are there. I know it is suboptimal but fixing this is sadly not a near-term option.
The actual length of 'fruits' is ~100 and that of 'rotten' is ~10. So this is not a small dataset. (There is a context for saying this...)
I already tried going through each element in 'rotten' and use Object.values(obj).includes() for each element in 'fruits' to eventually create a filtered array, but considering the size of the dataset, this is expensive and I will need a better way to handle this.
What might be a better way to do this? Any suggestions? Thanks a lot in advance!
It is unusual (and not practical) that your input objects all use different properties for storing the same information (fruit, type, item...). So, you should really improve the source of this data.
But given it is like this, you'll have to scan each property of each object and compare it with the rotten fruit names. The latter can be put in a Set, which will reduce the time complexity of the search:
const fruits = [
{ fruit: "banana", price: 100 },
{ type: "apple", price: 200 },
{ item: "grape", price: 150 },
];
const rotten = ["banana", "orange"];
let rottenSet = new Set(rotten);
let result = fruits.filter(item =>
!Object.values(item).some(value => rottenSet.has(value))
);
console.log(result);
You can try this.
const fruits = [
{ fruit: "banana", price: 100 },
{ type: "apple", price: 200 },
{ item: "grape", price: 150 }
];
const rotten = ["banana", "orange"];
const stringfyFruites = JSON.stringify(fruits);
let filterdArray = [];
rotten.forEach((value) => {
const regexstr = '{[\\w":,\\s]+' + value + '[\\w":,\\s]+}';
const matchedobject = stringfyFruites
.match(new RegExp(regexstr, "gm"))
?.map((a) => JSON.parse(a));
if (matchedobject?.length) filterdArray = [...filterdArray, ...matchedobject];
});
I have a JSON response as below:
[{
"id": 1,
"food": {
"fruits": ["Banana", "Orange", "Apple", "Mango"],
"veggies": ["greens", "peppers", "carrot", "potatoes"],
}
},
{
"id": 2,
"food": {
"fruits": ["grapes", "berries", "peach", "pears"],
"veggies": ["cabbage", "spinach"],
"dairy": ["nutmilk", "goatmilk"]
}
}
]
Now i want to merge the Arrays each "id" (1,2 in example) into string ( ; delimited) like below:
id_1 = Banana;Orange;Apple;Mango;greens;peppers;carrot;potatoes
// observer "id_2" has additional array - "dairy"
id_2 = grapes;berries;peach;pears;cabbage;spinach;nutmilk;goatmilk
The key's are dynamic so for some records there are 2 arrays and for some records it can be 3 or 4 and may be 1.
I tried using react/Java Script Array.concat(), but i am not sure how to do it dynamically. Please help me. Thank you.
This is doable easily using Object.values().flat().join(';') demonstrated below:
let arr=[{"id":1,"food":{"fruits":["Banana","Orange","Apple","Mango"],"veggies":["greens","peppers","carrot","potatoes"],}},{"id":2,"food":{"fruits":["grapes","berries","peach","pears"],"veggies":["cabbage","spinach"],"dairy":["nutmilk","goatmilk"]}}];
const result = arr.map(({id,food}) => ({id, food: Object.values(food).flat().join(';')}));
console.log(result);
You may easily restructure the output by simply changing it to e.g. ["id_"+id]: Object.values(...)
First flatten using map, flat and join. Then convert the resulting array of objects to a single object using assign.
var db = [{"id": 1,"food": {"fruits": ["Banana", "Orange", "Apple", "Mango"], "veggies": ["greens","peppers","carrot","potatoes"], }},{"id" : 2,"food": {"fruits": ["grapes", "berries", "peach", "pears" ], "veggies": ["cabbage","spinach"], "dairy": ["nutmilk","goatmilk"]}}];
var flat = db.map(
({id, food}) => ({[`id_${id}`]: Object.values(food).flat().join(';')})
);
var result = Object.assign(...flat);
console.log(result);
This is really two problems: looping through an array of objects to combine them into one object, and looping through an object to concat all of its array.
Tackling the second one first, something like this would work:
const concatArraysInObject = obj =>
Object.values(obj).reduce((result, arr) => result.concat(arr), []);
const input = { a: [1,2,3], b: [4,5,6], c: [7,8,9] };
const output = concatArraysInObject(input);
console.log(output);
Object.values() will give you an array of all arrays in an object.
The reduce() function takes a two parameters: a function and initial value.
The function should also take (at least) 2 parameters: the result of the last call (or initial value) and the current value in the array.
It'll call the function once for each element in the array.
Now, with that solved, we can tackle the first problem.
For this, we can also use reduce() as well, and we'll construct our combined object on each call.
const concatArraysInObject = (obj) =>
Object.values(obj).reduce((result, arr) => result.concat(arr), []);
const mergeObjectsInArray = (arr, dataKey) =>
arr.reduce((result, obj) => ({ ...result, [obj.id]: concatArraysInObject(obj[dataKey]) }), {});
const input = [
{ id: 'A', data: { a: [1,2,3], b: [4,5,6] } },
{ id: 'B', data: { c: [7,8,9], d: [10,11,12] } }
];
const output = mergeObjectsInArray(input, 'data');
console.log(output);
An important note of warning: object key order is NOT guaranteed in JavaScript. While 99% of the time they will be in the order you expect, this is not a guarantee, so if you depend on the order of the keys for the order of the array (if order matters), you'll want to change your input structure. If order doesn't matter, it is probably fine how it is.
Try this using basic for loop. Inside you will compute key dynamically and value being flattened array of Object.values of the iterating object.
var input = [{
id: 1,
food: {
fruits: ["Banana", "Orange", "Apple", "Mango"],
veggies: ["greens", "peppers", "carrot", "potatoes"]
}
},
{
id: 2,
food: {
fruits: ["grapes", "berries", "peach", "pears"],
veggies: ["cabbage", "spinach"],
dairy: ["nutmilk", "goatmilk"]
}
}
];
var temp = [];
for (var i = 0; i < input.length; i++) {
temp.push({
[`id_${input[i].id}`]: Object.values(input[i].food)
.flat(1)
.join(";")
});
}
console.log(temp); // this gives you an array
console.log(Object.assign(...temp));// in case you require one single object
I have a bothersome length of characters before all keys in this object. Since they are all the same, I would like to do a .map() or forEach() or something with a .slice() in it to remove the first n characters. How is this done to all keys in the object?
I should say that we are already importing Lodash in the project so I can use that.
So I need to turn this:
{
'remove.this.string.a': "apple",
'remove.this.string.b': "banana",
'remove.this.string.c': "carrot",
'remove.this.string.d': "diakon"
}
and turn it into:
{
"a": "apple",
"b": "banana",
"c": "carrot",
"d": "diakon"
}
Use object.entries to get the keys and values. Loop over changing the key.
Changing the object directly
var obj = {
'remove.this.string.a': "apple",
'remove.this.string.b': "banana",
'remove.this.string.c': "carrot",
'remove.this.string.d': "diakon"
}
// Object.entries(obj).forEach(function(arr) {
// var key = arr[0]
// var value = arr[1]
// delete obj[key]
// obj[key.split(".").pop()] = value
// })
Object.entries(obj).forEach(([key, value]) => {
delete obj[key]
obj[key.split(".").pop()] = value
})
console.log(obj)
or reduce to create a new object
var obj = {
'remove.this.string.a': "apple",
'remove.this.string.b': "banana",
'remove.this.string.c': "carrot",
'remove.this.string.d': "diakon"
}
// const updated = Object.entries(obj).forEach(function(obj, arr) {
// var key = arr[0]
// var value = arr[1]
// obj[key.split(".").pop()] = value
// return obj
// }, {})
const updated = Object.entries(obj).reduce((obj, [key, value]) => {
obj[key.split(".").pop()] = value
return obj
}, {})
console.log(updated)
Another approach that avoinds the need for Object.fromEntries(), would be to use Array.reduce() as shown:
const input = {
'remove.this.string.a': "apple",
'remove.this.string.b': "banana",
'remove.this.string.c': "carrot",
'remove.this.string.d': "diakon"
};
const output = Object.entries(input).reduce((result, [key, value]) => {
/* Pluck last letter of key string */
const letter = key.slice(-1);
/* Insert letter key/value into result object */
return { ...result, [letter] : value };
}, {});
console.log(output);
If you've already got lodash, _.mapKeys is what you're looking for. Here's an example of what you asked for directly (to just slice to 19 characters), but you could easily do a split or replace or whatever else you'd like:
var _ = require('lodash')
let data = {
'remove.this.string.a': "apple",
'remove.this.string.b': "banana",
'remove.this.string.c': "carrot",
'remove.this.string.d': "diakon"
}
_.mapKeys(data, (val, key) => key.slice(19))
Here's a runkit:
https://runkit.com/daedalus28/slice-keys
let obj = {
'remove.this.string.a': "apple",
'remove.this.string.b': "banana",
'remove.this.string.c': "carrot",
'remove.this.string.d': "diakon"
};
let transformed = Object.entries(obj).reduce((t, [key, value]) => {
t[key.substr(19)] = value;
return t;
}, {});
console.log(transformed);
You could use the new Object.fromEntries along with Object.entries:
let remove = {
this: {
string: {}
}
}
remove.this.string.a = "apple"
remove.this.string.b = "banana"
remove.this.string.c = "carrot"
remove.this.string.d = "diakon"
console.log(remove.this.string)
let fixed = Object.fromEntries(
Object.entries(remove.this.string)
.map(([key, val]) => [key, val])
)
console.log(fixed)
Result:
{ a: 'apple', b: 'banana', c: 'carrot', d: 'diakon' }
Update:
For keys that are all one string:
let remove = {
'remove.this.string.a': 'apple',
'remove.this.string.b': 'banana',
'remove.this.string.c': 'carrot',
'remove.this.string.d': 'diakon'
}
let fixed = Object.fromEntries(
Object.entries(remove)
.map(([key, val]) => [key.replace('remove.this.string.', ''), val])
)
console.log(fixed)
Result:
{ a: 'apple', b: 'banana', c: 'carrot', d: 'diakon' }
I want to add an object and delete two objects in a function
const old1 = [
{item: "apple", number: 13}
]
const old2 = [
{item: "apple", number: 13},
{item: "banana", number: 11},
{item: "orange", number: 13}
]
First, I want to add an object in the array
const add = {item: "pear", number: 3}
Then I want to check if the array has these elements, if yes, then remove them. Here I want to remove anything "banana" and "orange"
const new2 =[
{item: "pear", number: 3},
{item: "apple", number: 13}
]
I tried old1.unshift to add an element.
I also tried old2.splice(0,2) to remove elements but it is based of the index order. I should check the item property and remove the relative one.
You can use Array.push to add the element:
old1.push({item: "pear", number: 3});
And for removing based on a condition - you could put the values you want to remove in an array, then run an Array.filter
let itemsToRemove = ["banana", "orange"]
let filteredItems = old2.filter(o => !itemsToRemove.includes(o.item));
For an array of objects I use code similar to this to add/update them.
addUpdateItem(item, arr) {
let ind = arr.map(i => i.id).indexOf(item.id) //find the position of element if exists
if(ind != -1) { //this means the item was found in the array
//normally I would want to update the item if it existed
Object.assign(arr[ind], item) //update the item
//but if you wanted to remove the item instead
arr.splice(ind, 1) //removes the item
} else {
arr.push(item)
}
}