Function that sums array numbers (including numbers as strings) - javascript

I have a function that needs to sum all numbers in an arrays, if those numbers are strings '1', '2' the function needs to sum those too.
I have written a function and tried parsing the numbers but it doesn't work.
basically, it needs to sum up all numbers. Can you do it without using reduce? I need a simple solution or a solution with .map Where is my mistake?
function sumArrNums(arr) {
let count = 0;
arr.forEach(el => typeof el == 'number' ? count+= el : '');
return count;
}
console.log(sumArrNums(['1', '2', 5,5,5, '3']))

Your ternary operator is doing nothing when the element is a string, you can use Number(el) (or unary +) to convert elements to numbers (strings will be converted, and numbers will remain numbers, so there is no need for type checking):
function sumArrNums(arr) {
let count = 0;
arr.forEach(el => count += Number(el));
return count;
}
console.log(sumArrNums(['1', '2', 5, 5, 5, '3']))

You can use isNaN to check if the number or string can be parsed to string or not, and than add values
Here + before el does implicit conversion from string to number
function sumArrNums(arr) {
let count = 0;
arr.forEach(el => count += !isNaN(el) ? +el : 0);
return count;
}
console.log(sumArrNums(['1', '2', 5, 5, 5, '3', {}, '1a', [] ]))

I'd like to post a "meta"-answer, pointing at some archetypal mistakes made by you and other posters and frequently seen in other code reviews.
Mistake 1: unary +
Unary plus seriously hurts readability, especially in combination with other operators. Please do your readers (including your a few months older self) a favor and use the Number function - this is what it's for:
+a + +b // 👎 wtf?
Number(a) + Number(b) // 👍 copy that
Apart from readability, Number(x) is identical to +x in every way.
Mistake 2: not checking for NaNs
Number conversions can fail, and when they fail, they return a NaN and NaNs are sticky, so this will return NaN despite valid numbers being present in the array:
[1, 2, 'blah'].reduce((a, b) => Number(a) + Number(b)) // 👎 =NaN
This will be better (in the context of summation, NaN can be considered 0):
[1, 2, 'blah'].reduce((a, b) => (Number(a) || 0) + (Number(b) || 0)) // 👍 =3
Mistake 3: not checking for empty values
Unfortunately, Number is broken in javascript. For "historical reasons" it returns 0 when given null or an empty string. For the summation function it doesn't matter, but it will bite you once you decide to use similar code for multiplication.
Mistake 4: reduce with no initial value
array.reduce(func) looks tempting, but unfortunately it doesn't work with empty arrays
[].reduce((a, b) => a + b) // 👎 TypeError: Reduce of empty array with no initial value
so consider the init mandatory:
[].reduce((a, b) => a + b, 0) // 👍 returns 0
Mistake 5: wrong iteration method
The choice between iteration methods (forEach/map/filter/reduce) is tough sometimes, but this simple set of rules should help in most cases:
use map to convert a number of things to the same number of other things
use filter to convert a number of things to a lesser number of the same things
use reduce to convert a number of things to one other thing
do not use forEach
For example, this:
result = [];
array.forEach(item => result.push(do_something(item))) // 👎
is an "antipattern" and should actually be map:
result = array.map(do_something) // 👍
Similarly, this
result = 0;
array.map(item => result = result + item)) // 👎
should be
result = array.reduce((res, item) => result + item, 0) // 👍
Putting it all together
Our assignment basically consists of three parts:
convert all elements in the array to numbers
remove those that couldn't be converted
sum the rest
For the first step we use map, then filter, then reduce:
let sumNumbers = a => a
.map (x => Number(x)) // convert to numbers
.filter (x => !Number.isNaN(x)) // remove NaN's
.reduce ((s, x) => s + x, 0) // sum
On a more advanced note, with a couple of helpers we can also write this "point-free", without arrow functions:
let not = fn => x => !fn(x);
let add = (x, y) => x + y;
let sumNumbers = a => a
.map(Number)
.filter(not(Number.isNaN))
.reduce(add, 0)

Use unary + operator to convert your strings to numbers:
const sumArrNums = arr => arr.reduce((sum, num) => sum + +num, 0)
console.log(sumArrNums(['1', '2', 5,5,5, '3']))

Your code is okay, you just need to ensure that you coerce the strings to number. There are lots of ways to do that, in your case you might use the unary +:
function sumArrNums(arr) {
let count = 0;
arr.forEach(el => {
count += +el;
})
return count;
}
console.log(sumArrNums(['1', '2', 5,5,5, '3']))
and yes, this is one of the few really solid use cases for reduce:
function sumArrNums(arr) {
// NOTE: Assumes at least one entry! More below...
return arr.reduce((a, b) => +a + +b);
}
console.log(sumArrNums(['1', '2', 5,5,5, '3']))
Note there we're coercing both arguments to the callback, since on the first call they'll be the first two entries in the array (after that, the first argument will be the previously returned value, a number, but using + on it is a no-op so it's fine).
That code assumes that arr will have at least one entry. If it doesn't, reduce will fail because if you don't provide an initial value for the accumulator and there aren't any elements in the array, it doesn't have any value to return. If you want to return 0, the simplest thing is to provide the initial value, which also means you don't have to apply + to the accumulator:
function sumArrNums(arr) {
return arr.reduce((acc, value) => acc + +value, 0);
}
console.log(sumArrNums(['1', '2', 5,5,5, '3']))
If you want to return something else (like NaN) for the case where the array has no entries, you probably want to branch:
function sumArrNums(arr) {
return !arr.length ? NaN : arr.reduce((a, b) => +a + +b);
}
console.log(sumArrNums(['1', '2', 5,5,5, '3']))

Related

Why is my for loop breaking earlier than expected?

I am trying to solve a problem on leetCode:
Given an unsorted integer array nums, return the smallest missing positive integer.
This is the code I came up with
var firstMissingPositive = function(nums) {
nums.sort();
let x = 1; //this is to compare the elements of nums
for (let num in nums) {
if (nums[num] <= 0) continue; //because anything less than 1 does not matter
else if (nums[num] != x) break; //if x is not present, x is the answer
else x++; // if x is present, x should increment and check for the next integer
}
return x;
};
This code works 106/173 testcases. It does not pass the following case, which looks very simple -
nums = [1,2,3,4,5,6,7,8,9,20];
The output I get is 3, whereas the expected output is 10.
I'm not looking for the right solution to the problem. I'm just curious why this seemingly simple test fails. I do not understand why my loop breaks at 3 when it passes 1 and 2. Please help!
Here's the root cause of your problem (mdn):
The sort() method sorts the elements of an array in place and returns
the sorted array. The default sort order is ascending, built upon
converting the elements into strings, then comparing their sequences
of UTF-16 code units values.
So what you get after sort is [1, 2, 20, 3, ...], as '20' string precedes '3' string. One possible way to fix this it to force sorting by numeric value:
nums.sort((a, b) => a - b);

Array sorting is broken with Bigint In JS?

It seems like Array.prototype.sort() is broken with BigInt
This works
const big = [1n, 2n, 3n, 4n];
big.sort();
console.log(big);
// expected output: Array [1n, 2n, 3n, 4n]
But this doesn't :(
const big = [1n, 2n, 3n, 4n];
big.sort((a,b)=>a-b);
console.log(big);
//Error: Cannot convert a BigInt value to a number
or am i doing something wrong?
JavaScript sort method requires a function as a parameter that can compare two elements of the array and return either a positive number, or a negative number or zero. Number is the keyword here.
BigInt operations like addition and subtraction returns BigInt type and not a Number type. And that's why the error you are getting.
So, Something like this should do the job
const big = [1n, 2n, 3n, 4n];
big.sort((a ,b) => {
if(a > b) {
return 1;
} else if (a < b){
return -1;
} else {
return 0;
}
});
console.log(big);
Interestingly, MDN document that I linked to previously, also suggests how to sort an array of BigInts, and it is concise:
Copying the whole section here for posterity:
const mixed = [4n, 6, -12n, 10, 4, 0, 0n]
// ↪ [4n, 6, -12n, 10, 4, 0, 0n]
mixed.sort() // default sorting behavior
// ↪ [ -12n, 0, 0n, 10, 4n, 4, 6 ]
mixed.sort((a, b) => a - b)
// won't work since subtraction will not work with mixed types
// TypeError: can't convert BigInt to number
// sort with an appropriate numeric comparator
mixed.sort((a, b) => (a < b) ? -1 : ((a > b) ? 1 : 0))
// ↪ [ -12n, 0, 0n, 4n, 4, 6, 10 ]
The reason is that a - b in the sort callback function will return a BigInt data type, while sort expects it to return something that is (or can coerce to) a Number data type.
So you can use a > b || -(a < b) as callback expression:
const big = [10n, 9n, 8n, 7n];
big.sort((a, b) => a > b || -(a < b));
console.log(big + ""); // 7,8,9,10
Note that the first version (without sort callback) does not work in general, because then sort will compare the elements as strings. It is clear that this can yield results that are not numerically sorted:
const big = [10n, 9n, 8n, 7n];
big.sort(); // string-based sort
console.log(big + ""); // 10,7,8,9 is wrong
This might appear to be broken at first but its not, the problem is definition of the compare function is always expecting a return value of -1, 0, 1 and it has no reason to expect bigint of -1 0 1 cause they all are in range of normal int..
so this should solve
big.sort((a,b)=>a<b?-1:(a>b?1:0));

happy number algo question solution not working

trying to figure out this coding problem:
Write an algorithm to determine if a number n is "happy".
A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.
Return True if n is a happy number, and False if not.
I've done some work but I'm not sure what I'm doing incorrectly. Would appreciate some pointers. Thanks!
function isHappy(numba1){
let sum = 0;
numba = numba1.toString().split('')
let numbaArr = numba.map(y => parseInt(y))
for (var x = 0; x< numbaArr.length; x++){
sum += numbaArr[x] ** 2
}
if (sum > 1){
isHappy(sum)
}
else if (sum === 1){
return true
}
else if (sum <= 0){
return false
}
}
There are two problems I see with your answer, one small and one large.
Small: The value of the recursive call is not being returned. This:
if (sum > 1){
isHappy(sum)
}
should be
if (sum > 1){
return isHappy(sum)
}
Large: you are not doing the essential work of checking whether we're cycling over the same values. For instance in testing 15, we get these values
15, 26, 40, 16, 37, 58, 89, 145, 42, 20, 4, 16
^^ ^^
and we can quit because we've seen 16 twice. 15 is not happy. But for 44 we get
44, 32, 13, 10, 1
and we hit 1 without cycling, so 44 is a happy number.
Your code needs to keep track of the values it's seen so far.
Here's one recursive approach:
const digitsSquareSum = (n) =>
String (n) .split ('') .map (n => n * n) .reduce ((a, b) => a + b, 0)
const _isHappy = (n, seen) =>
n == 1
? true
: seen .has (n)
? false
: _isHappy (digitsSquareSum (n), seen .add (n))
const isHappy = (n) =>
_isHappy(n, new Set())
// display the happy numbers among the first 100 positive integers
console .log (Array .from ({length: 100}, (_, i) => i + 1) .filter (isHappy) .join(', '))
We use a helper function to calculate the sum of the squares of the digits. This simply makes the main function cleaner. The main function, _isHappy is an internal, private function, not to be exposed to the users. It is recursive and maintains a Set of the values we've already seen. If our number is 1, we return true. If our number is in the set of values we've already seen, we return false. Otherwise, we add it to the already seen set, calculate the next test case by calling our helper, and call _isHappy with those.
Our public function simply calls this main function, creating the initial empty Set of seen values, and passing that along with the number to test.
In our demo, we use Array .from ({length: 100}, (_, i) => i + 1), which is one of several compact ways of creating an array of integers from 1 to 100. In practice, I would abstract this into a range function that takes lo and hi values and creates an array of integers in between them, but that's outside the point of this answer.
We do not have to use this breakdown of an internal recursive function with two parameters and a public function with one. We could use a default parameter like this:
const isHappy = (n, seen = new Set()) =>
console .log({n, seen}) ||
n == 1
? true
: seen .has (n)
? false
: isHappy (digitsSquareSum (n), seen .add (n))
But there are some potential problems with this. For instance we could not call it like we did before:
range(1, 100) .filter (isHappy)
because filter supplies additional parameters to its callback. Not only does it supply the value but also the index and the whole array. However isHappy thinks the second parameter is the Set of seen values; when it gets passed the index, things will fail. We can do this instead:
range(1, 100) .filter ((n) => isHappy (n))
But we will always have to take such cautions when writing this way. I've gotten in the habit of doing this only for internal functions where I control how it's called. And still once in a while it bites me.
Keep a list of seen numbers and mod 10 to get the last digit then divide by 10 and floor to shift digits:
function sumSquares(num) {
let sum = 0;
while (num != 0) {
sum += (num % 10) ** 2;
num = Math.floor(num / 10);
}
return sum;
}
function isHappy(num) {
const seen = new Set();
while (num != 1 && !seen.has(num)) {
seen.add(num);
num = sumSquares(num);
}
return num == 1;
}

sorting an array and comparing it to the original

I'm trying to fully understand functional programming when sorting an array why does the original array change to the sorted array also? I
want to check if the array is in ascending order.
let arr = [1,2,8,3,9,88,67];
let newArr = arr.sort((a,b) => a-b);
console.log(newArr);
console.log(arr);
I want to do something like....
if (arr === newArr) {
return true;
} else {
return false;
}
The original arr is also being sorted so it always is true,
some guidance would be great thanks.
That's just the way the sort operator was designed. It modifies the existing array, rather than creating a new array. If you want to create a new array, you can do it like this:
let arr = [1,2,8,3,9,88,67];
let newArr = arr.slice(); // creates a copy of the array
newArr.sort((a,b) => a-b);
console.log(newArr);
console.log(arr);
Javascript array is an object, When 2 variables reference the same object, changing one would change the value of the other
let obj1 = {x:1, y:2};
let obj2 = obj1;
obj2.x = 3;
console.log(obj1);
You can sort it using
let arr = [1,2,8,3,9,88,67];
let newArr = arr.slice().sort((a,b) => a-b);
console.log(newArr);
console.log(arr);
By assigning an object, you take the reference of the object, that means ecery change affects the same object.
For arrays, you could take "a shallow copy" of it with Array#slice and then map the check of the items.
var array = [1, 2, 8, 3, 9, 88, 67],
sorted = array.slice().sort((a, b) => a - b),
position = array.map((a, i) => a === sorted[i]);
console.log(sorted);
console.log(position);
.as-console-wrapper { max-height: 100% !important; top: 0; }
According to your question,
you just need to identify
If the array is in ascending order
To do that just apply some simple logic and we do not want to compare it with a sorted array.
This is the speediest method since it will break the loop on wrong condition.
let arr = [1,2,8,3,9,88,67];
let is_sorted = true;
for(let i=0, length = arr.length; i < length - 1; i++){
if(Number(arr[i]) > Number(arr[i+1])){
is_sorted = false;
break;
}
}
console.log("Is Sorted: ", is_sorted);
This is an XY problem – ie, you want "... to check if the array is in ascending order" and you tried sorting (via Array.prototype.sort) and then you're checking the result using binary operator ==. The result isn't what you expect, so you ask about the sort instead of keeping the focus on your actual goal: checking for ascending order
A case for why you shouldn't sort
Imagine a large array of hundreds or thousands or items. Is the following array in ascending order?
isAscending ([ 5, 1, ... thousands more items ... ])
// => true or false ?
Of course it's false because 1 is less than 5; the array is not in ascending order. Did we have to sort the entire array to arrive at that answer? No, we only needed to look at the first 2 items (in this case) to know the answer is false
Other answers show using .slice to copy the input array – this is silly tho, because of course we don't have to copy an array to determine if it is in ascending order – it's a waste of time/space.
A case for why you shouldn't use ==
Well first, you can't, because of the following (in JavaScript)
[ 1, 2, 3 ] == [ 1, 2, 3 ]
// => false
So how would use == if you could? Maybe an approach would be to check if each element in the first array is equal to each array element in the second array. Ok, now imagine two large arrays of hundreds of thousands of items. Are the following two arrays equal?
[ 1, ... thousands of items ... ] == [ 2, ... thousands of items ... ]
// => true or false ?
We know they're not equal because 1 != 2 – it's the same case as the sorting; there's no sense in comparing the rest of the items because we already know the arrays are not equal
There's other ways to compare Arrays (and objects) in JavaScript, but we only have one input array, so there's no array to compare it to – this is another dead-end to the approach
Check if an array is in ascending order
Ok, so now that were done talking about array sorting and array equality in JavaScript, we can actually write a function that does what you intend it to do
const isAscending = ([x,y,...rest]) =>
x === undefined // array is empty
? true
: y === undefined // single element array
? true
: x > y // first element is larger than second
? false
: isAscending ([y,...rest]) // check remaining elements
console.log (isAscending ([])) // true
console.log (isAscending ([1])) // true
console.log (isAscending ([1,3,5,7])) // true
console.log (isAscending ([5,1,3,7])) // false
Stack-safe recursion
But you have to be careful using recursion in JavaScript – input arrays of just a couple thousand elements could cause a stack overflow in the program above
Using a constant-space looping mechanism, we can rewrite isAscending in a way that works on arrays of any input size. The loop/recur interface gives us an opportunity to track any number/type of state variables, so this implementation also avoids the costly creation of many array slices (in rest parameter and spread argument syntax above)
const recur = (...args) =>
({ type: recur, args })
const loop = f =>
{
let acc = f ()
while (acc.type === recur)
acc = f (...acc.args)
return acc
}
const isAscending = xs =>
xs.length < 2
? true
: loop ((a = 0, b = 1) =>
a === xs.length
? true
: xs [a] > xs [b]
? false
: recur (a + 1, b + 1)) // *
console.log (isAscending ([])) // true
console.log (isAscending ([1])) // true
console.log (isAscending ([1,3,5,7])) // true
console.log (isAscending ([5,1,3,7])) // false
* or recur (b, b + 1), which saves on addition operation per array element
The sort method applied to an array will modify the array itself. So it's logic to have the first array sorted and both arr and newArr will be equal.
To test if the array is in ascending order you may loop through it and check if there is and index i where arr[i+1] < arr[i]
let arr = [1,2,8,3,9,88,67];
let test=true;
for (var i = 1; i < arr.length; i++) {
if(arr[i]<arr[i-1]) {
test=false;
break;
}
}
console.log(test);

sortedIndex for reverse sorted array?

It seems lodash's sortedIndex expects a forward sorted array for its binary search to work. (e.g. [0,1,2,4])
Is there a way to used sortedIndexBy when the array is reverse sorted? (e.g. [4,2,1,0])?
> _.sortedIndex( [0,1,2,4], 3 )
> 3
> _.sortedIndex( [4,2,1,0], 3 )
> 4
To get this to work now, I have to reverse the array, find the sortedIndex, insert the new element, and then un-reverse the array.
Note -- need something which works for sorting strings as well as numbers.
['A','B','D'] into ['D','B','A'] and insert 'C'.
How about _.sortedIndexBy?
Edited: For string comparison, String.prototype.charCodeAt() can help you convert it to Number, then the same logic can be applied.
const arr1 = [0, 1, 2, 4];
const arr2 = [4, 2 ,1, 0];
console.log(_.sortedIndex(arr1, 3 ));
// Similar, but with ranking function.
console.log(_.sortedIndexBy(arr2, 3, function(x) {return -x;}));
const charArr = ['D','B','A'];
// Take the first char and convert to Number
let index = _.sortedIndexBy(charArr, 'C', function(x) {
// Type checks. (If you want it to be general to many types..
if (typeof x === 'string') {
return -x.charCodeAt(0);
} else if (typeof x === 'number') {
return -x;
} // else ... for other types.....
});
console.log('To insert char C, put it to index: ', index);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
or by _.sortedIndex, it also has iteratee to rank before 4.0.0
const arr1 = [0, 1, 2, 4];
const arr2 = [4, 2 ,1, 0];
console.log(_.sortedIndex(arr1, 3));
console.log("Reversed order without ranking func: ",_.sortedIndex(arr2, 3));
// Ranking function to inverse the order.
console.log("Reversed order with ranking func: ",_.sortedIndex(arr2, 3, function(x) {return -x;}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.3.1/lodash.min.js"></script>
Thanks to pilau:
The sortedIndex expects the array is forward sorted, so we can't just put the reverse sorted array and get arr.length - index, and in order to handle vary scenario, I think we need to do either
Reverse array -> get sorted index and put -> reverse it again. or
Get reversed copy by slice and reverse -> get sorted index and calculate by arr.length - index -> inserted to origin array.
To achieve the expected result.

Categories