JavaScript Tally multiple elements of an array - javascript

I have been tasked with a counting challenge which I should return an object containing a count of elements in the array. for e.g.
expect(createTally(['a', 'b', 'a'])).to.eql({ a: 2, b: 1 });
So I have managed to do this with the higher-order function reduce but I want to be able to do this with a for loop so I can really see how this works, my code for the reduce method is below...
const createTally = items => {
const counter = items.reduce((acc, curr) => {
acc[curr] = (acc[curr] || 0) + 1
return acc
}, {})
return counter
}
So far for my for loop I have ...
const createTally = items => {
const tally = {};
let count = 0
if (items.length > 0) {
for (let i = 0; i < items.length; i++) {
if (items[i].length > count) {
count += count + 1
console.log(count)
}
const key = items[i]
tally[key] = count
return tally
}
} else {
return tally;
}
}
I am struggling to increment my count and not passing any tests other than being able to return an empty object when passed an empty array and passing 1 key value pair when given a single element, any help would be much appreciated, thank you

A much simpler solution would yield something like the following, without all those for loops:
const createTally = (items = []) => {
const tally = {}
items.forEach(key => {
tally[key] = tally[key] ? tally[key] + 1 : 1
})
return tally
}
console.log(createTally(['a', 'b', 'a']))

I'm not too sure why you are checking the .length of the item in your for-loop implementation. The main thing you need to do is iterate over your array of items using a for loop. Then check if the current item is in your tally object. If it already is you can increment its associated counter value, if it isn't, you can initialize its associated counter value to 1.
The for loop won't iterate if your array length is initially 0 (ie: empty), so it will your code by default will return an empty object if this is the case.
See example below (see code comments for further details):
const createTally = items => {
const tally = {}; // acts as the `acc`
for (let i = 0; i < items.length; i++) { // loop over indexes in your array
let current = items[i]; // get current item in your array (curr)
// if statement peforms this logic seen in `.reduce()`: acc[curr] = (acc[curr] || 0) + 1
if(tally[current]) // if the current item is already a key in your object then...
tally[current]++ // increment the counter for current item
else // if the current item isn't an item in your object, then...
tally[current] = 1; // initialize the counter to 1
}
return tally; // return the `tally` (modified by the for loop)
}
console.log(createTally(['a', 'b', 'a'])); // {"a": 2, "b": 1}
console.log(createTally([])); // {}

You have created my unnecessary loops and condition. You just need a single loop and an if condition.
Run a loop. Check if the item is already inside the object. If it is there increase it by one. Otherwise assign it to 0 and then it will be increase by 1.
You also don't need to check the length of the array in the start. Consider if the length is 0 the for loop will not iterate even once and tally will be an empty object returned at end.
const createTally = items => {
const tally = {};
for (let i = 0; i < items.length; i++) {
if (!tally[items[i]]) {
tally[items[i]] = 0;
}
tally[items[i]]++;
}
return tally
}
console.log(createTally(['a', 'b', 'a']))

Related

Write a function getDuplicates

Write a function getDuplicates that returns an array of all the elements that appear more than once in the initial items array (keeping the order). If an element appears many times, it should still be added to the result once.
This is my code
function getDuplicates(items) {
let result = [];
if (items === [0,0,0,0]) {return [0]}
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
if (items[i] === items[j]) {
result.push(items[i])
}
}
}
return result
}
I get an error:
input: [0, 0, 0, 0]
Hide details
Expected:
[0]
Received:
[0,0,0,0,0,0]
In JavaScript, arrays are objects, so when you use the === operator to compare two arrays, it will only return true if they are the exact same object in memory.
Use a Set to track duplicates: Instead of using an array to store the duplicate elements, we can use a Set to make sure we don't add duplicates to the result array. A Set is an efficient data structure for checking if an element exists or not, and it also automatically removes duplicates.
Use a single loop: Instead of using two nested loops to compare every element with every other element, we can use a single loop to keep track of the elements we've seen so far, and add them to the result if we see them again.
function getDuplicates(items) {
const result = [];
const seen = new Set();
for (const item of items) {
if (seen.has(item) && !result.includes(item)) {
result.push(item);
} else {
seen.add(item);
}
}
return result;
}
console.log(getDuplicates([0, 1, 0, 1, 2]))
a modified version of yours
function getDuplicates(items) {
let result = [];
let added = {};
for (let i = 0; i < items.length; i++) {
if (!added[items[i]] && items.indexOf(items[i], i + 1) !== -1) {
result.push(items[i]);
added[items[i]] = true;
}
}
return result;
}
console.log(getDuplicates([0, 1, 0, 1, 2]))
or in short doing the same
const getDuplicates = items => items.filter((item, index) => items.indexOf(item) !== index && items.lastIndexOf(item) === index);
console.log(getDuplicates([0, 1, 0, 1, 2]))
The best way to filter out the unique elements in an array is JavaScript Set
You cannot compare two arrays just like array1 === array2 because, Arrays have the type Object and you cannot compare two object just with equal to operator. Objects are not compared based on their values but based on the references of the variables. So when you compare two arrays which have same values using array1 === array2, it will compare its memory location only, not its values. So it will be only false.
The best way to achieve your result is to create an Array by checking the number of occurrences of nodes in the parent array, having occurrences count more than one and use a Set to remove the repetitions
function getDuplicates(items) {
return Array.from(new Set(items.filter(node => items.filter(x => node === x).length > 1)))
}
console.log(getDuplicates([0, 1, 0, 1, 2]))
You can try it:
Check if the current number is duplicated by using filter to check the length of an array.
Check if the result array contains duplicates.
function getDuplicates(items) {
let result = [];
for (let i = 0; i < items.length; i++) {
if ((items.filter(item => item == items[i])).length > 1 && !result.includes(items[i])) {
result.push(items[i]);
}
}
return result;
}
console.log(getDuplicates([0, 0, 0, 0]));
So. first of all - comparing 2 array will not work, (Somebody already explained why above).
Your code doesn't work because of if statement. You're checking if an array doesn't have any value except 0.
Try summing all numbers in the array and check if it's 0.
if(arr.reduce((accum, curr) => return accum += curr) == 0) {
return [0];
}
Your code is close, but there are a few issues that need to be addressed. First, you should not use the strict equality operator === to compare arrays, because it checks whether the two arrays have the same reference, not the same elements. Instead, you can use the JSON.stringify() method to compare the string representations of the arrays.
Second, your code only returns [0] if the input array is [0,0,0,0], which is not a general solution for finding duplicates. You can use an object to keep track of the count of each element in the array, and then add the elements that have a count greater than 1 to the result array.
Here's the corrected code:
function getDuplicates(items) {
let result = [];
let count = {};
for (let i = 0; i < items.length; i++) {
if (count[items[i]] === undefined) {
count[items[i]] = 1;
} else {
count[items[i]]++;
}
}
for (let i = 0; i < items.length; i++) {
if (count[items[i]] > 1 && result.indexOf(items[i]) === -1) {
result.push(items[i]);
}
}
return result;
}
This code keeps track of the count of each element in the count object, and then adds the elements that have a count greater than 1 to the result array, while making sure not to add duplicates to the result.

How to convert the index of a flatten multidimensional array (any depth) to the original index?

For example, suppose I have a multidimensional array:
const a1=[["a",["a","b"]],"b",[["b"],"c"]];
which the flatten from is
const a2=["a","a","b","b","c","c"];
and accessing a2[2] is equivalent to a1[0][1][1], also access a2[3] is equivalent to a1[1].
However I'm not interested in what the element is in such index, instead I want a function that can convert the index of a2 equivalent to in a1, eg:
magicFunction(2,[["a",["a","b"]],"b",[["b"],"c"]])
returns [0,1,1],
also
magicFunction(3,[["a",["a","b"]],"b",[["b"],"c"]])
returns [1]. And the input multidimensional array can be any depth (more than 3 layers). How do I write such magic function?
Note: I know that may be easy to do if I know the array is 2d and the sub-array have same number of elements:
const a1=[["a","a"],["b","b"],["c","c"]];
const magicFunction=function(target,a){
let count=0;
for(let i=0;i<a.length;i++){
for(let j=0;j<a[i].length;j++){
if(count==target){
return [i,j];
}
count++;
}
}
return [];
};
alert(magicFunction(3,a1));
However now I need the function to accept a with n-dimensional array which d is unknown before testing, which I don't know how many layers of for-loop should I write to handle it, also the length of each sub-array may not be the same.
Here's a recursive function that generates the path by traversing the array and counting values until it reaches the n'th value. The function returns either an array of the path (if we reach the n'th value) or the current count (if we don't).
const magicFunction = (index, array, count=0) => {
for (let idx = 0; idx < array.length; idx++) {
if (Array.isArray(array[idx])) {
let res = magicFunction(index, array[idx], count)
if (Array.isArray(res)) return [idx].concat(res)
else count = res
}
else {
if (count == index) return [idx]
count++
}
}
return count
}
for (i = 0; i < 6; i++) {
res = magicFunction(i, [["a",["b","c"]],"d",[["e"],"f"]])
console.log(`${i} : [${res}]`)
}
You can flatten an array into a list of [element path] pairs and fetch paths from that list:
function* flatWithPaths(a, parent=[]) {
for (let [i, e] of a.entries()) {
if (Array.isArray(e))
yield* flatWithPaths(e, parent.concat(i))
else
yield [e, parent.concat(i)]
}
}
//
const a = [["a",["b","c"]],"d",[["e","f"],[[["g","h"]]]]]
let n = 0
for (let [elem, path] of flatWithPaths(a)) {
console.log(n++, elem, path.join())
}

Can I have an object key as part of an array?

I am trying to sort some numbers, and I want to count the number of times that an array has a certain number.
My question is more about the structure of an the array than the counting the number part. I would like to build an array that looks like this below.
let numbers = [1,2,3,4,4,4,5,5,6];
How could I make the data structure below?
numbers[3].count // this will be equal to 3 after I loop through;
How do I make each part of the array have an object parameter?
Do I just loop through like so?
for (let i = 0; i < numbers.length; i++){
numbers[i] = {
count: 0
}
}
I understand this wont give me the right count, but I don't care about that part of the problem. I would like to solve that on my own. I just need to be sure that this is the correct way to add the object parameters.
I would build these functions on my own. Something like this
You can copy and paste this in the console of your browser.
// my numbers list
const numbers = [1, 2, 3, 4, 4, 4, 5, 5, 6];
// reduced to unique entries
const uniques = [...new Set(numbers)];
// function to count occurrences in my list of number
const count = (n) => numbers.filter((num) => num === n).length;
// you can test here
console.log(`counting ${uniques[4]}s`, count(uniques[4]));
// get these as object
console.log(uniques.map((unique) => ({[unique]: count(unique)})))
Simplest way to achieve this by using Array.forEach().
let numbers = [1,2,3,4,4,4,5,5,6];
const obj = {};
numbers.forEach((item) => {
obj[item] = (obj[item] || 0) + 1;
});
console.log(obj);
const numbers = [1,2,3,4,4,4,5,5,6];
const counts = {};
numbers.forEach((x) => counts[x] = (counts[x] || 0) + 1);
console.log(counts)
You can use the Array#reduce() method to group elements together into sub arrays. Since arrays have a length property that gives the the number of elements this can be applied to each group of like elements. We do not need to create a new count property.
let numbers = [1,2,3,4,4,4,5,5,6];
const freq = numbers.reduce(
(acc,cur) => ({...acc,[cur]:(acc[cur] || []).concat(cur)})
);
console.log( freq[4] );
console.log( freq[4].length );
Alternatively, you can put the numbers in an object and get all the unique elements, then for each unique element define a get property that groups like elements together using the Array#filter() method. Again, the length array property can be used to return the number of elements for each unique element.
const o = {numbers: [1,2,3,4,4,4,5,5,6]};
o.uniq = [...new Set(o.numbers)];
o.uniq.forEach(n => Object.defineProperty(
o,n,{get:() => o.numbers.filter(num => num === n)}
));
console.log( o[5] );
console.log( o[5].length );
Reduce is perfect for these kinds of problems.
const numbers = [1,2,3,4,4,4,5,5,6];
const countedObject = numbers.reduce((tmpObj, number) => {
if (!tmpObj[number]) {
tmpObj[number] = 1;
} else {
tmpObj[number] += 1;
}
return tmpObj
}, {});
console.log(countedObject);
if you feel the need to nest it further you can of course do this.
But if count is the only property you need, I'd suggest sticking to the first version.
const numbers = [1,2,3,4,4,4,5,5,6];
const countedObject = numbers.reduce((tmpObj, number) => {
if (!tmpObj[number]) {
tmpObj[number] = {count: 1};
} else {
tmpObj[number].count += 1;
}
return tmpObj
}, {});
console.log(countedObject);

Finding first occurrence of each digit in an array

Taking each four digit number of an array in turn, return the number that you are on when all of the digits 0-9 have been discovered. If not all of the digits can be found, return "Missing digits!"
I've tried to loop through then set a conditional if (i != i+1) push into new array this just gave me the array, it's apparent my logic is wrong. could anyone help me out
For example calling this function with
arr = findAllDigits([5175, 4538, 2926, 5057, 6401, 4376, 2280, 6137, 8798, 9083])
the code should return 5057.
While calling
arr = findAllDigits([4883, 3876, 7769, 9846, 9546, 9634, 9696, 2832, 6822, 6868])
should return "missing numbers"
function findAllDigits(arr) {
newArr = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] != arr[i + 1]) newArr.push(arr[i]);
console.log(newArr);
}
}
Do I need to split because it is taking everything before the comma as
one number, then iterate over?
You can use Set here
Loop over the array and then create a set, You have to return the current number if set size becomes 10 because you need to check 0-9
function findAllDigits(arr) {
const set = new Set();
for (let n of arr) {
String(n)
.split("")
.forEach((c) => set.add(c));
if (set.size === 10) return n;
}
return "Missing digits!";
}
const arr1 = [5175, 4538, 2926, 5057, 6401, 4376, 2280, 6137, 8798, 9083];
const arr2 = [4883, 3876, 7769, 9846, 9546, 9634, 9696, 2832, 6822, 6868];
console.log(findAllDigits(arr1));
console.log(findAllDigits(arr2));
Your for loop is only checking to see if the array entry is equal to the next one. You need to split up the digits inside each entry and store them individually:
function findAllDigits(arr) {
newArr = [];
for (let i = 0; i < arr.length; i++) {
// now iterate the individual digits
const entryAsString = arr[i].toString();
for (let j = 0; j < entryAsString.length; j++) {
// if we haven't seen the digit before, add it to the array
if(!newArr.includes(j) {
newArr.push(j);
}
}
// we know we have all digits when newArr is 10 entries long
if (newArr.length) {
console.log(arr[i]);
// you can also return this value here
}
}
}
One more solution:
const arr1 = [5175, 4538, 2926, 5057, 6401, 4376, 2280, 6137, 8798, 9083];
const arr2 = [4883, 3876, 7769, 9846, 9546, 9634, 9696, 2832, 6822, 6868];
const findAllDigits = (arr) => {
// Declare new Set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
const digits = new Set();
// return the first item from array that fits the condition,
// find() method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
return arr.find((curr) => (
// String(5175) -> '5175' : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
// [...'5175'] -> ['5','1','7','5'] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
// .forEach(digits.add, digits) - forEach with callback function and context : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
// comma operator lets get rid of return : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
[...String(curr)].forEach(digits.add, digits),
// condition - is find() method need to return an item
(digits.size === 10)
// if returned value is not undefined or null return finded number oterwise error string
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
)) ?? "Missing digits!";
};
console.log(findAllDigits(arr1)); //5057
console.log(findAllDigits(arr2)); //Missing digits!

How to remove certain elements from an array into a new array and leave the others only the original array?

How to write a function to remove certain elements into a new array and leave the original array with only the remaining elements?
the first part is easy using a for loop pushing the even numbers into a new array but mutating the original array to leave only the odd numbers is hard
function remove(arr, cb){
var removed = [];
var newArr = [];
for(var i = 0; i < arr.length; i++) {
if(cb(arr[i], i, arr)) {
removed.push(arr[i]);
}
}
return removed;
}
Use an else statement to fill newArr with values that should stay in the original arr, then empty it using splice() before copying the items from newArr back into it.
function remove (arr, cb) {
var removed = [];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (cb(arr[i], i, arr)) {
removed.push(arr[i]);
} else {
newArr.push(arr[i]);
}
}
arr.splice(0);
for (var i = 0; i < newArr.length; i++) {
arr.push(newArr[i]);
}
return removed;
}
Welcome to Stackoverflow!
Personally, I'd avoid anything that mutates an input parameter, as this increases code complexity and makes it hard to reason about what's happening from the calling side.
Instead, I'd write a method that returns an array of two arrays. This can be easily split into two variables at the calling end using by using array destructuring.
See the example below:
const splitArr = (arr, pred) =>
arr.reduce(
(prev, curr, idx) => {
prev[+pred(curr, idx, arr)].push(curr);
return prev;
}, [[], []]
);
// usage //
const myArr = [1, 2, 3, 4];
const [arr1, arr2] = splitArr(myArr, x => x > 2);
console.log(arr1);
console.log(arr2);
Because pred is a function that returns a boolean value, we can co-erce this value to 0 or 1 using +someBoolean. We can then use this value as an index to decide into which of the two output arrays the value should be pushed.
You were definitely on the right track with your solution, a couple tweaks and we can make it very readable and also very easy to work with. I tried to keep the format of what it looked like you were doing.
I do take advantage of destructuring here, this could be returned as just an object, and then reference the properties.
const myArr = [0,1,2,3,4,5,6,7,8,9,10];
const splitItems = (arr, logicFunc) => {
let secondSet = []
const firstSet = arr.filter(v => {
if (logicFunc(v)) return true
else secondSet.push(v)
})
return { firstSet, secondSet }
}
const myLogicFunc = v => (v < 3 || v == 9)
const { firstSet, secondSet } = splitItems(myArr, myLogicFunc)
console.log(`My first set: ${firstSet}`) // My first set: 0,1,2,9
console.log(`My second set: ${secondSet}`) // My second set: 3,4,5,6,7,8,10
/* OR without destructuring:
const myArrays = splitItems(myArr, myLogicFunc)
console.log(`My first set: ${myArrays.firstSet}`)
console.log(`My second set: ${myArrays.secondSet}`)
*/
Please let me know if you have any questions
In modern JavaScript apps we do not mutate arrays we create new array, this avoids side effects, so what we do is create two new arrays
const split = (source, conditionFunc) = [ source.filter(i => conditionFunc(i)), source.filter(i => !conditionFunc(i))];
Then you have an array of two arrays of the values that meed condition and those that don't and you have not caused any side effects.
const odssAndEvens = split(source, i => i % 2 === 1);
Or with reduce so you don't iterate the array twice
const split = (source, conditionFunc) = source.reduce((results, item) => {
if (conditionFunc(item)) {
results[0].push(item);
} else {
results[1].push(item);
}
return results;
}, [[],[]]);

Categories

Resources