Formatting a number by a decimal - javascript

I'm trying to transform an array of numbers such that each number has only one nonzero digit.
so basically
"7970521.5544"
will give me
["7000000", "900000", "70000", "500", "20", "1", ".5", ".05", ".004", ".0004"]
I tried:
var j = "7970521.5544"
var k =j.replace('.','')
var result = k.split('')
for (var i = 0; i < result.length; i++) {
console.log(parseFloat(Math.round(result[i] * 10000) /10).toFixed(10))
}
Any ideas, I'm not sure where to go from here?

Algorithm:
Split the number in two parts using the decimal notation.
Run a for loop to multiply each digit with the corresponding power of 10, like:
value = value * Math.pow(10, index); // for digits before decimal
value = value * Math.pow(10, -1 * index); // for digits after decimal
Then, filter the non-zero elements and concatenate both the arrays. (remember to re-reverse the left-side array)
var n = "7970521.5544"
var arr = n.split('.'); // '7970521' and '5544'
var left = arr[0].split('').reverse(); // '1250797'
var right = arr[1].split(''); // '5544'
for(let i = 0; i < left.length; i++)
left[i] = (+left[i] * Math.pow(10, i) || '').toString();
for(let i = 0; i < right.length; i++)
right[i] = '.' + +right[i] * Math.pow(10, -i);
let res = left.reverse() // reverses the array
.filter(n => !!n)
// ^^^^^^ filters those value which are non zero
.concat(right.filter(n => n !== '.0'));
// ^^^^^^ concatenation
console.log(res);

You can use padStart and padEnd combined with reduce() to build the array. The amount you want to pad will be the index of the decimal minus the index in the loop for items left of the decimal and the opposite on the right.
Using reduce() you can make a new array with the padded strings taking care to avoid the zeroes and the decimal itself.
let s = "7970521.5544"
let arr = s.split('')
let d_index = s.indexOf('.')
if (d_index == -1) d_index = s.length // edge case for nums with no decimal
let nums = arr.reduce((arr, n, i) => {
if (n == 0 || i == d_index) return arr
arr.push((i < d_index)
? n.padEnd(d_index - i, '0')
: '.' + n.padStart(i - d_index, '0'))
return arr
}, [])
console.log(nums)

You could split your string and then utilize Array.prototype.reduce method. Take note of the decimal position and then just pad your value with "0" accordingly. Something like below:
var s = "7970521.5544";
var original = s.split('');
var decimalPosition = original.indexOf('.');
var placeValues = original.reduce((accum, el, idx) => {
var f = el;
if (idx < decimalPosition) {
for (let i = idx; i < (decimalPosition - 1); i++) {
f += "0";
}
accum.push(f);
} else if (idx > decimalPosition) {
let offset = Math.abs(decimalPosition - idx) - 2;
for (let i = 0; i <= offset; i++) {
f = "0" + f;
}
f = "." + f;
accum.push(f);
}
return accum;
}, []);
console.log(placeValues);

Shorter alternative (doesn't work in IE) :
var s = "7970521.5544"
var i = s.split('.')[0].length
var a = [...s].reduce((a, c) => (i && +c && a.push(i > 0 ?
c.padEnd(i, 0) : '.'.padEnd(-i, 0) + c), --i, a), [])
console.log( a )
IE version :
var s = "7970521.5544"
var i = s.split('.')[0].length
var a = [].reduce.call(s, function(a, c) { return (i && +c && a.push(i > 0 ?
c + Array(i).join(0) : '.' + Array(-i).join(0) + c), --i, a); }, [])
console.log( a )

function standardToExpanded(n) {
return String(String(Number(n))
.split(".")
.map(function(n, i) {
// digits, decimals..
var v = n.split("");
// reverse decimals..
v = i ? v.reverse() : v;
v = v
.map(function(x, j) {
// expanded term..
return Number([x, n.slice(j + 1).replace(/\d/g, 0)].join(""));
})
.filter(Boolean); // omit zero terms
// unreverse decimals..
v = i ? v.map(function(x) {
return '.' + String(x).split('').reverse().join('')
}).reverse() : v;
return v;
})).split(',');
}
console.log(standardToExpanded("7970521.5544"));
// -> ["7000000", "900000", "70000", "500", "20", "1", ".5", ".05", ".004", ".0004"]
This looks like something out of my son's old 3rd Grade (core curriculum) Math book!

Related

How to generate a random number based on fixed character set in javascript

I'm generating a number based on a fixed character set.
function generator()
{
var text = "";
var char_list = "LEDGJR", number_list = "0123456789";
for(var i=0; i < 2; i++ )
{
text += char_list.charAt(Math.floor(Math.random() * char_list.length));
}
for(var j=0; j < 2; j++ )
{
text += number_list.charAt(Math.floor(Math.random() *
number_list.length));
}
return text;
}
Result :
RE39, JE12 etc...
Once all the permutation related to the above sequence is done, then the generator should generate string as RE391, JE125 means adding one more number to the complete number.
How can I get the permutation count of sequence?
For simplicity consider the case where:
chars = "AB"
nums = "123";
and we want to generate a 4-digit sequence of two chars and two numbers.
We define these variables
rows = [chars, chars, nums, nums]
rowSizes = rows.map(row => row.length) // [2, 2, 3, 3]
It’s easy to see the set size of all possible permuations equals:
spaceSize = rowSizes.reduce((m, n) => m * n, 1) // 2*2*3*3 = 36
And we define two set of utility functions, usage of which I'll explain in detail later.
decodeIndex() which gives us uniqueness
function euclideanDivision(a, b) {
const remainder = a % b;
const quotient = (a - remainder) / b;
return [quotient, remainder]
}
function decodeIndex(index, rowSizes) {
const rowIndexes = []
let dividend = index
for (let i = 0; i < rowSizes.length; i++) {
const [quotient, remainder] = euclideanDivision(dividend, rowSizes[i])
rowIndexes[i] = remainder
dividend = quotient
}
return rowIndexes
}
getNextIndex() which gives us pseudo-randomness
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q]
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N
}
Uniqueness
Like mentioned above, spaceSize is the number of all possible permutations, thus each index in range(0, spaceSize) uniquely maps to one permutation. decodeIndex helps with this mapping, you can get the corresponding permutation to an index by:
function getSequenceAtIndex(index) {
const tuple = decodeIndex(index, rowSizes)
return rows.map((row, i) => row[tuple[i]]).join('')
}
Pseudo-Randomness
(Credit to this question. I just port that code into JS.)
We get pseudo-randomness by polling a "full cycle iterator"†. The idea is simple:
have the indexes 0..35 layout in a circle, denote upperbound as N=36
decide a step size, denoted as Q (Q=23 in this case) given by this formula‡
Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
randomly decide a starting point, e.g. number 5
start generating seemingly random nextIndex from prevIndex, by
nextIndex = (prevIndex + Q) % N
So if we put 5 in we get (5 + 23) % 36 == 28. Put 28 in we get (28 + 23) % 36 == 15.
This process will go through every number in circle (jump back and forth among points on the circle), it will pick each number only once, without repeating. When we get back to our starting point 5, we know we've reach the end.
†: I'm not sure about this term, just quoting from this answer
‡: This formula only gives a nice step size that will make things look more "random", the only requirement for Q is it must be coprime to N
Full Solution
Now let's put all the pieces together. Run the snippet to see result.
I've also includes the a counter before each log. For your case with char_list="LEDGJR", number_list="0123456789", the spaceSize for 4-digit sequence should be 6*6*10*10 = 3600
You'll observe the log bump to 5-digit sequence at 3601 😉
function euclideanDivision(a, b) {
const remainder = a % b;
const quotient = (a - remainder) / b;
return [quotient, remainder];
}
function decodeIndex(index, rowSizes) {
const rowIndexes = [];
let divident = index;
for (let i = 0; i < rowSizes.length; i++) {
const [quotient, remainder] = euclideanDivision(divident, rowSizes[i]);
rowIndexes[i] = remainder;
divident = quotient;
}
return rowIndexes;
}
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor((2 * N) / (1 + Math.sqrt(5))));
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q];
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N;
}
function generatorFactory(rows) {
const rowSizes = rows.map((row) => row.length);
function getSequenceAtIndex(index) {
const tuple = decodeIndex(index, rowSizes);
return rows.map((row, i) => row[tuple[i]]).join("");
}
const spaceSize = rowSizes.reduce((m, n) => m * n, 1);
const [firstIndex, N, Q] = getIndexGeneratorParams(spaceSize);
let currentIndex = firstIndex;
let exhausted = false;
function generator() {
if (exhausted) return null;
const sequence = getSequenceAtIndex(currentIndex);
currentIndex = getNextIndex(currentIndex, N, Q);
if (currentIndex === firstIndex) exhausted = true;
return sequence;
}
return generator;
}
function getRows(chars, nums, rowsOfChars, rowsOfNums) {
const rows = [];
while (rowsOfChars--) {
rows.push(chars);
}
while (rowsOfNums--) {
rows.push(nums);
}
return rows;
}
function autoRenewGeneratorFactory(chars, nums, initRowsOfChars, initRowsOfNums) {
let realGenerator;
let currentRowOfNums = initRowsOfNums;
function createRealGenerator() {
const rows = getRows(chars, nums, initRowsOfChars, currentRowOfNums);
const generator = generatorFactory(rows);
currentRowOfNums++;
return generator;
}
realGenerator = createRealGenerator();
function proxyGenerator() {
const sequence = realGenerator();
if (sequence === null) {
realGenerator = createRealGenerator();
return realGenerator();
} else {
return sequence;
}
}
return proxyGenerator;
}
function main() {
const char_list = "LEDGJR"
const number_list = "0123456789";
const generator = autoRenewGeneratorFactory(char_list, number_list, 2, 2);
let couter = 0
setInterval(() => {
console.log(++couter, generator())
}, 10);
}
main();

Function in Javascript that inserts dashes or asterisks between each two odd or even numbers

I want to write a function that inserts dashes (' - ') between each two odd numbers and inserts asterisks (' * ') between each two even numbers. For instance:
Input: 99946
Output: 9-9-94*6
Input: 24877
Output: 2*4*87-7
My try
function dashAst (para) {
let stringArray = para.toString().split('');
let numbArray = stringArray.map(Number);
for (let i = 0; i<numbArray.length; i++) {
if (numbArray[i] %2 === 0 && numbArray[i+1] % 2 === 0) {
numbArray.splice(numbArray.indexOf(numbArray[i]), 0, '*')
}
else if (numbArray[i] %2 !== 0 && numbArray[i+1] %2 !== 0) {
numbArray.splice(numbArray.indexOf(numbArray[i]), 0, '-')
}
}
return numbArray
}
When I try to invoke the function it returns nothing. For instance, I tested the splice-command separately and it seems to be correct which makes it even more confusing to me. Thanks to everyone reading, or even helping a beginner out.
Looping through an Array that changes its length during the loop can be very messy (i needs to be adjusted every time you splice). It's easier to create a new result variable:
function dashAst(para) {
const stringArray = para.toString().split('');
const numbArray = stringArray.map(Number);
let result = "";
for (let i = 0; i < numbArray.length; i++) {
const n = numbArray[i], next = numbArray[i + 1];
result += n;
if (n % 2 == next % 2) {
result += n % 2 ? '-' : '*';
}
}
return result;
}
console.log(dashAst(99946)); // "9-9-94*6"
console.log(dashAst(24877)); // "2*4*87-7"
You could map the values by checking if the item and next item have the same modulo and take a separator which is defined by the modulo.
function dashAst(value) {
return [...value.toString()]
.map((v, i, a) => v % 2 === a[i + 1] % 2 ? v + '*-'[v % 2] : v)
.join('');
}
console.log(dashAst(99946)); // 9-9-94*6
console.log(dashAst(24877)); // 2*4*87-7
I hope this helps
var str = '24877';
function dashAst (para) {
let stringArray = para.toString().split('');
let numbArray = stringArray.map(x => parseInt(x));
console.log(numbArray);
var out=[];
for(let i = 0; i < numbArray.length; i++) {
if(numbArray[i] % 2 == 0){
out.push(numbArray[i]);
numbArray[i + 1] % 2 == 0 ? out.push('*') : 0;
}else if(numbArray[i] % 2 != 0) {
out.push(numbArray[i]);
numbArray[i + 1] != undefined ? out.push('-') : 0;
}
}
console.log(out.join(''));
return out;
}
dashAst(str);

Determining if an integer can be expressed as a palindromic sum

I was asked this question in an interview:
An integer is special if it can be expressed as a sum that's a palindrome (the same backwards as forwards). For example, 22 and 121 are both special, because 22 equals 11+11 and 121 equals 29+92.
Given an array of integers, count how many of its elements are special.
but I couldn't think of any solution. How can this be done?
In the stress and the hurry of an interview, I would have certainly found a dumb and naive solution.
pseudo code
loop that array containing the numbers
Looping from nb = 0 to (*the number to test* / 2)
convert nb to string and reverse the order of that string (ie : if you get "29", transform it to "92")
convert back the string to a nb2
if (nb + nb2 == *the number to test*)
this number is special. Store it in the result array
end loop
end loop
print the result array
function IsNumberSpecial(input)
{
for (let nb1 = 0; nb1 <= (input / 2); ++nb1)
{
let nb2 = parseInt(("" + nb1).split("").reverse().join("")); // get the reverse number
if (nb2 + nb1 == input)
{
console.log(nb1 + " + " + nb2 + " = " + input);
return (true);
}
}
return (false);
}
let arr = [22, 121, 42];
let len = arr.length;
let result = 0;
for (let i = 0; i < len; ++i)
{
if (IsNumberSpecial(arr[i]))
++result;
}
console.log(result + " number" + ((result > 1) ? "s" : "") + " found");
Here's a rather naïve solution in pseudocode for determining if a number is 'special':
Given an number N (assumed to be an integer)
Let I = Floor(N / 2)
Let J = Ceil(N / 2)
While (I > 0)
If I is the reverse of J Then
Return True
End
I <- I - 1
J <- J + 1
End
Return False
A quick JS implementation:
function isSpecial(n) {
for (var i = Math.floor(n / 2), j = Math.ceil(n / 2); i > 0; i--, j++) {
console.info(`checking ${i} + ${j}`);
if (i.toString().split('').reverse().join('') === j.toString())
return true;
}
return false;
}
console.log(isSpecial(121));
I'll leave it up to the you implement the function to count the special numbers in the array. This could be made more efficient by improving the rather crude method for checking for string reversals or possibly by more intelligently skipping numbers.
Some pseudo code?
num_special = 0
for item in array:
for num in range(1, total):
if num + int(string(num).reversed()) == item
num_special += 1
break
print(num_special)
EDIT:
Here's a working Python example:
array = [22, 121]
num_special = 0
for item in array:
for num in range(1, item):
if (num + int(str(num)[::-1]) == item):
num_special += 1
break
print(num_special)
https://repl.it/repls/UsedLovelyCategory
Assuming we want two summands - this does not seem to be specified in the question but every answer has assumed it!
(Without this assumption, every number can be written as a reversible sum of 1s.)
Single digit summands:
n is even
Two digit summands:
10x + y + 10y + x
11x + 11y
11(x + y)
n is divisible by 11
Three digit summands:
100x + 10y + z + 100z + 10y + x
101x + 20y + 101z
101(x + z) + 20y
more complex but we can still
do better than a brute force
loop of 1 to n / 2
Etc... (we could probably write a function that generalises and searches over this algebra)
UPDATE
JavaScript code (interestingly, a result for 1111111110 seems to be found faster by the brute force 1 to n/2 loop! Maybe some other optimisations can be made):
function f(n){
let start = new Date;
let numDigits = 0;
let t = Math.ceil(n / 2);
while (t){
numDigits++;
t = ~~(t/10);
}
// Summands split between x and x+1 digits
if (n / 2 + 0.5 == Math.pow(10, numDigits - 1))
return false;
let cs = [];
let l = Math.pow(10, numDigits - 1);
let r = 1;
while (l >= r){
cs.push(l + r);
l /= 10;
r *= 10;
}
let sxs = new Array(cs.length);
const m = cs.length & 1 || 2;
sxs[cs.length-1] = m*cs[cs.length-1];
for (let i=cs.length-2; i>=0; i--)
sxs[i] = 2*cs[i] + sxs[i + 1];
let stack = [[0, n, []]];
let results = [];
while (stack.length){
let [i, curr, vs] = stack.pop();
if (i == cs.length - 1){
let d = curr / cs[i];
if (d == ~~d &&
((cs.length & 1 && d < 10) || ((!(cs.length & 1) || cs.length == 1) && d < 19)))
results.push(vs.concat('x'));
continue;
}
t = 2;
curr -= t*cs[i];
stack.push([
i + 1, curr,
vs.slice().concat(t)]);
while (curr >= sxs[i + 1]){
curr -= cs[i];
stack.push([
i + 1, curr,
vs.slice().concat(++t)]);
}
}
let time = new Date - start;
return [!!results.length, (time) + 'ms', cs, results];
}
let ns = [
22, 121, 42,
66666,
777777,
8888888,
99999999,
68685,
68686]
for (let n of ns)
console.log(n, JSON.stringify(f(n)));
My JS variant:
const reverseInt = (n) =>
parseInt(n.toString().split('').reverse().join(''))
const checkSpecialInt = (n) =>{
for(let i=1;i<=n;i++){
if (i+reverseInt(i)==n) return true;
}
return false;
}
const checkSpecialIntArray = (arr) =>
arr.filter((e,i)=>checkSpecialInt(e)).length;
let test = [122, 121, 22, 21];
console.log(checkSpecialIntArray(test));
The requirement does not includes returning every possible combination of matched "special numbers", only that a match is found.
const isSpecialInteger = arr => {
// `arr`: `Array`
// result, initialized to `0`
let res = 0;
// iterate input `arr`
for (let n of arr) {
// divide `n` by `2`
const c = n / 2;
// check if `n` is an integer or decimal
// if decimal subtract decimal portion of 1st divided decimal
// add decimal portion to 2nd portion of divided decimal
// else set `x`, `y` to even division of input `n`
let [x, y] = !Number.isInteger(c) ? [c - (c % 10), c + (c % 10)] : [c, c];
// set label for `for` loop
// decrement result of `Math.max(x, y)`
N: for (let i = Math.max(x, y); i; i--) {
// check if `i` converted to
// string, then array reveresed
// if equal to `n`
// if `true` increment `res` by `1` `break` `N` loop
if (i + +[...`${i}`].reverse().join`` === n) {
res+= 1;
break N;
}
}
}
// return positive integer or `0`
return res;
}
console.log(
isSpecialInteger([22, 121])
);
Here is one of the brute-force approaches in JAVA and this can be optimised further,
import java.util.Scanner;
public class Solution
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
String str = in.nextLine();
String[] inp = str.split(",");
System.out.println(isSpecial(inp,inp.length));
}
public static int isSpecial(String[] inp, int inpSize)
{
int arr[] = new int[inp.length];
for(int i=0;i<inpSize;i++)
{
arr[i] = Integer.parseInt(inp[i]);
}
int spclCount = 0;
for(int i=0;i<arr.length;i++)
{
for(int j=1;j<((arr[i]/2)+1);j++)
{
if(j+getReverse(j) == arr[i])
{
spclCount++;
break;
}
}
}
return spclCount;
}
public static int getReverse(int n)
{
int rem,rev=0;
while(n != 0)
{
rem = n % 10;
rev = (rev*10) + rem;
n /= 10;
}
return rev;
}
}
Question:
An integer is special if it can be expressed as a sum that's a palindrome (the same backwards as forwards). For example, 22 and 121 are both special, because 22 equals 11+11 and 121 equals 29+92.
Given an array of integers, count how many of its elements are special
My Approach:
public class PalindromicSum {
public static void getSplNumber(int[] arrip) {
//to iterate i/p array
for (int i = 0; i < arrip.length; i++) {
int tempSum = 0;
//to iterate from 1 to number/2
for (int j = 1; j <= arrip[i] / 2; j++) {
//to get the reverse of the number
int revNum = getRevNum(j);
tempSum = revNum + j;
if (tempSum == arrip[i]) {
System.out.println(arrip[i]);
break;
}
}
}
}
public static int getRevNum(int num) {
int rev = 0;
//to get reverse of a number
while(num!=0) {
int reminder = num%10;
rev = rev*10 + reminder;
num = num/10;
}
return rev;
}
public static void main(String[] args) {
int[] arr = { 121, 11, 10, 3, 120, 110};
getSplNumber(arr);
}
}

Javascript toFixed function

The expected result of:
(1.175).toFixed(2) = 1.18 and
(5.175).toFixed(2) = 5.18
But in JS showing:
(1.175).toFixed(2) = 1.18 but
*(5.175).toFixed(2) = 5.17*
How to rectify the problem?
It's not a bug. It's related to the fact numbers aren't stored in decimal but in IEEE754 (so 5.175 isn't exactly stored).
If you want to round in a specific direction (up) and you consistently have numbers of this precision, you might use this trick :
(5.175 + 0.00001).toFixed(2)
You could always try using round, instead of toFixed.
Math.round(5.175*100)/100
You could even try putting it in some prototype method if you want.
Created a jsBin that implements a simple prototype on Number.
Number.prototype.toFixed = function(decimals) {
return Math.round(this * Math.pow(10, decimals)) / (Math.pow(10, decimals));
};
It's because the numbers are stored as IEEE754.
I would recommend you to use the Math class (round, floor or ceil methods, depending on your needing).
I've just created a class MathHelper that can easily solve your problem:
var MathHelper = (function () {
this.round = function (number, numberOfDecimals) {
var aux = Math.pow(10, numberOfDecimals);
return Math.round(number * aux) / aux;
};
this.floor = function (number, numberOfDecimals) {
var aux = Math.pow(10, numberOfDecimals);
return Math.floor(number * aux) / aux;
};
this.ceil = function (number, numberOfDecimals) {
var aux = Math.pow(10, numberOfDecimals);
return Math.ceil(number * aux) / aux;
};
return {
round: round,
floor: floor,
ceil: ceil
}
})();
Usage:
MathHelper.round(5.175, 2)
Demo: http://jsfiddle.net/v2Dj7/
Actually I think this is a bug in the implementation of Number.prototype.toFixed. The algorithm given in ECMA-262 20.1.3.3 10-a says to round up as a tie-breaker. As others have mentioned, there probably isn't a tie to break due to floating point imprecisions in the implementation. But that doesn't make it right :)
At least this behavior is consistent across FF, Chrome, Opera, Safari. (Haven't tried others.)
FWIW, you can actually implement your own version of toFixed per the spec in JS and that behaves as you would expect. See http://jsfiddle.net/joenudell/7qahrb6d/.
Kippie
your solution has problems
one of them
39133.005.toFixed(2) => 39133
var Calc = function () {
var self = this;
this.float2Array = function(num) {
var floatString = num.toString(),
exp = floatString.indexOf(".") - (floatString.length - 1),
mant = floatString.replace(".", "").split("").map(function (i) { return parseInt(i); });
return { exp: exp, mant: mant };
};
this.round2 = function (num, dec, sep) {
var decimal = !!dec ? dec : 2,
separator = !!sep ? sep : '',
r = parseFloat(num),
exp10 = Math.pow(10, decimal);
r = Math.round(r * exp10) / exp10;
var rr = Number(r).toFixed(decimal).toString().split('.');
var b = rr[0].replace(/(\d{1,3}(?=(\d{3})+(?:\.\d|\b)))/g, "\$1" + separator);
r = (rr[1] ? b + '.' + rr[1] : b);
return r;
};
this.toFixed10 = function (f, num) {
var prepareInt = self.float2Array(f),
naturalInt = prepareInt.mant,
places = Math.abs(prepareInt.exp),
result = prepareInt.mant.slice(),
resultFixedLenth;
// if number non fractional or has zero fractional part
if (f.isInt()) {
return f.toFixed(num);
}
// if the number of decimal places equals to required rounding
if (places === num) {
return Number(f).toString();
}
//if number has trailing zero (when casting to string type float 1.0050 => "1.005" => 005 <0050)
if (places < num) {
return Number(f).round2(num);
}
for (var e = naturalInt.length - (places > num ? (places - num) : 0), s = 0; e >= s; e--) {
if (naturalInt.hasOwnProperty(e)) {
if (naturalInt[e] > 4 && naturalInt[e - 1] < 9) {
result[e] = 0;
result[e - 1] = naturalInt[e - 1] + 1;
break;
} else if (naturalInt[e] > 4 && naturalInt[e - 1] === 9) {
result[e] = 0;
result[e - 1] = 0;
result[e - 2] = naturalInt[e - 2] < 9 ? naturalInt[e - 2] + 1 : 0;
} else if (e === 0 && naturalInt[e] === 9 && naturalInt[e + 1] === 9) {
result[e] = 0;
result.unshift(1);
} else {
break;
}
}
}
if (places - num > 0) {
resultFixedLenth = result.slice(0, -(places - num));
} else {
for (var i = 0, l = num - places; i < l; i++) {
result.push(0);
}
resultFixedLenth = result;
}
return (parseInt(resultFixedLenth.join("")) / Math.pow(10, num)).round2(num);
};
this.polyfill = function() {
if (!Array.prototype.map) {
Array.prototype.map = function (callback, thisArg) {
var T, A, k;
if (this == null) { throw new TypeError(' this is null or not defined'); }
var O = Object(this), len = O.length >>> 0;
if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); }
if (arguments.length > 1) { T = thisArg; }
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
};
this.init = function () {
self.polyfill();
Number.prototype.toFixed10 = function (decimal) {
return calc.toFixed10(this, decimal);
}
Number.prototype.round2 = function (decimal) {
return calc.round2(this, decimal);
}
Number.prototype.isInt = function () {
return (Math.round(this) == this);
}
}
}, calc = new Calc(); calc.init();
this works good)
obj = {
round(val) {
const delta = 0.00001
let num = val
if (num - Math.floor(num) === 0.5) {
num += delta
}
return Math.round(num + delta)
},
fixed(val, count = 0) {
const power = Math.pow(10, count)
let res = this.round(val * power) / power
let arr = `${res}`.split('.')
let addZero = ''
for (let i = 0; i < count; i++) {
addZero += '0'
}
if (count > 0) {
arr[1] = ((arr[1] || '') + addZero).substr(0, count)
}
return arr.join('.')
}
}
obj.fixed(5.175, 2)
// "5.18"

How to sort strings in JavaScript numerically

I would like to sort an array of strings (in JavaScript) such that groups of digits within the strings are compared as integers not strings. I am not worried about signed or floating point numbers.
For example, the result should be ["a1b3","a9b2","a10b2","a10b11"] not ["a1b3","a10b11","a10b2","a9b2"]
The easiest way to do this seems to be splitting each string on boundaries around groups of digits. Is there a pattern I can pass to String.split to split on character boundaries without removing any characters?
"abc11def22ghi".split(/?/) = ["abc","11","def","22","ghi"];
Or is there another way to compare strings that does not involve splitting them up, perhaps by padding all groups of digits with leading zeros so they are the same length?
"aa1bb" => "aa00000001bb", "aa10bb" => "aa00000010bb"
I am working with arbitrary strings, not strings that have a specific arrangement of digit groups.
I like the /(\d+)/ one liner from Gaby to split the array. How backwards compatible is that?
The solutions that parse the strings once in a way that can be used to rebuild the originals are much more efficient that this compare function. None of the answers handle some strings starting with digits and others not, but that would be easy enough to remedy and was not explicit in the original question.
["a100", "a20", "a3", "a3b", "a3b100", "a3b20", "a3b3", "!!", "~~", "9", "10", "9.5"].sort(function (inA, inB) {
var result = 0;
var a, b, pattern = /(\d+)/;
var as = inA.split(pattern);
var bs = inB.split(pattern);
var index, count = as.length;
if (('' === as[0]) === ('' === bs[0])) {
if (count > bs.length)
count = bs.length;
for (index = 0; index < count && 0 === result; ++index) {
a = as[index]; b = bs[index];
if (index & 1) {
result = a - b;
} else {
result = !(a < b) ? (a > b) ? 1 : 0 : -1;
}
}
if (0 === result)
result = as.length - bs.length;
} else {
result = !(inA < inB) ? (inA > inB) ? 1 : 0 : -1;
}
return result;
}).toString();
Result: "!!,9,9.5,10,a3,a3b,a3b3,a3b20,a3b100,a20,a100,~~"
Another variant is to use an instance of Intl.Collator with the numeric option:
var array = ["a100", "a20", "a3", "a3b", "a3b100", "a3b20", "a3b3", "!!", "~~", "9", "10", "9.5"];
var collator = new Intl.Collator([], {numeric: true});
array.sort((a, b) => collator.compare(a, b));
console.log(array);
I think this does what you want
function sortArray(arr) {
var tempArr = [], n;
for (var i in arr) {
tempArr[i] = arr[i].match(/([^0-9]+)|([0-9]+)/g);
for (var j in tempArr[i]) {
if( ! isNaN(n = parseInt(tempArr[i][j])) ){
tempArr[i][j] = n;
}
}
}
tempArr.sort(function (x, y) {
for (var i in x) {
if (y.length < i || x[i] < y[i]) {
return -1; // x is longer
}
if (x[i] > y[i]) {
return 1;
}
}
return 0;
});
for (var i in tempArr) {
arr[i] = tempArr[i].join('');
}
return arr;
}
alert(
sortArray(["a1b3", "a10b11", "a10b2", "a9b2"]).join(",")
);
Assuming you want to just do a numeric sort by the digits in each array entry (ignoring the non-digits), you can use this:
function sortByDigits(array) {
var re = /\D/g;
array.sort(function(a, b) {
return(parseInt(a.replace(re, ""), 10) - parseInt(b.replace(re, ""), 10));
});
return(array);
}
It uses a custom sort function that removes the digits and converts to a number each time it's asked to do a comparison. You can see it work here: http://jsfiddle.net/jfriend00/t87m2/.
Use this compare function for sorting...
function compareLists(a, b) {
var alist = a.split(/(\d+)/), // Split text on change from anything
// to digit and digit to anything
blist = b.split(/(\d+)/); // Split text on change from anything
// to digit and digit to anything
alist.slice(-1) == '' ? alist.pop() : null; // Remove the last element if empty
blist.slice(-1) == '' ? blist.pop() : null; // Remove the last element if empty
for (var i = 0, len = alist.length; i < len; i++) {
if (alist[i] != blist[i]){ // Find the first non-equal part
if (alist[i].match(/\d/)) // If numeric
{
return +alist[i] - +blist[i]; // Compare as number
} else {
return alist[i].localeCompare(blist[i]); // Compare as string
}
}
}
return true;
}
Syntax
var data = ["a1b3", "a10b11", "b10b2", "a9b2", "a1b20", "a1c4"];
data.sort(compareLists);
alert(data);
There is a demo at http://jsfiddle.net/h9Rqr/7/.
Here's a more complete solution that sorts according to both letters and numbers in the strings
function sort(list) {
var i, l, mi, ml, x;
// copy the original array
list = list.slice(0);
// split the strings, converting numeric (integer) parts to integers
// and leaving letters as strings
for( i = 0, l = list.length; i < l; i++ ) {
list[i] = list[i].match(/(\d+|[a-z]+)/g);
for( mi = 0, ml = list[i].length; mi < ml ; mi++ ) {
x = parseInt(list[i][mi], 10);
list[i][mi] = !!x || x === 0 ? x : list[i][mi];
}
}
// sort deeply, without comparing integers as strings
list = list.sort(function(a, b) {
var i = 0, l = a.length, res = 0;
while( res === 0 && i < l) {
if( a[i] !== b[i] ) {
res = a[i] < b[i] ? -1 : 1;
break;
}
// If you want to ignore the letters, and only sort by numbers
// use this instead:
//
// if( typeof a[i] === "number" && a[i] !== b[i] ) {
// res = a[i] < b[i] ? -1 : 1;
// break;
// }
i++;
}
return res;
});
// glue it together again
for( i = 0, l = list.length; i < l; i++ ) {
list[i] = list[i].join("");
}
return list;
}
I needed a way to take a mixed string and create a string that could be sorted elsewhere, so that numbers sorted numerically and letters alphabetically. Based on answers above I created the following, which pads out all numbers in a way I can understand, wherever they appear in the string.
function padAllNumbers(strIn) {
// Used to create mixed strings that sort numerically as well as non-numerically
var patternDigits = /(\d+)/g; // This recognises digit/non-digit boundaries
var astrIn = strIn.split( patternDigits ); // we create an array of alternating digit/non-digit groups
var result = "";
for (var i=0;i<astrIn.length; i++) {
if (astrIn[i] != "") { // first and last elements can be "" and we don't want these padded out
if (isNaN(astrIn[i])) {
result += astrIn[i];
} else {
result += padOneNumberString("000000000",astrIn[i]);
}
}
}
return result;
}
function padOneNumberString(pad,strNum,left) {
// Pad out a string at left (or right)
if (typeof strNum === "undefined") return pad;
if (typeof left === "undefined") left = true;
var padLen = pad.length - (""+ strNum).length;
var padding = pad.substr(0,padLen);
return left? padding + strNum : strNum + padding;
}
Sorting occurs from left to right unless you create a custom algorithm. Letters or digits are compared digits first and then letters.
However, what you want to accomplish as per your own example (a1, a9, a10) won’t ever happen. That would require you knowing the data beforehand and splitting the string in every possible way before applying the sorting.
One final alternative would be:
a) break each and every string from left to right whenever there is a change from letter to digit and vice versa; &
b) then start the sorting on those groups from right-to-left. That will be a very demanding algorithm. Can be done!
Finally, if you are the generator of the original "text", you should consider NORMALIZING the output where a1 a9 a10 could be outputted as a01 a09 a10. This way you could have full control of the final version of the algorithm.

Categories