Group or chunk array with maximum sum limit - javascript

I have an array like this
const array = [{id: 1, size: 1}, {id: 2, size: 2}, {id: 3, size: 4}, {id: 4, size: 1}, {id: 5, size: 2}, {id: 6, size: 3}, ...]
I want to group or chunk this array with the maximum sum of size properties (the sum size of every indexes cannot be greater than 4),
so new array should be something like this:
const newArray = [
[{id:1, size: 1}, {id:2, size: 2}, {id:4, size: 1}],
[{id:3, size: 4}],
[{id:5, size: 3}],
[{id:6, size: 4}],
...
]

You could find the next slot by looking to the sum of each slot.
let array = [{ id: 1, size: 1 }, { id: 2, size: 2 }, { id: 3, size: 4 }, { id: 4, size: 1 }, { id: 5, size: 2 }, { id: 6, size: 3 }],
sum = 4,
result = array.reduce((r, o) => {
const temp = r.find(a => a.reduce((s, { size }) => s + size, 0) + o.size <= sum);
if (temp) temp.push(o);
else r.push([o]);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

my way...
const array =
[ { id: 1, size: 1 }
, { id: 2, size: 2 }
, { id: 3, size: 4 }
, { id: 4, size: 1 }
, { id: 5, size: 2 }
, { id: 6, size: 3 }
// , ...
]
, szMax = array.reduce((t,c)=>Math.max(t,c.size),0)
, temp = array.map(e=>({...e}))
, result = []
;
while (temp.length > 0)
{
let sz = szMax
, nv = []
;
while( sz > 0 )
{
let idx = temp.findIndex(x=>x.size <= sz)
if (idx===-1) break
nv.push( temp[idx] )
sz -= temp[idx].size
temp.splice(idx,1)
}
result.push([...nv])
nv = []
}
console.log( result )
.as-console-wrapper{max-height:100% !important;top: 0;}

Related

How to check with an array have some values

How to verify with i have only 2 or 3 numbers inside this?
without this ----> if(Array.includes(1) && !Array.includes(3))
const servicesTest: IServices[] = [
{
id: '1',
name: 'Hair',
price: 25,
icon: 'https://cdn-icons-png.flaticon.com/512/7478/7478480.png'
},
{
id: '2',
name: 'Beard',
price: 20,
icon: 'https://cdn-icons-png.flaticon.com/512/7578/7578754.png'
},
{
id: '3',
name: 'Eyebrow',
price: 15,
icon: 'https://cdn-icons-png.flaticon.com/512/2821/2821012.png'
}
]
if the client choose hair + beard this will be 40 not 45.
I´m doing this:
const name = findServices.map(services => services.name)
if (name.includes('Hair') && name.includes('Beard') && !name.includes('Eyebrown')) {
return (
setTotalDay(prevState => prevState + 40),
setTotalMonth(prevState => prevState + 40)
)
}
I would create an array of discounts like this:
const discounts = [{
price: 30,
ids: [1, 2],
}];
Then check if the array has only discounted items like this:
array.length === discount.ids.length && array.every((item) => discount.ids.includes(item.id))
const discounts = [{
price: 30,
ids: [1, 2],
}];
const discounted = [{
id: 1,
name: 'Hair',
price: 20,
},
{
id: 2,
name: 'Beard',
price: 30,
},
];
const fullPrice = [{
id: 1,
name: 'Hair',
price: 20,
},
{
id: 2,
name: 'Beard',
price: 30,
},
{
id: 3,
name: 'Tea',
price: 30,
},
];
console.log("discounted", getTotal(discounted));
console.log("full price", getTotal(fullPrice));
function getTotal(array) {
for (const discount of discounts) {
if (
array.length === discount.ids.length &&
array.every((item) => discount.ids.includes(item.id))
) {
return discount.price;
}
}
return array.reduce((sum, item) => sum + item.price, 0);
}
answering your question before the edit.
Assuming we have this array
const Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Let's say we want to check if values 2 and 3 exist.
We store the values in an array let toCheck = [2,3];
We can use function every to loop all the elements of toCheck array against the Array const
Example Follows:
const Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let toCheck = [1,2];
const allExist = toCheck.every(value => {
return Array.includes(value);
});
Hope it helps.

Efficient way to get top count of an option in an array of objects

I have an array of objects like this,
[
{
user: 'A',
answers: [
{
id: 1,
score: 3,
},
{
id: 2,
score: 1,
},
...
]
},
{
user: 'B',
answers: [
...
where I have 200 users, each user answers a set of 40 questions, each question has an id and a score.
What I'm trying to do is add up each question's score. So that I can figure out which question has the highest score, which has the lowest. Aka, top question and bottom question.
What would be the best way to do this?
The current way I am doing feels a little long-winded.
const allAns = []
myList.forEach( user => allAns.push( ...user.answers ) )
const questionsScored = allAns.reduce( ( obj, cur ) => {
!obj[ cur.id ] ? obj[ cur.id ] = cur.score : obj[ cur.id ] += cur.score
return obj
}, {} )
const sortingList = []
for ( const qn in questionsScored ) {
sortingList.push( [ qn, questionsScored[ qn ] ] )
}
sortingList.sort( ( a, b ) => b[ 1 ] - a[ 1 ] )
console.log( sortingList[ 0 ], sortingList[ sortingList.length - 1 ] )
You're taking all the steps necessary so if it's working it's fine though you could replace some of your forEach() loops with available methods:
with .flatMap()
const allAns = myList.flatMap(({answers})=>answers);
and using Object.entries()
const sortingList = Object.entries(questionsScored);
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
allAns = input.flatMap(({ answers }) => answers),
questionsScored = allAns.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {}),
sortingList = Object.entries(questionsScored).sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
Or combined into a single chained call, but it's not necessarily better.
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
sortingList = Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
If you would like to avoid the sort() call you can instead collect the low and high counts using a forEach() after the initial reduce()
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
lowScore = { count: Infinity },
highScore = { count: -Infinity };
Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.forEach(([id, count]) => {
// update low count
if (count < lowScore.count) {
lowScore.count = count;
lowScore.id = id;
}
// update high count
if (count > highScore.count) {
highScore.count = count;
highScore.id = id;
}
});
console.log({ lowScore, highScore })
// sample data
let data = [{
user: 'A',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
{
user: 'B',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
]
let scoreSum = []; //scoreSum to store total score of each question
let initialValue = 0;
for (let i = 0; i < 4; i++) {
let sum = data.reduce(function (accumulator, currentValue) {
return accumulator + currentValue.answers[i].score;
}, initialValue)
scoreSum.push(sum);
}
let highestScore = Math.max(...scoreSum);
let lowestScore = Math.min(...scoreSum);
// increasing index by 1 to match with question numbers
let highestScoreIndex = scoreSum.indexOf(highestScore) + 1;
let lowestScoreIndex = scoreSum.indexOf(lowestScore) + 1;
// Array.prototype.getDuplicates returns an object where the keys are the duplicate entries
// and the values are an array with their indices.
Array.prototype.getDuplicates = function () {
var duplicates = {};
for (var i = 0; i < this.length; i++) {
if (duplicates.hasOwnProperty(this[i])) {
duplicates[this[i]].push(i);
} else if (this.lastIndexOf(this[i]) !== i) {
duplicates[this[i]] = [i];
}
}
return duplicates;
};
let sameScore = scoreSum.getDuplicates();
// checking if highest score has duplicates
// and if so then updaing highest score index
//with highest score indices
if (sameScore.hasOwnProperty(highestScore)) {
highestScoreIndex = sameScore[highestScore].map((a) => a + 1);
}
// checking if lowest score has duplicates
// and if so then updaing lowest score index
//with lowest score indices
if (sameScore.hasOwnProperty(lowestScore)) {
lowestScoreIndex = sameScore[lowestScore].map((a) => a + 1);
}
console.log(`Top question no(s): ${highestScoreIndex} highest score:${highestScore}`);
console.log(`bottom question no(s): ${lowestScoreIndex} lowest score:${lowestScore}`);
I only loop once through each answer in .answers for each user using nested reduce.
The input array got three users with each three answers.
let input = [{ user: 'A', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 4, }, { id: 3, score: 0, }] }, { user: 'c', answers: [{ id: 1, score: 0, }, { id: 2, score: 3, }, { id: 3, score:5, }] }]
function showBestAndWorstFrom(input) {
let highestScore = {'id': 0, 'score': -Infinity};
let lowestScore = {'id': 0, 'score': Infinity};
let currentScore = 0;
let id = 0;
const LAST_USER = input.length - 1;
let answers = input.reduce((combinedObj, user, userIndex) => {
return user.answers.reduce((_answerObj, _answer) => {
id = _answer.id
currentScore = (_answerObj[id] || 0) + _answer.score;
_answerObj[id] = currentScore;
if (userIndex == LAST_USER) {
highestScore = (highestScore.score < currentScore) ? {'id': id, 'score': currentScore } : highestScore;
lowestScore = (lowestScore.score > currentScore) ? {'id': id, 'score': currentScore } : lowestScore;
}
return _answerObj;
}, combinedObj);
}, {});
// console.log(answers); // { "1": 4, "2": 8, "3": 5 }
return {highestScore, lowestScore};
}
console.log(showBestAndWorstFrom(input))

JavaScript objects array filter by minimum value of an attribute

I need to filter this object array by minimum value of 'rest' attribute. This is an one way to do it. Is there any other ways ?
'data' variable is a result of chained function. Is there any other way to do this without calling 'data' variable again inside Math.min() function.
let data =
[ { size: 5, qty: 2, rest: 0 },
{ size: 2, qty: 5, rest: 0 },
{ size: 1, qty: 10, rest: 0 },
{ size: 3, qty: 3, rest: 1 },
{ size: 4, qty: 2, rest: 2 } ]
let result = data.filter(e=> e.rest === Math.min(...data.map(f=>f.rest) ) );
console.log(result);
// result is
//[ { size: 5, qty: 2, rest: 0 },
// { size: 2, qty: 5, rest: 0 },
// { size: 1, qty: 10, rest: 0 }]
The easiest way is to pull the min function out of the filter like this:
let min = Math.min(...data.map(item => item.rest))
This is much more efficient as we are no longer loop over the data to find the min for every iteration of the filter.
We now have n * 2 passes instead of n^2 passes. (n is the size of your data set, 5 in this case)
Full example below:
let data = [
{ size: 5, qty: 2, rest: 0 },
{ size: 2, qty: 5, rest: 0 },
{ size: 1, qty: 10, rest: 0 },
{ size: 3, qty: 3, rest: 1 },
{ size: 4, qty: 2, rest: 2 }
]
let min = Math.min(...data.map(item => item.rest))
let result = data.filter(item => item.rest === min)
console.log(result)
Hope this helps!
Lloyd
data.map inside of data.filter is O(N^2); for an O(N) solution, iterate through data ahead of time to calculate the minimum, then filter by that minimum:
let data =
[ { size: 5, qty: 2, rest: 0 },
{ size: 2, qty: 5, rest: 0 },
{ size: 1, qty: 10, rest: 0 },
{ size: 3, qty: 3, rest: 1 },
{ size: 4, qty: 2, rest: 2 } ];
const minRest = Math.min(...data.map(({ rest }) => rest));
let result = data.filter(({ rest }) => rest === minRest);
console.log(result);
imo. the simplest/best solution is the one #CertainPerformance gave you.
Just wanted to add another solution with linear runtime (that truly iterates only once over the Array)
let data = [
{ size: 5, qty: 2, rest: 0 },
{ size: 2, qty: 5, rest: 0 },
{ size: 1, qty: 10, rest: 0 },
{ size: 3, qty: 3, rest: 1 },
{ size: 4, qty: 2, rest: 2 }
];
let result = data.reduce((result, item) => {
let minRest = result.length? result[0].rest: item.rest;
if (item.rest < minRest) {
minRest = item.rest;
result.length = 0;
}
if (item.rest === minRest) {
result.push(item);
}
return result;
}, []);
console.log(result);
#mathieux51 got me another idea how you can do this inside a method chain, but the readability/clarity/intention is not as good as with the other approaches:
let data = [
{ size: 5, qty: 2, rest: 0 },
{ size: 2, qty: 5, rest: 0 },
{ size: 1, qty: 10, rest: 0 },
{ size: 3, qty: 3, rest: 1 },
{ size: 4, qty: 2, rest: 2 }
];
let result = data.sort((a, b) => a.rest - b.rest)
.filter((item, index, array) => item.rest === array[0].rest);
console.log(result);
It sounds like you want to sort the list. I would do it as following:
const result = data.sort((a, b) => a.rest - b.rest)
Get Min or Max
Since no one mentioned this method I will update it here.
myArray.sort(function (a, b) {
return a.rest - b.rest
})
var min = myArray[0],
max = myArray[myArray.length - 1]
It has good readability/clarity/intentions.

How to transform an array into an object in javascript

I have an array like this:
[
{ id: “Идент”, name: “Назв”, price: “Сто”, quantity: “Коло” },
[ 1, “продукт 1”, “400”, 5 ],
[ 2, “продукт 2”, “300”, 7 ],
[ 2, “продукт 2”, “300”, 7 ]]
How can I transform it into something like this:
{
items: [
{ name: "Хлеб", id: 1, price: 15.9, quantity: 3 },
{ name: "Масло", id: 2, price: 60, quantity: 1 },
{ name: "Картофель", id: 3, price: 22.6, quantity: 6 },
{ name: "Сыр", id: 4, price:310, quantity: 9 }
]
};
I assume that index 0:id,1:name,2:price,3:quantity. here you go,
var array = [
[12,"abc",232,2],
[12,"abc",232,2],
[12,"abc",232,2],
[12,"abc",232,2]
];
var obj = {};
obj.options = (function(array){
var e = [];
for(i in array){
t = {};
t.id = array[i][0];
t.name = array[i][1];
t.price = array[i][2];
t.quantity = array[i][3];
e.push(t);
}
return e;
})(array);
console.log(obj)
To convert an array with data to an array with objects, you could use another array with keys and iterate it for the assignment of the properties for the new objects.
var data = [{ id: 'id', name: 'name', price: 'price', quantity: 'quantity' }, [0, 'foo', 1.99, 201], [1, 'abc', 2.5, 42], [2, 'baz', 10, 99], [6, 'bar', 21.99, 1]],
keys = Object.keys(data[0]),
result = {
items: data.slice(1).map(function (a) {
var temp = {};
keys.forEach(function (k, i) {
temp[k] = a[i];
});
return temp;
})
};
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sort array of objects with hierarchy by hierarchy and name

I have an array of nested objects:
[
{_id:1, parent:0, name:'Z'},
{_id:4, parent:0, name:'A'},
{_id:2, parent:1, name:'H'},
{_id:8, parent:2, name:'G'},
{_id:5, parent:4, name:'M'},
{_id:6, parent:4, name:'N'},
{_id:3, parent:1, name:'Z'},
{_id:7, parent:2, name:'L'}
]
I need to sort them in the way for nodes at same level will be sorted by alphabetic order (asc/desc configurable) and all child nodes should be after their parent and before their parent's sibling node also sorted by alphabetic order.
For example, if sorted by asc, the output should be
[
{ _id: 4, parent: 0, name: 'A' },
{ _id: 5, parent: 4, name: 'M' },
{ _id: 6, parent: 4, name: 'N' },
{ _id: 1, parent: 0, name: 'Z' },
{ _id: 2, parent: 1, name: 'H' },
{ _id: 8, parent: 2, name: 'G' },
{ _id: 7, parent: 2, name: 'L' },
{ _id: 3, parent: 1, name: 'Z' }
]
In the output, 4 is before 1 because A < Z. 5 and 6 are sorted alphabetically under 4 and before 1. Similar case for 8 and 7 under 2 and before 3.
and if desc, the output should be:
[
{ _id: 1, parent: 0, name: 'Z' },
{ _id: 3, parent: 1, name: 'Z' },
{ _id: 2, parent: 1, name: 'H' },
{ _id: 7, parent: 2, name: 'L' },
{ _id: 8, parent: 2, name: 'G' },
{ _id: 4, parent: 0, name: 'A' },
{ _id: 5, parent: 4, name: 'M' },
{ _id: 6, parent: 4, name: 'N' }
]
I tried to implement a function as below.
function sortByHierarchyAndName(arr, sort) {
var i = 0;
j = 0;
t = 0;
parentFound = false;
x = arr.length;
arr2 = [];
//Sort by parent asc first
arr = arr.sort(function(a, b) {
if(a.parent < b.parent) return -1;
if(a.parent > b.parent) return 1;
return 0;
});
for(; i < x; i += 1) {
t = arr2.length;
if(t === 0) arr2.push(arr[i]);
else if(arr[i].parent === 0) {
for(j = 0; j < t; j += 1) {
if(sort === -1) {
if(arr[i].name >= arr2[j].name) arr2.splice(j, 0, arr[i]);
} else {
if(arr[i].name <= arr2[j].name) arr2.splice(j, 0, arr[i]);
}
}
if(arr2.length === t) arr2.push(arr[i]);
}
else {
parentFound = false;
for(j = 0; j < t; j += 1) {
if(arr[i].parent === arr2[j]._id) {
if(j === t - 1) {
arr2.push(arr[i]);
}
parentFound = true;
} else if(arr[i].parent === arr2[j].parent) {
if(sort === -1) {
if(j === t - 1) arr2.push(arr[i]);
else if(arr[i].name >= arr2[j].name) {
arr2.splice(j, 0, arr[i]);
j = t;
}
} else {
if(j === t - 1) arr2.push(arr[i]);
else if(arr[i].name <= arr2[j].name) {
arr2.splice(j, 0, arr[i]);
j = t;
}
}
} else if(arr[i].parent > arr2[j].parent && parentFound) {
arr2.splice(j, 0, arr[i]);
j = t;
}
}
}
}
return arr2;
}
Assuming array.sort() take f(n) amount of time when sorting by parent asc for an array of length n, I'm doing some performance analysis for the implementation as below.
Best case: f(n) + x * n + y * sum(1 to n/2)*n
Worst case: f(n) + x * n + y * sum(1 to n)*n;
x - factor in processing any given element in arr.
y - factor in processing any given element in arr against any element in arr2.
As you can see, in both case, the duration of execution will grow exponentially as n grows, so I'm wondering if I can do something to improve this.
You can use a recursive algorithm and hash object, I believe that performance of this algorithm will take about O(n log n):
function hierarchySortFunc(a,b ) {
return a.name > b.name;
}
function hierarhySort(hashArr, key, result) {
if (hashArr[key] == undefined) return;
var arr = hashArr[key].sort(hierarchySortFunc);
for (var i=0; i<arr.length; i++) {
result.push(arr[i]);
hierarhySort(hashArr, arr[i]._id, result);
}
return result;
}
var arr = [
{ _id: 4, parent: 0, name: 'A' },
{ _id: 5, parent: 4, name: 'M' },
{ _id: 6, parent: 4, name: 'N' },
{ _id: 1, parent: 0, name: 'Z' },
{ _id: 2, parent: 1, name: 'H' },
{ _id: 8, parent: 2, name: 'G' },
{ _id: 7, parent: 2, name: 'L' },
{ _id: 3, parent: 1, name: 'Z' }
]
var hashArr = {};
for (var i=0; i<arr.length; i++) {
if (hashArr[arr[i].parent] == undefined) hashArr[arr[i].parent] = [];
hashArr[arr[i].parent].push(arr[i]);
}
var result = hierarhySort(hashArr, 0, []);
for (var i=0; i<result.length; i++) console.log(result[i]);
Result:
{_id: 4, parent: 0, name: "A"}
{_id: 5, parent: 4, name: "M"}
{_id: 6, parent: 4, name: "N"}
{_id: 1, parent: 0, name: "Z"}
{_id: 2, parent: 1, name: "H"}
{_id: 8, parent: 2, name: "G"}
{_id: 7, parent: 2, name: "L"}
{_id: 3, parent: 1, name: "Z"}
If you want to change the sorting order, please change heirarchySortFunc():
function hierarchySortFunc(a,b ) {
return a.name < b.name;
}
It might be simpler just to combine the item names and sort alphabetically.
var array = [
{_id:1, parent:0, name:'Z'},
{_id:4, parent:0, name:'A'},
{_id:2, parent:1, name:'H'},
{_id:8, parent:2, name:'G'},
{_id:5, parent:4, name:'M'},
{_id:6, parent:4, name:'N'},
{_id:3, parent:1, name:'Z'},
{_id:7, parent:2, name:'L'}
]
var getItemFromID = function(id) {
return array.filter(function(item){
return item._id === id;
})[0]
}
var getCombinedName = function(item) {
var parent = getItemFromID(item.parent);
if (parent) {
return getCombinedName(parent) + item.name;
} else {
return item.name;
}
}
array.forEach(function(item){
item.combinedName = getCombinedName(item);
})
var sortedArray = array.sort(function(a,b) {
return a.combinedName > b.combinedName;
});
Result:
{_id: 4, parent: 0, name: "A", combinedName: "A"}
{_id: 5, parent: 4, name: "M", combinedName: "AM"}
{_id: 6, parent: 4, name: "N", combinedName: "AN"}
{_id: 1, parent: 0, name: "Z", combinedName: "Z"}
{_id: 2, parent: 1, name: "H", combinedName: "ZH"}
{_id: 8, parent: 2, name: "G", combinedName: "ZHG"}
{_id: 7, parent: 2, name: "L", combinedName: "ZHL"}
{_id: 3, parent: 1, name: "Z", combinedName: "ZZ"}

Categories