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));
Related
Disclaimer: I'm new to JS.
So, all of my test are passing but one - the last one - and I can't figure out how to fix it. Why is 0 returning the last element and not an empty array? Also, on a separate note, when I tried to use a default value it returned the wrong values hence why I decided to include a conditional statement.
const takeRight = (array, n) => {
n = -n;
//setting both default and a negative value for 'n': if there is no number, then n should be -1
const condition = !n ? -1 : n;
return array.slice(condition);
};
console.log(takeRight([1, 2, 3])); // returns [3] --> expected [3]
console.log(takeRight([1, 2, 3], 2)); //returns [2, 3] --> expected [2,3]
console.log(takeRight([1, 2, 3], 5)); //returns [1, 2, 3] --> expected [1,2,3]
console.log(takeRight([1, 2, 3], 0)); //returns [3] --> expected []
The problem is in calculating condition variable:
const condition = !n ? -1 : n;
expression !0 is true, so n is changed to -1 when has value 0.
If you want to check against not a numbers, use isNaN.:
const takeRight = (array, n) => {
n = isNaN(n) ? -1 : -n;
return array.slice(n);
};
The problem might be your expression. Check for "Not a Number"
const condition = isNaN(n) ? -1 : n;
MDN: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/isNaN
So, first of all, you shouldn't reassign arguments. But the actual issue here is using !n to test if it exists. It might be a neat trick here and there but is actually just confusing. Prefixing anything with a ! in JavaScript turns it into a boolean and then negates it, and the rules for this conversion are not obvious and also not the same in every language. But in JS, !0 gives you the same result as !undefined or !null, because the number 0 often symbolizes an "off-state", basically a false, but shorter. Anyways, to avoid all of that, just use default parameters and use a ternary operator in the return statement to check for the specific case of n = 0 like so:
const takeRight = (array, n = 1) => n === 0 ? [] : condition array.slice(-n);
You can of course make that a bit more verbose as well:
const takeRight = (array, n = 1) => {
if (n === 0) {
return [];
}
return condition array.slice(-n);
};
EDIT: Even !"" returns true by the way, which is not really "obvious" as well, so using ! to check if something is defined or exists, can really cause problems and unexpected outcomes. I'd advise against it unless you really really know what you are doing and why.
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;
}
The code below is modified version of a code taken from the book Professional JavaScript for Web Developers.
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
function numElements(typedArrayConstructor, ...typedArrays) {
// Count the total elements in all arrays
return typedArrays.reduce((x,y) => (x.length || x) + y.length);
}
console.log(numElements(Int32Array, Int8Array.of(1, 2, 3), Int16Array.of(4, 5, 6), Float32Array.of(7, 8, 9)));
My question is what does the (x.length || x) do? Why do we need to perform an or operation on x.length and x?
A little more explanation to go with Pointy's answer:
The || in JavaScript isn't just a logical OR operation, it deals with "truthy/falsey" values, not just booleans.
undefined is falsey. When the first operand of || is falsey, the second operand is evaluated, and becomes the result of the expression. Thus undefined || 0 equals 0.
In your sample code, this means when x is 0, you add 0, and get a proper numeric result. If you try to add to undefined to another number, all of your calculations turn into NaN after that.
When .reduce() is invoked with only one argument, the very first iteration uses element 0 as the first callback parameter and element 1 as the second.
In your case, that means that on the first iteration, x will be one of the arrays (note that y is always an array). Thus that little expression differentiates between when it's the first iteration and when it's a subsequent iteration by taking advantage of the fact that
someNumber.length
is always undefined. (As correctly noted in another answer, it's critical to recall that (undefined || x) will always be x, whatever its value may be.) On subsequent iterations therefore x is the running total of the array lengths.
The .reduce() could have been written as follows instead:
return typedArrays.reduce((x,y) => x + y.length, 0);
By passing the second argument (0) to .reduce(), the first callback invocation will be the same as the others, and x will always be a number.
If x has any elements and x exists then use the length in the sum. Otherwise if length is undefined then return the current element x
Example 1: - happens on first iteration of reduce loop
x is array [1, 2, 3]
x.length || x -> returns the length of array or current total
// summation of code would do the following
firsthArrayLength + secondArrayLength = newTotal
Example 2: - happens on rest of iterations of reduce loop
x is integer 5
x.length || x -> returns x since length of integer is undefined
// summation of code would do the following
currentTotal + curLength = newTotal
NOTE: Keep in mind that with this example if any of the arrays is null or undefined then it will throw since we cannot access property length of undefined or null
So sad that is taken from a book. Using reduce and || like that is reckless and unprofessional -
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
const numElements = (constructor, ...arrs) =>
new constructor(arrs.reduce((r, a) => r + a.length, 0))
const result =
numElements
( Int32Array
, Int8Array.of(1, 2, 3)
, Int16Array.of(4, 5, 6)
, Float32Array.of(7, 8, 9)
)
console.log(result.constructor)
console.log(result)
// Int32Array
// { 0, 0, 0, 0, 0, 0, 0, 0, 0 }
The description of the function says it should concat the other arrays, not just initialise an empty typed array. Here's what that might look like -
// First argument is the type of array that should be returned
// Remaining arguments are all the typed arrays that should be concatenated
const concatMixed = (constructor, ...arrs) =>
{ const r = new constructor(arrs.reduce((r, a) => r + a.length, 0))
let i = 0
for (const a of arrs)
for (const val of a)
r[i++] = val
return r
}
const result =
concatMixed
( Int32Array
, Int8Array.of(1, 2, 3)
, Int16Array.of(4, 5, 6)
, Float32Array.of(7.1, 8.2, 9.3)
)
console.log(result.constructor)
console.log(result)
// Int32Array
// { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
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']))
I am studying Javascript and currently learning the .sort() function for arrays. I understood that it can take either no argument or one between a-b and b-a.
What I don't understand however is the use of return 0, return -1 and return 1. Here is an example (source: http://www.codewars.com/kata/572df796914b5ba27c000c90) :
var arr=[1,2,3,4,5,6,100,999]
arr.sort((a,b)=>{
if (a%2==b%2) return a-b;
if (a%2>b%2) return -1;
return 1;
})
console.log(arr)
//output: [ 1, 3, 5, 999, 2, 4, 6, 100 ]
I understand what it's supposed to do, i.e. separate odd and even numbers and sort them in ascending order. But what is the meaning of return -1 and return 1? Can someone walk me through this function step by step?
I tried to play with the code and change some values, for example change return -1 to return 0, to try to understand how it could work, but I still don't get it.
Where can I find resources with details about that return element?
According to the sort docs:
If the parameter functionComparison is supplied, the elements of the
array are sorted according to the return value of the comparison
function. If a and bare two elements to compare, then:
If functionComparison(a, b) is less than 0, we sort a with an index
less than b( a will be ranked before b)
If functionComparison(a, b) returns 0, we leave a and b unchanged
relative to each other, but sorted with respect to all the other
elements. Note: The ECMAScript standard does not guarantee this
behavior, so all browsers (eg Mozilla versions prior to 2003) do not
respect this. If functionComparison(a, b) is greater than 0, we sort b
with an index less than a.
functionComparison(a, b) must always return the same result from the
same pair of arguments. If the function returns inconsistent results,
then the order in which the items are sorted is not defined.
Now if a > b, returning 1 or a positive value is one and the same thing, similarly, if a < b then returning -1 or the difference is the same. If both are equal the difference is 0 and hence return 0
Where can I find resources with details about that return element?
return value is for the comparator function.
As per spec
If comparefn is not undefined, it should be a function that accepts
two arguments x and y and returns a negative value if x < y, zero if x
= y, or a positive value if x > y.