I have an array like this
students = [{name: 'Abbey', age: 25}, {name: 'Brian', age: 45},
{name: 'Colin', age: 25}, {name: 'Dan', age: 78}]
and I want the output to be;
uniqueAges = [45, 78]
To be clear, if there is an age value that appears more than once in the students array, I do not want any of the objects with that age in my uniqueAges array. 'Abbey' and 'Colin' have the same age so they are both out.
I know I can do something like this and run uniqueAgeGetter(students)
function uniqueAgeGetter(list){
var listCopy = list.slice();
var uniqueAges = list.slice();
for (var i = list.length - 1; i >= 0; i--) {
for (var j = listCopy.length - 1; j >= 0; j--) {
if(listCopy[j].name !== list[i].name &&
listCopy[j].age == list[i].age){
uniqueAges.splice(i, 1)
}
}
}
console.log(uniqueAges)
return uniqueAges
}
But is it possible to do it without a second loop? I'm not an expert on time complexity but I am trying to find if it is possible this task can be O(n).
Edit:
I am not asking if uniqueAgeGetter be rewritten to read nicer or use functions like map, reduce or filter (as my understanding is they are ultimately a loop as well).
My question is can uniqueAgeGetter be refactored in a way that reduces the time complexity? Can it be done with only one loop?
Thank you.
This can be done in O(n) time by counting the number of times an age has been seen, and filtering out the ages with a count more than one.
Since ages have reasonable limits, we can use an integer array of length equal to the maximum possible age to store the age counts. In the example below, I take the maximum possible age to be a comfortable 200.
var students = [
{name: 'Abbey', age: 25 },
{name: 'Brian', age: 45 },
{name: 'Colin', age: 25 },
{name: 'Dan', age: 78 }
];
var studentAges = students.map(val => val.age);
var ageCounts = Array(200).fill(0);
studentAges.forEach(age => ageCounts[age] += 1);
var uniqueAges = studentAges.filter(age => ageCounts[age] == 1);
console.log(uniqueAges);
The first idea, we can do over two step:
Step1: Sort the array
-- There are many algorithms to do it. As I know, currently, the complexity of best algorithm now is O(Nlog(N)) with N is the number of array.
Step2: Remove the duplicated elements
-- The complexity of this step is O(N)
So, over two steps, the complexity is O(N) + O(Nlog(N)). Finally, the complexity is O(Nlog(N))
The second idea
This also has the complexity is O(Nlog(N)) but it will be O(N) for next time you want to get the unique age.
Instead of saving the data in array, you can rebuild in a binary search tree with a little custom. This node in this tree will save all the elements with same age.
The complexity for the first time you build the tree is O(Nlog(N))
About the algorithm which has the complexity is O(N), currently, I think there are no technique to solve it. :D
You can use reduce
The first reduce is to summarise the array and convert it into an object using the age as a the key. Using the age as the key will make it easier to check if the age already exist. The object properties will have an array value like [2,true], where the first element is the age and the second element tells if the age has duplicates. Using Object.values will convert the object into an array.
The second reduce is to form the desired output.
let students = [{name: 'Abbey', age: 25 }, {name: 'Brian', age: 45 },{name: 'Colin', age: 25 }, {name: 'Dan', age: 78 }];
let uniqueAges = Object.values(students.reduce((c, v) => {
if (c[v.age] === undefined) c[v.age] = [v.age, true];
else c[v.age][1] = false;
return c;
}, {})).reduce((c, v) => {
if (v[1]) c.push(v[0]);
return c;
}, []);
console.log(uniqueAges);
Here is one way you could do it. I think the time complexity would be O(n^2) where n is the number of elements in the original array and m is the number of unique elements in the output array.
const students = [
{name: 'Abbey', age: 25 },
{name: 'Brian', age: 45 },
{name: 'Colin', age: 25 },
{name: 'Dan', age: 78 }
];
const uniqueStudents = students.map(val => val.age)
.sort()
.reduce((current, next) => {
return current.length === 0 ? [].concat(current, next)
: current[current.length - 1] !== next ? [].concat(current, next)
: current.slice(0, -1);
}, []);
console.log(uniqueStudents);
🚀 The fastest way with a single iteration.
const students = [
{name: `Abbey`, age: 25},
{name: `Brian`, age: 45},
{name: `Colin`, age: 25},
{name: `Dan`, age: 78},
{name: `Dan`, age: 25}
]
// no global variables
function unique(key) {
const occurrences = {}
const list = {}
return (result, next, index, {length}) => {
const value = next[key]
if (list[value]) {
occurrences[value] = value
}
else {
list[value] = value
result.push(value)
}
return index === length - 1 ? result.filter(v => !occurrences[v]) : result
}
}
const uniqueNames = students.reduce(unique(`name`), [])
const uniqueAges = students.reduce(unique(`age`), [])
console.log(uniqueAges)
for getting unique elements
const data = [128, 128,128,121,127,127,121,121,121,121,122,125];
const uniqueData = Object.keys(data.reduce((r,c) => {r[c] = true; return r;}, {}))
console.log(uniqueData)
But doing this will sort the array and will not keep the original order of the array
Complexity O(n)
Related
I currently have the following items and implementation below. I am not sure if using reduce within another reduce is performant. Is there a better way to sum up nested arrays?
const items = [
{
id: 111,
fruits: [
{
name: "apple",
total: 5
},
{
name: "pineapple",
total: 1
}
]
},
{
id: 222,
fruits: [
{
name: "kiwi",
total: 2
}
]
}
];
// my implementation using reduce within another to get the sum of totals.
const sumOfFruits = items
.reduce((sum, curr) => sum + curr.fruits
.reduce((fruitSum, fruitCurr) => fruitSum + fruitCurr.total));
console.log(sumOfFruits);
// returns 8
A cleaner (not necessarily faster) way to do this would be:
collect all "fruits" (flatMap)
pick "totals" (map)
sum the result
const items = [
{
id: 111,
fruits: [
{
name: "apple",
total: 5
},
{
name: "pineapple",
total: 1
}
]
},
{
id: 222,
fruits: [
{
name: "kiwi",
total: 2
}
]
}
];
//
res = items
.flatMap(x => x.fruits)
.map(x => x.total)
.reduce((s, n) => s + n, 0)
console.log(res)
Regarding performance, the thing about javascript is that it's performant enough unless you have millions of objects. And if you do have millions of objects, you shouldn't be using javascript in the first place.
Your code does not produce the desired output: it coerces an object to string and performs string concatenation.
This is because reduce is called without second argument, and thus the accumulator gets the first object as value, while you want the accumulated value to be a number.
So you need to add 0 as second argument for the outer reduce call. For the inner reduce call you can improve a little bit and provide sum as initial value. That way you don't have to do sum + anymore.
You can also make use of destructuring in the callback parameters:
This leads to the following code:
const items = [{id: 111,fruits: [{name: "apple",total: 5},{name: "pineapple",total: 1}]},{id: 222,fruits: [{name: "kiwi",total: 2}]}];
const sumOfFruits = items.reduce(
(sum, {fruits}) => fruits.reduce(
(fruitSum, {total}) => fruitSum + total,
sum // Continue with the sum we already have
), 0 // Start with 0 for the accumulator
);
console.log(sumOfFruits); // 8
Many would agree that this is how it should be done. If performance really is an issue, then you can get a slight improvement by using plain old for loops. These do not use callbacks, and so can be expected to do the job a little bit faster, but with less elegant code. In my experience they also perform a tiny bit faster than for..of loops:
var items = [{id: 111,fruits: [{name: "apple",total: 5},{name: "pineapple",total: 1}]},{id: 222,fruits: [{name: "kiwi",total: 2}]}];
var sumOfFruits = 0;
for (var i = 0; i < items.length; i++) {
var fruits = items[i].fruits;
for (var j = 0; j < fruits.length; j++) {
sumOfFruits += fruits[j].total;
}
}
console.log(sumOfFruits); // 8
It probably is not worth the millisecond you would gain from this with normal sized input.
I'm trying to write a function that will be called with an array that has information on a person such as their name and then age. I need this function to grab all of the numbers only and then return them then added up. I've done some research and it seems filter and reduce are what I need to do this in the easiest way for a total beginner like me to do?
Apologies for any typos/wrong jargon as my dyslexia gets the better of me sometimes.
An example of what kind of array is being passed into the function when called;
{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 },
Would return the total added numbers.
// returns 92
Why isn't the array I'm calling this function with working? Can you provide me a working example without the array being hardcoded like the other answers? - I'm passing in an array to the function. The main objective is to grab any number from the passed in array and add them together with an empty array returning 0.
function totalNums(person) {
person.reduce((a,b) => a + b, 0)
return person.age;
}
console.log(totalNums([]))
You need to save the result into a new variable then console.log() it like this
const arr= [{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 },...
];
function totalNums(person) {
let res = person.reduce((a,b) => a + b.age, 0)
return res;
}
console.log(totalNums(arr));
and this is why it has to be like that
.reduce()
js methods like .map(), .filter(), .reduce() and some others, they return a new array, they don't modify the original array.
You can console.log(arr); and you will get this output:
[{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 },...
];
Your original array unchanged even after running your function so in order to get the result you expect, you need to store it inside a new variable
You need to save the result of your reduce.
For example with array of numbers you would do:
function totalNums(person) {
let res = person.reduce((a,b) => a + b, 0)
return res;
}
console.log(totalNums([5,6,4]))
And for your example you would like to do something like this:
function totalNums(person) {
let res = person.reduce((a,b) => a + b.age, 0)
return res;
}
console.log(totalNums([
{ name: 'Clarie', age: 22 },
{ name: 'Bobby', age: 30 },
{ name: 'Antonio', age: 40 }
]))
function totalNums(person) {
person.reduce((a,b) => a + b, 0)
return person.age;
}
console.log(totalNums([]))
Talking about the function you have created it is incorrect because:
return person.age; Here you are passing an array to function and then accessing it like it's an object.
person.reduce((a,b) => a + b, 0) you can't add a and b because b is an object.
You are not storing value which reduce function will return.
Solution Below :
The reduce function always returns something It never makes changes in the original array.
function totalNums(persons) {
const totalAge = persons.reduce((total, person) => total + person.age, 0);
return totalAge;
}
const persons = [
{ name: "Clarie", age: 22 },
{ name: "Bobby", age: 30 },
{ name: "Antonio", age: 40 },
];
console.log(totalNums(persons));
You can replace total and person with a and b respectively in the above code snippet for your reference.
Here's a list of parents and I want to sort the parents by their 2nd's child's age with ramda:
[
{
name: "Alicia",
age: "43",
children: [{
name: "Billy",
age: "3"
},
{
name: "Mary",
age: "8"
},
]
},
{
name: "Felicia",
age: "60",
children: [{
name: "Adrian",
age: "4"
},
{
name: "Joseph",
age: "5"
},
]
}
]
How do I do on about it? I tried doing something along the lines of
parents.sort(
sortBy("-children.age"))
);
Use R.sortBy and extract the value with a function create with R.pipe. The function gets the children array of the object with R.prop, takes the last child (R.last), gets the age with R.propOr (returns 0 if no children), and converts to a Number. You can use R.negate if you want to reverse the order.
const { sortBy, pipe, prop, last, propOr } = R
const fn = sortBy(pipe(
prop('children'),
last,
propOr(0, 'age'),
Number,
// negate - if you want to reverse the order
))
const parents = [{"name":"Alicia","age":"43","children":[{"name":"Billy","age":"3"},{"name":"Mary","age":"8"}]},{"name":"Felicia","age":"60","children":[{"name":"Adrian","age":"4"},{"name":"Joseph","age":"5"}]}]
const result = fn(parents)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
In vanilla JavaScript (making some assumptions about the relatively poorly formatted input) using the Array.prototype.sort method:
let parents = [ .... ]; // What you have above
parents = parents.sort((a, b) => {
return a.children[1].age - b.children[1].age; // Change - to + for ascending / descending
});
Be careful though - what would happen if a parent had fewer than 2 children?
Assuming your JSON above was hand generated, including the syntax errors, then assuming your real data is just fine (an array of parents, with each parent having a children array of objects) then a normal JS sort will work just fine:
const compareC2(parent1, parent2) {
let c1 = parent1.children;
let c2 = parent2.children;
if (!c1 || !c2) {
// what happens if someone has no children?
}
let l1 = c1.length;
let l2 = c2.length;
if (l1 === 0 || l2 === 0) {
// different symptom, but same question as above
}
if (l1 !== l2) {
// what happens when the child counts differ?
}
if (l1 !== 2) {
// what happens when there are fewer, or more than, 2 children?
}
// after a WHOLE LOT of assumptions, sort based on
// the ages of the 2nd child for each parent.
return c1[1].age - c2[1].age;
}
let sorted = parents.sort(compareC2);
I would use sortWith with ascend functions. Using sortWith allows you to define a first sort order function, a second sort order function, etc.
const people = [
{
name: "Alicia",
age: "43",
children: [{
name: "Billy",
age: "3"
},
{
name: "Mary",
age: "8"
},
]
},
{
name: "Felicia",
age: "60",
children: [{
name: "Adrian",
age: "4"
},
{
name: "Joseph",
age: "5"
},
]
}
];
const by2ndChildAge = ascend(pathOr(0, ['children', 1, 'age']));
const by1stChildAge = ascend(pathOr(0, ['children', 0, 'age']));
console.log(sortWith([by2ndChildAge, by1stChildAge], people));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {sortWith, ascend, pathOr} = R;</script>
The simplest solution is, I think, just to combine sortBy with path:
const sortBy2ndChildAge = sortBy(path(['children', 1, 'age']))
const people = [{name: "Alicia", age: "43", children: [{name: "Billy", age: "3"}, {name: "Mary", age: "8"}]}, {name: "Felicia", age: "60", children: [{name: "Adrian", age: "4"}, {name: "Joseph", age: "5"}]}]
console.log(sortBy2ndChildAge(people))
<script src="https://bundle.run/ramda#0.26.1"></script><script>
const {sortBy, path} = ramda </script>
There are several potential flaws with this, that others have noted. Are parents always guaranteed to have at least two children? Do we really want a lexicographic sort -- i.e. '11' < '2' -- or do you want to convert these values to numbers?
It would be easy enough to fix both of these problems: sortBy(compose(Number, pathOr(0, ['children', 1, 'age']))), but that depends upon what you're trying to do. If you're just using this to learn about Ramda, then sortBy and path are both useful functions to know. sortBy is useful when you can convert the items to be sorted to some ordered type -- Strings, numbers, dates, or anything with a numeric valueOf method. You supply that conversion function and a list of values and it will sort by that. path is simply a null-safe read for a list of nested properties in an object.
If I have an array of strings, I can use the .join() method to get a single string, with each element separated by commas, like so:
["Joe", "Kevin", "Peter"].join(", ") // => "Joe, Kevin, Peter"
I have an array of objects, and I’d like to perform a similar operation on a value held within it; so from
[
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
]
perform the join method only on the name attribute, to achieve the same output as before.
Currently I have the following function:
function joinObj(a, attr){
var out = [];
for (var i = 0; i < a.length; i++){
out.push(a[i][attr]);
}
return out.join(", ");
}
There’s nothing wrong with that code, it works, but all of a sudden I’ve gone from a simple, succinct line of code to a very imperative function. Is there a more succinct, ideally more functional way of writing this?
If you want to map objects to something (in this case a property). I think Array.prototype.map is what you're looking for if you want to code functionally.
(fiddle)
If you want to support older browsers, that are not ES5 compliant you can shim it (there is a polyfill on the MDN page above). Another alternative would be to use underscorejs's pluck method:
var users = [
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
];
var result = _.pluck(users,'name').join(",")
Well you can always override the toString method of your objects:
var arr = [
{name: "Joe", age: 22, toString: function(){return this.name;}},
{name: "Kevin", age: 24, toString: function(){return this.name;}},
{name: "Peter", age: 21, toString: function(){return this.name;}}
];
var result = arr.join(", ");
console.log(result);
On node or ES6+:
users.map(u => u.name).join(', ')
I've also come across using the reduce method, this is what it looks like:
console.log(
[
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
]
.reduce(function (a, b) {
return (a.name || a) + ", " + b.name}
)
)
The (a.name || a) is so the first element is treated correctly, but the rest (where a is a string, and so a.name is undefined) isn't treated as an object.
Edit: I've now refactored it further to this:
x.reduce(function(a, b) {return a + ["", ", "][+!!a.length] + b.name;}, "");
which I believe is cleaner as a is always a string, b is always an object (due to the use of the optional initialValue parameter in reduce)
Edit 6 months later: Oh what was I thinking. "cleaner". I've angered the code Gods.
I don't know if there's an easier way to do it without using an external library, but I personally love underscore.js which has tons of utilities for dealing with arrays, collections etc.
With underscore you could do this easily with one line of code:
_.pluck(arr, 'name').join(', ')
lets say the objects array is referenced by the variable users
If ES6 can be used then the easiest solution will be:
users.map(user => user.name).join(', ');
If not, and lodash can be used so :
_.map(users, function(user) {
return user.name;
}).join(', ');
const lists = [
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
]
const joinedStr = lists.map((list) => list.name).join(" ")
console.log('joined',joinedStr)
This should do it since map will return an array of strings and you can join it then
If object and dynamical keys: "applications\":{\"1\":\"Element1\",\"2\":\"Element2\"}
Object.keys(myObject).map(function (key, index) {
return myObject[key]
}).join(', ')
An old thread I know but still super relevant to anyone coming across this.
Array.map has been suggested here which is an awesome method that I use all the time.
Array.reduce was also mentioned...
I would personally use an Array.reduce for this use case.
Why? Despite the code being slightly less clean/clear. It is a much more efficient than piping the map function to a join.
The reason for this is because Array.map has to loop over each element to return a new array with all of the names of the object in the array. Array.join then loops over the contents of array to perform the join.
You can improve the readability of jackweirdys reduce answer by using template literals to get the code on to a single line. "Supported in all modern browsers too"
// a one line answer to this question using modern JavaScript
x.reduce((a, b) => `${a.name || a}, ${b.name}`);
not sure, but all this answers tho they work but are not optiomal since the are performing two scans and you can perform this in a single scan. Even though O(2n) is considered O(n) is always better to have a true O(n).
const Join = (arr, separator, prop) => {
let combined = '';
for (var i = 0; i < arr.length; i++) {
combined = `${combined}${arr[i][prop]}`;
if (i + 1 < arr.length)
combined = `${combined}${separator} `;
}
return combined;
}
This might look like old school, but allows me to do thig like this:
skuCombined = Join(option.SKUs, ',', 'SkuNum');
you can convert to array so get object name
var objs = [
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
];
document.body.innerHTML = Object.values(objs).map(function(obj){
return obj.name;
});
This worked for me:
var users = [
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
]
return users.map((user: { name: string; }) => user.name).join(", ");
try this
var x= [
{name: "Joe", age: 22},
{name: "Kevin", age: 24},
{name: "Peter", age: 21}
]
function joinObj(a, attr) {
var out = [];
for (var i=0; i<a.length; i++) {
out.push(a[i][attr]);
}
return out.join(", ");
}
var z = joinObj(x,'name');
z > "Joe, Kevin, Peter"
var y = joinObj(x,'age');
y > "22, 24, 21"
Easiest way:
const oldArrayOfObjects = [
{name: "Bob", age:40},
{name: "Andrew", age:25},
{name: "Peter", age:30}];
const newArrayOfStringNames = oldArrayOfObjects.map((OA) => OA.name);
const newArrayOfAges = oldArrayOfObjects.map((OA) => OA.age);
console.log({newArrayOfStringNames, newArrayOfAges})
I'm trying to take an array of many arrays that contain many objects and manipulate it into an array of objects.
So, let's say I have an array that looks like this:
[
[
{Name: 'Josh', email: 'josh#gmail.com', Points: 33},
{Name: 'Doug', email: 'doug#gmail.com', Points: 12}
],
[
{Name: 'Josh', email: 'josh#gmail.com', Points: 11},
{Name: 'Doug', email: 'doug#gmail.com', Points: 18}
],
[
{Name: 'Josh', email: 'josh#gmail.com', Points: 2},
{Name: 'Doug', email: 'doug#gmail.com', Points: 27}
]
]​
The Desired outcome would be an array of objects that has a 'Points' property to hold an array of the points. So it would look like this:
[
{Name: 'Josh', email: 'josh#gmail.com', Points: [33, 11, 2]},
{Name: 'Doug', email: 'doug#gmail.com', Points: [12, 18, 27]}
]
This problem seems simple, but I can't seem to figure out the best way of doing this. If you want this data in a fiddle, I made this for you to play with: http://jsfiddle.net/Qhxzz/1/
Here's one way using .reduce() and .forEach(). (You'll need patches if you're supporting older browsers.)
var consolidated = data.reduce(function(holder, arr) {
arr.forEach(function(obj) {
if (obj.email in holder.emails)
holder.emails[obj.email].Points.push(obj.Points);
else {
holder.emails[obj.email] = obj;
holder.result.push(obj);
obj.Points = [obj.Points];
}
});
return holder;
}, {emails:{},result:[]}).result;
And actually, we could flatten out the original Array using concat.apply(), and be left with one monolithic Array to iterate.
flat = [].concat.apply([], data);
This turns your data into this structure:
[{Name:"Josh", email:"josh#gmail.com", Points:33},
{Name:"Doug", email:"doug#gmail.com", Points:12},
{Name:"Josh", email:"josh#gmail.com", Points:11},
{Name:"Doug", email:"doug#gmail.com", Points:18},
{Name:"Josh", email:"josh#gmail.com", Points:2},
{Name:"Doug", email:"doug#gmail.com", Points:27}]
And makes the consolidation a bit simpler by eliminating the need for the inner .forEach().
var consolidated = flat.reduce(function(holder, obj) {
if (obj.email in holder.emails)
holder.emails[obj.email].Points.push(obj.Points);
else {
holder.emails[obj.email] = obj;
holder.result.push(obj);
obj.Points = [obj.Points];
}
return holder;
}, {emails:{}, result:[]}).result;
Note that this depends on the input structure being static (no recursion here). Assuming you're not using any libraries:
var result = [];
for (var i = 0; i < initial.length; i++) {
var innerArray = initial[i];
for (var j = 0; j < innerArray.length; j++) {
var item = innerArray[j];
var found = false;
// search result for an object with a matching email prop
for (var k = 0; k < result.length; k++) {
if (result[k].email === item.email) {
found = true;
// add this Point to the Points array
result[k].Points.push(item.Points);
}
}
if (!found) {
// convert Points to an array of Points
item.Points = [item.Points];
result.push(item);
}
}
}
http://jsfiddle.net/Qhxzz/2/