Chunk an array of objects and add empty arrays at the end - javascript

I need to chunk an array of objects so i write:
function conditionalChunk(array, size, rules = {}) {
let copy = [...array],
output = [],
i = 0;
while (copy.length)
output.push(copy.splice(0, rules[i++] ?? size))
return output
}
and it works fine if I have rules like { 0: 2, 1: 2 }
const input = [[1,2,3],[4,5,6,7]],
output = conditionalChunk(input.flat(), 3, { 0: 2, 1: 2 });
// OUTPUT: [[1,2],[3,4],[5,6,7]]
but when I have rules at the end like { 0: 2, 1: 2, 2:0, 5:0 } my function ignore to create empty arrays at the end.
the output I need is:
const input = [[1,2,3],[4,5,6,7]],
output = conditionalChunk(input.flat(), 3, { 0: 2, 1: 2, 2:0, 5:0 });
// OUTPUT: [[1,2],[3,4],[],[5,6,7],[]]
so I just need to not ignore rules for empty arrays at the end of array. How I can do that?

Finally, you could check the keys of rules and if greater or equal to the next index of output push an empty array.
function conditionalChunk(array, size, rules = {}) {
let output = [],
i = 0;
while (i < array.length)
output.push(array.slice(i, i += rules[output.length] ?? size));
let l = Object.keys(rules).reduce((l, v) => l + (v >= output.length), 0);
while (l--) output.push([]);
return output;
}
console.log(conditionalChunk([1, 2, 3, 4, 5, 6, 7], 3, { 0: 2, 1: 2 })); // [[1,2],[3,4],[5,6,7]]
console.log(conditionalChunk([1, 2, 3, 4, 5, 6, 7], 3, { 0: 2, 1: 2, 2: 0, 5: 0 })); // [[1,2],[3,4],[],[5,6,7],[]]
console.log(conditionalChunk([1, 2, 3, 4, 5, 6, 7], 3, { 0: 0, 1: 2, 8: 0, 7: 0, 9:0, 20:0 }));
.as-console-wrapper { max-height: 100% !important; top: 0; }

maybe you can try this, I prefer for...each loop in place of a while.
function conditionalChunk(array, size = 1, rules = {}) {
let copy = [...array],
output = [],
i = 0;
for (const rule of Object.keys(rules)) {
let val = copy.splice(0, rules[rule] || size);
if (typeof val === 'undefined') val = [];
output.push(val);
}
return output;
}
var results = conditionalChunk([1, 2, 3, 4, 5], 2, { 0:2, 1: 2, 2:2, 3:0 });
console.log(results, '::results::');
https://jsfiddle.net/5ozbj7na/

A solution to your problem would be to add this bit of code after the while loop before the return statement
output.length = Math.max.apply(null, [output.length, ...Object.keys(rules)]) || output.length;
for (r in rules)
{
output[r] = new Array(rules[r]);
}
It just extends the output array to the desired length then populates required spots with empty arrays (sorry for the complicated first line but JavaScript is complicated so :| ...)

Please try this one, this should work for you and is done based on your solution, so it should be easier to understand
function conditionalChunk(array, size, rules = {}) {
let copy = [...array],
output = [],
i = 0;
while (copy.length) output.push(copy.splice(0, rules[i++] ?? size));
const arr = Object.keys(rules).filter((key) => key > i);
arr.forEach((key) => {
if (rules[key] === 0) output.push([]);
});
return output;
}

Related

Counting Duplicate elements in an array: Javascript

Could someone please look over my code and explain why my return value = 3 when it should be 2? The object has the correct count {1: 2, 2: 3, 3: 1, 4: 1, 5: 1}. Only two values are >= 2.
function countDuplicates(arr) {
let dupNums = {};
let count = 0;
​
for (let i=0; i<arr.length; i++) {
​
if (dupNums[arr[i]] === undefined) {
dupNums[arr[i]] = 0;
}
​
if (dupNums[arr[i]] !== undefined) {
dupNums[arr[i]] += 1;
}
​
if (dupNums[arr[i]] >= 2) {
count++;
}
}
return count;
}
console.log(countDuplicates([1,2,1,3,2,4,5,2]));
You have three 2s and two 1s. Each time a duplicate is found, count gets incremented.
Iterate over the values of the object afterwards instead, and count up the number of values which are >= 2:
function countDuplicates(arr) {
const dupNums = {};
for (const num of arr) {
dupNums[num] = (dupNums[num] || 0) + 1;
};
return Object.values(dupNums)
.filter(num => num >= 2)
.length;
}
console.log(countDuplicates([1, 2, 1, 3, 2, 4, 5, 2]));
or with reduce:
function countDuplicates(arr) {
const dupNums = {};
for (const num of arr) {
dupNums[num] = (dupNums[num] || 0) + 1;
};
return Object.values(dupNums)
.reduce((a, num) => a + (num >= 2), 0)
}
console.log(countDuplicates([1, 2, 1, 3, 2, 4, 5, 2]));
Use one Set to track dup nums and another Set for tracking count.
Following should work for you scenario.
function countDuplicates(arr) {
const dupNums = new Set();
const countSet = new Set();
arr.forEach((num) =>
dupNums.has(num) ? countSet.add(num) : dupNums.add(num)
);
return countSet.size;
}
console.log(countDuplicates([1, 2, 1, 3, 2, 4, 5, 2]));

JS Arrays — Reorganize 1D Array into 2D Array of N Groups Placing Values Sequentially

I've tried a few approaches to this but I can't seem to come up with a viable solution...
In short, I want to create a function to transform a 1D array of any length into a new 2D array of a specific length. Each value from the 1D array should be sequentially placed into each child-array up to the specified length, then start back at the first child-array.
Input / Desired Output Below:
function groupArray(arr, numberOfGroups) {
...
};
// Input Data
const items = [1, 2, 3, 4, 5, 6, 7];
const size = 3;
console.log(groupArray(items, size));
// Expected Output
// [[1, 4, 7], [2, 5], [3, 6]]
You could take the reminder operator % with index and wanted size of the array for getting the right target array.
const
groupArray = (array, size) => array.reduce((result, value, index) => {
const target = index % size;
if (!result[target]) result[target] = [];
result[target].push(value);
return result;
}, []);
console.log(groupArray([1, 2, 3, 4, 5, 6, 7], 3));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using nested for
function groupArray(arr, numberOfGroups) {
var result = []
for (var i = 0; i < numberOfGroups; i++) {
var subarr = []
for (var j = i; j < arr.length; j += numberOfGroups) {
subarr.push(arr[j])
}
result.push(subarr)
}
return result
};
// Input Data
const items = [1, 2, 3, 4, 5, 6, 7];
const size = 3;
console.log(groupArray(items, size));
Also something like this would do it,
you'd not need to mutate any input
const chunkify = (step, list) => list.reduce(
([chunk, rest], item, i) => {
const pick = i % step === 0;
return [
chunk.concat(pick ? item : []),
rest.concat(pick ? [] : item),
];
},
[[], []],
);
const group = (step, list, result = []) => {
if (!list.length) { return result; }
const [chunk, rest] = chunkify(step, list);
return group(step - 1, rest, [...result, chunk]);
};
const data = [1, 2, 3, 4, 5, 6, 7];
const step = 3;
console.log(
group(step, data),
);
Have method next, each call will return next index and when limit reaches numberOfGroups reset the index 0. (in this case, it will be like 0, 1, 2, 0, 1, 2....)
With this method, can easily push to corresponding output sub array.
const groupArray = (arr, numberOfGroups) => {
const output = Array.from({ length: numberOfGroups }, () => []);
const next = index => index % numberOfGroups;
arr.forEach((num, i) => output[next(i)].push(num));
return output;
};
// Input Data
const items = [1, 2, 3, 4, 5, 6, 7];
const size = 3;
console.log(groupArray(items, size));

Using reduce to delete even numbers in an object

I'm given an array of numbers. I have created an object named counts, whose keys are the numbers and values the amount of times those numbers appear in the array. Can't work out how to use reduce to delete the even counts of the numbers.
A = [ 20, 1, -1, 2, -2, 3, 3, 5, 5, 1, 2, 4, 20, 4, -1, -2, 5 ]
n = 5
function findOdd(A) {
let counts = {};
for (let i = 0; i < A.length; i++) {
let num = A[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
//counts -> { '1': 2, '2': 2, '3': 2, '4': 2, '5': 3, '20': 2, '-1': 2, '-2': 2 }
const answer = Object.keys(counts).reduce((object, key) => {
if (key % 2 !== 0) {
object[key] = counts[key];
}
return object;
}, {})
return answer;
Must return the key of the odd count.
SOLUTION:
function findOdd(A) {
const counts = {};
for (let i = 0; i < A.length; i++) {
let num = A[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
Object.keys(counts).forEach(key => {
if(counts[key] % 2 === 0) {
delete counts[key];
}
});
return Number(Object.keys(counts));
}
You can make use of Object.entries to obtain entires, then filter entries whose values are odd, and then reconstruct new Object from those entries using Object.fromEntries:
const countObject = { '1': 2, '2': 2, '3': 2, '4': 2, '5': 3, '20': 2, '-1': 2, '-2': 2 };
const oddEntries = Object.entries(countObject).filter(([key, value]) => value % 2 !== 0);
const oddCountObject = Object.fromEntries(oddEntries)
console.log(oddCountObject)
function findOdd(arr) {
const counts = {}; // `const` declared objects/arrays are not immutable
for(let i = 0; i < arr.length; i++) {
counts[arr[i]] = counts[arr[i]] || 0;
counts[arr[i]]++;
}
Object.keys(counts).forEach(key => {
if(counts[key] % 2 === 0) {
delete counts[key];
}
});
return counts;
}
const array = [1, 2, 3, 4, 5, 6, 1, 1, 4, 5, 4, 9];
// 1:3, 2:1, 3:1, 4:3, 6:1, 9:1
// Does not show a `5` key due to there being an even number of fives in `array`
console.log(findOdd(array));
Yes, I know delete is inefficient, but that shouldn't matter, unless it is a requirement for being quick. I believe you can just set counts[key] = undefined or counts[key] = null, you can see the benchmarks here
I know there are already great answers but as the question said "using reduce" I thought it'd be fun to try doing this using only reduce:
const findOdd = arr => arr.reduce((acc, d, i, list) => {
if (!acc[d]) {
acc[d] = 0;
}
acc[d]++;
if (i === list.length - 1) {
return Object.keys(acc)
.reduce((subAcc, key) => ({
...subAcc,
...(acc[key] % 2 === 0 ? {} : {
[key]: acc[key]
})
}), {})
}
return acc;
}, {})

How to remove *some* items from array when there is repetition? (Lodash/Underscore preferred)

I'm trying to write a function that filters out triplets from an array of 6 dice. Is there an easy way to do it using Lodash or Underscore?
noTriplets([1,1,1,3,3,5]) // = [3,3,5]
noTriplets([1,1,1,1,3,5]) // = [1,3,5]
noTriplets([1,1,1,1,1,5]) // = [1,1,5]
noTriplets([1,1,1,5,5,5]) // = []
noTriplets([1,1,1,1,1,1]) // = []
It's a little rough and dirty, but it doesn't require you to know your triplets ahead of time. Within the noTriplets() - I create a quick hashMap and then loop through that object. The loop logic handles the triplet piece.
const arrayTestOne = [1,1,1,3,3,5];
const arrayTestTwo = [1,1,1,1,3,3,5];
const arrayTestThree = [1,1,1,3,3,3];
const arrayTestFour = [1,1,1,1,3,3,3,5,5,5,5,5,5,5,5,5,5,5,5,7,7];
const hashMap = (array) => array.reduce((allNums, num) => {
if (num in allNums) {
allNums[num]++
}
else {
allNums[num] = 1
}
return allNums
}, {})
function noTriplets(arr) {
let newArr = [];
let obj = hashMap(arr);
for (var key in obj) {
for (let i=0; i < obj[key] % 3; i++) {
newArr.push(key)
}
}
console.log(newArr)
}
noTriplets(arrayTestOne)
noTriplets(arrayTestTwo)
noTriplets(arrayTestThree)
noTriplets(arrayTestFour)
You could use a count for every item and calculate how many items to ignore.
function noTriplets(array) {
var hash = {};
array.forEach(function (a) {
hash[a] = hash[a] || { count: 0 };
hash[a].ignore = Math.floor(++hash[a].count / 3) * 3;
});
return array.filter(function (a, i) {
return --hash[a].ignore < 0;
});
}
console.log(noTriplets([1, 1, 1, 3, 3, 5])); // [3, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 3, 5])); // [1, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 1, 5])); // [1, 1, 5]
console.log(noTriplets([1, 1, 1, 5, 5, 5])); // []
console.log(noTriplets([1, 1, 1, 1, 1, 1])); // []
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use an object to record the values and then generate a new array using the previous object.
function noTriplets(arr){
var tripletCount = arr.reduce((dice,value) => {
dice[value] = dice[value] || { count : 0 };
dice[value].count = (dice[value].count + 1) % 3;
return dice;
},{});
return Object.keys(tripletCount).reduce((arr,key) => {
return arr.concat(new Array(tripletCount[key].count).fill(key));
},[]);
}
console.log(noTriplets([1, 1, 1, 3, 3, 5])); // [3, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 3, 5])); // [1, 3, 5]
console.log(noTriplets([1, 1, 1, 1, 1, 5])); // [1, 1, 5]
console.log(noTriplets([1, 1, 1, 5, 5, 5])); // []
console.log(noTriplets([1, 1, 1, 1, 1, 1])); // []
My universal solution with pure JS. You can specify how many repetition items should be removed. For example, here created noDoubles, noTriplets and noQuadruples methods.
function isArrayWithIdenticalElements(array) {
return array.length > 1 && !!array.reduce(function(a, b){ return (a === b) ? a : NaN; });
}
function noRepetition(numberOfRepetition, array) {
var sliceLength = numberOfRepetition - 1;
var pointer = sliceLength;
var element = array[pointer];
while (element) {
if (isArrayWithIdenticalElements(array.slice(pointer - sliceLength, pointer + 1))) {
array.splice(pointer - sliceLength, numberOfRepetition);
pointer = pointer - sliceLength;
element = array[pointer];
} else {
pointer = pointer + 1;
element = array[pointer];
}
}
return array;
}
var noDoubles = noRepetition.bind(null, 2);
var noTriplets = noRepetition.bind(null, 3);
var noQuadruples = noRepetition.bind(null, 4);
console.log('noTriplets([1,1,1,3,3,5] ==> ', noTriplets([1,1,1,3,3,5])); // = [3,3,5]
console.log('noTriplets([1,1,1,1,3,5] ==> ', noTriplets([1,1,1,1,3,5])); // = [1,3,5]
console.log('noTriplets([1,1,1,1,1,5] ==> ', noTriplets([1,1,1,1,1,5])); // = [1,1,5]
console.log('noTriplets([1,1,1,5,5,5] ==> ', noTriplets([1,1,1,5,5,5])); // = []
console.log('noTriplets([1,1,1,1,1,1] ==> ', noTriplets([1,1,1,1,1,1])); // = []
console.log('noQuadruples([1,1,1,3,3,5] ==> ', noQuadruples([1,1,1,3,3,5])); // = [1,1,1,3,3,5]
console.log('noQuadruples([1,1,1,1,3,5] ==> ', noQuadruples([1,1,1,1,3,5])); // = [3,5]
console.log('noDoubles([1,1,1,5,5,5] ==> ', noDoubles([1,1,1,5,5,5])); // = [1,5]
Great answers! After reading everyone's answers, especially Christopher Messer's, I came up with a lodash-based version:
function noTriplets(arr) {
var hashMap = _.countBy(arr)
var filler = n => Array(hashMap[n] % 3).fill(n)
return _.flatten(_.keys(hashMap).map(filler))
}

Find the least duplicate items in an array

Okay so as the title says my goal is to find the least duplicate elements, given that the elements are only integers.
ex1: array = [1,1,2,2,3,3,3] result should be 1,2
ex2: array = [1,2,2,3,3,4] result should be 1,4
I could use the xor operator to find the elements that appear only once but since there might be only duplicates I cant.
I was thinking of first checking with XOR if the're any non-duplicate elements. If no proceed with fors to check for only two occurrences of the same element and so on, but that is not a good approach as its kinda slow,
any suggestions?
Another approach, using new Set() and few Array.prototype functions. If you have any questions, let me know.
var array1 = [1, 1, 2, 2, 3, 3, 3],
array2 = [1, 2, 2, 3, 3, 4];
function sort(arr) {
var filtered = [...new Set(arr)],
solution = [],
res = filtered.reduce(function(s, a) {
s.push(arr.filter(c => c == a).length);
return s;
}, []);
var minDupe = Math.min.apply([], res);
res.forEach((v, i) => v == minDupe ? solution.push(filtered[i]) : null)
console.log(solution)
}
sort(array1);
sort(array2);
Using Array#forEach instead of Array#reduce.
var array1 = [1, 1, 2, 2, 3, 3, 3],
array2 = [1, 2, 2, 3, 3, 4];
function sort(arr) {
var filtered = [...new Set(arr)],
solution = [],
res = [];
filtered.forEach(v => res.push(arr.filter(c => c == v).length));
var minDupe = Math.min.apply([], res);
res.forEach((v, i) => v == minDupe ? solution.push(filtered[i]) : null)
console.log(solution)
}
sort(array1);
sort(array2);
There may be a better or faster solution, but I would suggest to create a hash (object) with the integer as keys and the counts as values. You can create this in one loop of the array. Then, loop over the object keys keeping track of the minimum value found and add the key to the result array if it satisfies the minimum duplicate value.
Example implementation:
const counts = input.reduce((counts, num) => {
if (!counts.hasOwnProperty(num)) {
counts[num] = 1;
}
else {
counts[num]++;
}
return counts;
}, {});
let minimums = [];
let minCount = null;
for (const key in counts) {
if (!minimums.length || counts[key] < minCount) {
minimums = [+key];
minCount = counts[key];
}
else if (counts[key] === minCount) {
minimums.push(+key);
}
}
return minimums;
You can also simplify this a little bit using lodash: one operation to get the counts and another to get the minimum count and get the list of values that match that minimum count as a key:
import { countBy, invertBy, min, values } from "lodash";
const counts = countBy(input);
const minCount = min(values(counts));
return invertBy(counts)[minCount];
You could count the appearance, sort by count and delete all same max count keys. Then return the original values.
Steps:
declarate all variables, especial the hash object without any prototypes,
use the items as key got the hash table and if not set use an object with the original value and a count property,
increment count of actual hash,
get all keys from the hash table,
sort the keys in descending order of count,
get the count of the first element and store it in min,
filter all keys with min count,
get the original value for all remaining keys.
function getLeastDuplicateItems(array) {
var hash = Object.create(null), keys, min;
array.forEach(function (a) {
hash[a] = hash[a] || { value: a, count: 0 };
hash[a].count++;
});
keys = Object.keys(hash);
keys.sort(function (a, b) { return hash[a].count - hash[b].count; });
min = hash[keys[0]].count;
return keys.
filter(function (k) {
return hash[k].count === min;
}).
map(function (k) {
return hash[k].value;
});
}
var data = [
[1, 1, 2, 2, 3, 3, 3],
[1, 2, 2, 3, 3, 4],
[4, 4, 4, 6, 6, 4, 7, 8, 5, 5, 6, 3, 4, 6, 6, 7, 7, 8, 3, 3]
];
console.log(data.map(getLeastDuplicateItems));
.as-console-wrapper { max-height: 100% !important; top: 0; }
A single loop solution with a variable for min and an array for collected count.
function getLeastDuplicateItems(array) {
var hash = Object.create(null),
temp = [],
min = 1;
array.forEach(function (a) {
var p = (temp[hash[a]] || []).indexOf(a);
hash[a] = (hash[a] || 0) + 1;
temp[hash[a]] = temp[hash[a]] || [];
temp[hash[a]].push(a);
if (min > hash[a]) {
min = hash[a];
}
if (p === -1) {
return;
}
temp[hash[a] - 1].splice(p, 1);
if (min === hash[a] - 1 && temp[hash[a] - 1].length === 0) {
min++;
}
}, []);
return temp[min];
}
var data = [
[1, 1, 2, 2, 3, 3, 3],
[1, 2, 2, 3, 3, 4],
[4, 4, 4, 6, 6, 4, 7, 8, 5, 5, 6, 3, 4, 6, 6, 7, 7, 8, 3, 3],
];
console.log(data.map(getLeastDuplicateItems));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories