Related
This is from LeetCode - Valid Palindrome.
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.
Note: For the purpose of this problem, we define empty string as valid palindrome.
while (regex.test(s[start])) {
start++;}
--> can't understand how it works, I understood only that s[start] is alphanumeric characters, it will be false
if (!s[start] || !s[end])
--> What does this mean?
Below is the whole code
var isPalindrome = function(s) {
let regex = /[\W]/;
let start = 0;
let end = s.length - 1;
while (start < end) {
// Moves front runner to next alphanumeric
while (regex.test(s[start])) {
start++;
}
// Moves back runner to next alphanumeric
while (regex.test(s[end])) {
end--;
}
// Above would run until null if there are no alphanumeric characters so return true
if (!s[start] || !s[end]) {
return true;
}
// Check if equal and return false if not
if (s[start].toLowerCase() != s[end].toLowerCase()) {
return false;
}
// If index values match continue the while loop
start++;
end--;
}
return true;
};
Please Advise!
Same as previous solution, plus:
+ removing spaces and symbols, except (A-Z,a-z,0-9)
+ lowercasing
const isPalindrome = function(str) {
const expr = /[\W_]/g;
const lowcaseStr = str.toLowerCase().replace(expr, '');
const reverseStr = lowcaseStr.split('').reverse().join('');
return lowcaseStr === reverseStr
};
This is heavily over-engineered. Why not just use one for loop with 2 counters, one starting at 0 and one at the last index and, while they aren't equal, check if the characters at those indices are the same. Or use built in functions like
function palindrome(str){ return str.split('').reverse().join('') == str}
I am trying to learn how this problem has been solved but I am lost at string.slice(0,1) and i++ after that.
What is the need to use the slice method?
The questions is:
Write a function called countChars that accepts two parameters: a string and a character. This function should return a number representing the number of times that the character appears in string.
function countChars(string, character) {
let count = 0;
let i = 0;
while (i < string.length) {
if (string[i] === character) {
count++;
}
string.slice(0, 1);
i++;
}
return count;
}
Nina already solved it using your code, a shorter option would be using String.match
function countChars(string, character) {
const { length } = string.match(new RegExp(character, 'ig')) || [];
return length;
}
console.log(countChars('Foobar', 'o'));
Explanation of your code:
function countChars(string, character) {
let count = 0; // set variable count to zero
let i = 0; // set variable i to zero
while (i < string.length) { // loop while i is less than lenght of string
if (string[i] === character) { // check character at position i
count++; // Increment count by one
}
string.slice(0, 1); // returns the first character in string, but return value is not used. Can be removed
i++; // increment variable i by one
}
return count; // returns count
}
console.log("Count", countChars("ABCDABC", "A"));
string.slice(0, 1) The slice method extracts a section of a string and returns it as a new string, without modifying the original string. The return value is not used in this function, so you can remove the call. I guess that the person who wrote it tried to remove the first character of the string for each iteration of the loop. That is not a good way to solve the problem, for example because it creates one string per iteration and wastes memory.
i++ The increment operator ++ increments (adds one to) its operand and returns a value. There are two ways to use this operator: as pre increment ++i (returns the value before incrementing) or post increment i++ (returns the value after incrementing). Both variants are useful. When used on a single line (like in your example) it doesn't matter. You can also write it as i = i + 1 or using the addition assignment i += 1.
let value = 0;
console.log( 'Pre increment: before %d, result %d, after %d', value, ++value, value); // before 0, result 1, after 1
value = 0;
console.log( 'Post increment: before %d, result %d, after %d', value, value++, value); //before 0, result 0, after 1
Using the while-statement is one way to create the loop that iterates over the string, using the for-statement is another, more compact way.
Example with while:
let i = 0; // initialization
while ( i < string.length ) { // check contition
// ... some code ...
i += 1;
}
Example with for:
for ( let i = 0; i < string.length; i += 1 ) {
// ... some code ...
}
The pros with for are: the initialization, condition and final expression is defined in the header. The scope of the i variable can with let be restricted to be defined only inside the for-loop.
Your example, but with a for-loop:
function countChars(string, character) {
let count = 0; // set variable count to zero
for (let i = 0; i < string.length; i += 1) {
if (string[i] === character) { // check character at position i
count++; // Increment count by one
}
}
return count; // returns count
}
console.log("Count", countChars("ABCDABC", "A"));
The code is buggy!
There is a bug with this solution, and it has to do with how characters are defined. In Javascript each position of the string is a 16-bit integer. It works for most text, but fails for emojis and other characters that aren't in the Unicode Basic Multilingual Plane since they are defined with two positions in the string (surrogate pairs).
A solution that works is to use the split-method, that splits a string object into an array of strings, using a specified separator string to determine where to make each split. Note that if the separator string isn't found, or if the input string is empty, an array of one string will be returned. If there is one match, the returned array will have two strings. So the split-method returns a value that is one more than what we want, but that is easy to fix by just subtracting one.
function countChars(string, character) {
return string.split(character).length - 1;
}
console.log("Count", countChars("ABCD😃ABC😃", "😃"));
Optimizations
While “Premature optimization is the root of all evil”, there are some things that can be written in a clean and optimized way while you write the code. For example, if you are writing to a file, it is very inefficient to open the file, write something, and then close the file inside the loop:
for( i = 0; i < array.length; i + i = 1) {
fs.openFile("log.txt").write(array[i]).close();
}
It should be no surprise that it is more efficient to first open the file, write inside the loop, and then close the file:
fh = fs.openFile("log.txt");
for( i = 0; i < array.length; i + i = 1) {
fh.write(array[i]);
}
fh.close();
The reason I mention this, is because when I write a for-statement, I usually initialize both the variable and the length in the initialization part of the for-statement, since the length of the string doesn't change (and using the property string.length is always more expensive than using a local variable).
for (let i = 0, length = string.length; i < length; i += 1) {
// ... code ...
}
Another thing: string[i] and string.charAt(i) creates a new string in every iteration of the loop. To compare a single character inside a loop, it is much faster to compare integers instead of strings, by using string.charCodeAt(i). And instead of string.charCodeAt(i) you can use string.codePointAt(i) to make it safe to use with all Unicode characters like emoji, not only the characters in the BMP.
As I said above, the method that you used isn't safe to use with Unicode. For example, if you search for an emoji (😃), you will not get the correct result.
The following two methods are safe to use with all Unicode codepoints. Both of them can handle a needle with multiple characters.
function count_split(haystack, needle) {
return haystack.split(needle).length - 1;
}
This function uses a regular expression as the needle, and that can give unexpected results, since some characters have special meaning. For example, if you search for a dot (.) it will match every character. On the other hand, you can do an advanced search for '[ab]' that will match all 'a' and 'b' characters.
function count_match(haystack, needle) {
const match = haystack.match(new RegExp(needle, 'g')) || [];
return match.length;
}
The following method is safe to use with any Unicode, and the needle must be a single codepoint.
function countChars(haystack, needle) {
let count = 0;
const codePoint = needle.codePointAt(0);
if (
!((needle.length === 1) || (needle.length === 2 && codePoint > 0xffff))
) {
// NOTE: Using charPointAt(0) returns the codePoint of the first character.
// Since the characters in the string is stored as 16-bit integers,
// characters outside of the Unicode BMP is stored as surrogate pairs
// in two positions. Thats why the length of the string is1 for
// a BMP codepoint and 2 for a codepoint outside of the BMP.
// codePointAt handles the decoding of the surrogate pair.
throw Error('needle should be one codepoint');
}
for (let i = 0, length = haystack.length; i < length; i += 1) {
if (haystack.codePointAt(i) === codePoint) {
count++;
}
}
return count;
}
I have created test-cases on jsperf.com to compare the speed of the Unicode safe-functions mentioned above, and also the UNSAFE-functions that where published in this thread while I was writing this answer.
Without slice
function countChars(string, character) {
let count = 0;
let i = 0;
while (i < string.length) {
if (string[i] === character) {
count++;
}
i++;
}
return count;
}
console.log(countChars('foobar', 'o'))
With slice, but without i.
function countChars(string, character) {
let count = 0;
while (string.length) { // check if the length is not zero
if (string[0] === character) {
count++;
}
string = string.slice(1); // take the rest of the string, beginning from index 1
}
return count;
}
console.log(countChars('foobar', 'o'))
If you're willing to look for other solutions, here is a short one.
function count(str, char){
let count = str.split('').filter(c => {
return c.toLowerCase() === char.toLowerCase()
});
return count.length;
}
console.log(count('hello world', 'l'));
Note: The above solution is case-insensitive.
I would do it like this
function countChars(string, character) {
return string.split('').filter(e => e === character).length;
}
console.log(countChars('foobar', 'o'))
But #bambam RegEx approach is likely the most efficient.
A shorter version :)
function count(string,char) {
return string.split('').filter(x => x === char).length;
}
Thanks
How to get the number before 'x'?
I tried using .split('x')[0] but it grabs everything before 'x'.
123x // Gives 123
123y+123x - // Gives 123
123x+123x - // Gives 246
I've tested a function which uses regex that I think will work. I've included the results, an explanation of how the function works, and then a commented version of the function.
Note, this doesn't handle any algebra more complex than adding and subtracting simple terms. I would refer to https://newton.now.sh/ for that, it's an API which can handle simplification (I am not affiliated).
Results:
console.log(coefficient("-x+23x")); // 22
console.log(coefficient("123y+123x")); // 123
// replaces spaces
console.log(coefficient("x + 123x")); // 124
console.log(coefficient("-x - 123x")); // -124
console.log(coefficient("1234x-23x")); // 1211
// doesn't account for other letters
console.log(coefficient("-23yx")); // 1
Explanation:
First the function removes spaces. Then it uses a regex, which finds any sequence of numbers that are followed by an 'x'. If there's a +/- in front, the regex keeps that. The function loops through these sequences of numbers, and adds them to a total. If there's an 'x' that does not have numbers with it, its coefficient is assumed as -1 or 1.
Commented Code:
function coefficient(str) {
// remove spaces
str = str.replace(/\s/g, '');
// all powerful regex
var regexp = /(\+|-)?[0-9]*x/g
// total
sum = 0;
// find the occurrences of x
var found = true;
while (found) {
match = regexp.exec(str);
if (match == null) {
found = false;
} else {
// treated as +/- 1 if no proceeding number
if (isNaN(parseInt(match[0]))) {
if (match[0].charAt(0) == "-") {
sum--;
} else {
sum++;
}
// parse the proceeding number
} else {
sum += parseInt(match[0]);
}
}
}
return sum;
}
I don't know if there is sufficient cleverness in ECMAScript regular expressions to do look behind, but you can do it with match and post processing to remove the "x".
If the intention is to sum the terms, then a further operation with reduce is required. Trimming the x could be combined with reduce so that map isn't required.
console.log(
'123x+123x'.match(/\d+x/ig).map(function(v){
return v.slice(0,-1)
}).reduce(function(sum, v){return sum + +v},0)
);
console.log(match('123x+123y+456x', 'x'))
console.log(match('123x+123y+456x', 'y'))
function match(str, x) {
return str.match(new RegExp('\\d+' + x, 'g')).reduce((cur, p) => {
return cur + parseInt(p.substr(0, p.length - x.length))
}, 0)
}
I've been on this problem for several hours now and have done all I can to the best of my current newbie javaScript ability to solve this challenge but I just can't figure out exactly what's wrong. I keep getting "UNEXPECTED TOKEN ILLEGAL on here: http://jsfiddle.net/6n8apjze/14/
and "TypeError: Cannot read property 'length' of null": http://goo.gl/LIz89F
I think the problem is the howManyRepeat variable. I don't understand why I'm getting it can't read the length of null when clearly word is a word from str...
I got the idea for:
word.toLowerCase().split("").sort().join("").match(/([.])\1+/g).length
...here: Get duplicate characters count in a string
The Challenge:
Using the JavaScript language, have the function LetterCountI(str) take the str
parameter being passed and return the first word with the greatest number of
repeated letters. For example: "Today, is the greatest day ever!" should return
greatest because it has 2 e's (and 2 t's) and it comes before ever which also
has 2 e's. If there are no words with repeating letters return -1. Words will
be separated by spaces.
function LetterCountI(str){
var wordsAndAmount={};
var mostRepeatLetters="-1";
var words=str.split(" ");
words.forEach(function(word){
// returns value of how many repeated letters in word.
var howManyRepeat=word.toLowerCase().split("").sort().join("").match(/([.])\1+/g).length;
// if there are repeats(at least one value).
if(howManyRepeat !== null || howManyRepeat !== 0){
wordsAndAmount[word] = howManyRepeat;
}else{
// if no words have repeats will return -1 after for in loop.
wordsAndAmount[word] = -1;
}
});
// word is the key, wordsAndAmount[word] is the value of word.
for(var word in wordsAndAmount){
// if two words have same # of repeats pick the one before it.
if(wordsAndAmount[word]===mostRepeatLetters){
mostRepeatLetters=mostRepeatLetters;
}else if(wordsAndAmount[word]<mostRepeatLetters){
mostRepeatLetters=mostRepeatLetters;
}else if(wordsAndAmount[word]>mostRepeatLetters){
mostRepeatLetters=word;
}
}
return mostRepeatLetters;
}
// TESTS
console.log("-----");
console.log(LetterCountI("Today, is the greatest day ever!"));
console.log(LetterCountI("Hello apple pie"));
console.log(LetterCountI("No words"));
Any guidance is much appreciated. Thank you!! ^____^
Here is the working code snippet:
/*
Using the JavaScript language, have the function LetterCountI(str) take the str
parameter being passed and return the first word with the greatest number of
repeated letters. For example: "Today, is the greatest day ever!" should return
greatest because it has 2 e's (and 2 t's) and it comes before ever which also
has 2 e's. If there are no words with repeating letters return -1. Words will
be separated by spaces.
console.log(LetterCountI("Today, is the greatest day ever!") === "greatest");
console.log(LetterCountI("Hello apple pie") === "Hello");
console.log(LetterCountI("No words") === -1);
Tips:
This is an interesting problem. What we can do is turn the string to lower case using String.toLowerCase, and then split on "", so we get an array of characters.
We will then sort it with Array.sort. After it has been sorted, we will join it using Array.join. We can then make use of the regex /(.)\1+/g which essentially means match a letter and subsequent letters if it's the same.
When we use String.match with the stated regex, we will get an Array, whose length is the answer. Also used some try...catch to return 0 in case match returns null and results in TypeError.
/(.)\1+/g with the match method will return a value of letters that appear one after the other. Without sort(), this wouldn't work.
*/
function LetterCountI(str){
var wordsAndAmount={};
var mostRepeatLetters="";
var words=str.split(" ");
words.forEach(function(word){
var howManyRepeat=word.toLowerCase().split("").sort().join("").match(/(.)\1+/g);
if(howManyRepeat !== null && howManyRepeat !== 0){ // if there are repeats(at least one value)..
wordsAndAmount[word] = howManyRepeat;
} else{
wordsAndAmount[word] = -1; // if no words have repeats will return -1 after for in loop.
}
});
// console.log(wordsAndAmount);
for(var word in wordsAndAmount){ // word is the key, wordsAndAmount[word] is the value of word.
// console.log("Key = " + word);
// console.log("val = " + wordsAndAmount[word]);
if(wordsAndAmount[word].length>mostRepeatLetters.length){ //if two words have same # of repeats pick the one before it.
mostRepeatLetters=word;
}
}
return mostRepeatLetters ? mostRepeatLetters : -1;
}
// TESTS
console.log("-----");
console.log(LetterCountI("Today, is the greatest day ever!"));
console.log(LetterCountI("Hello apple pie"));
console.log(LetterCountI("No words"));
/*
split into words
var wordsAndAmount={};
var mostRepeatLetters=0;
loop through words
Check if words has repeated letters, if so
Push amount into object
Like wordsAndAmount[word[i]]= a number
If no repeated letters...no else.
Loop through objects
Compare new words amount of repeated letters with mostRepeatLetters replacing whoever has more.
In the end return the result of the word having most repeated letters
If all words have no repeated letters return -1, ie.
*/
The changes made:
[.] turned into . as [.] matches a literal period symbol, not any character but a newline
added closing */ at the end of the code (the last comment block was not closed resulting in UNEXPECTED TOKEN ILLEGAL)
if(howManyRepeat !== null || howManyRepeat !== 0) should be replaced with if(howManyRepeat !== null && howManyRepeat !== 0) since otherwise the null was testing for equality with 0 and led to the TypeError: Cannot read property 'length' of null" issue. Note that .match(/(.)\1+/g).length cannot be used since the result of matching can be null, and this will also cause the TypeError to appear.
The algorithm for getting the first entry with the greatest number of repetitions was wrong since the first if block allowed subsequent entry to be output as a correct result (not the first, but the last entry with the same repetitions was output actually)
-1 can be returned if mostRepeatLetters is empty.
Hope you dont mind if I rewrite this code. My code may not be that efficient.
Here is a snippet
function findGreatest() {
// ipField is input field
var getString = document.getElementById('ipField').value.toLowerCase();
var finalArray = [];
var strArray = [];
var tempArray = [];
strArray = (getString.split(" "));
// Take only those words which has repeated letter
for (var i = 0, j = strArray.length; i < j; i++) {
if ((/([a-zA-Z]).*?\1/).test(strArray[i])) {
tempArray.push(strArray[i]);
}
}
if (tempArray.length == 0) { // If no word with repeated Character
console.log('No such Word');
return -1;
} else { // If array has words with repeated character
for (var x = 0, y = tempArray.length; x < y; x++) {
var m = findRepWord(tempArray[x]); // Find number of repeated character in it
finalArray.push({
name: tempArray[x],
repeat: m
})
}
// Sort this array to get word with largest repeated chars
finalArray.sort(function(z, a) {
return a.repeat - z.repeat
})
document.getElementById('repWord').textContent=finalArray[0].name;
}
}
// Function to find the word which as highest repeated character(s)
function findRepWord(str) {
try {
return str.match(/(.)\1+/g).length;
} catch (e) {
return 0;
} // if TypeError
}
Here is DEMO
function LetterCountI(str) {
var word_arr = str.split(" ");
var x = word_arr.slice();
for(var i = 0; i < x.length; i ++){
var sum = 0;
for(var y = 0; y < x[i].length; y++){
var amount = x[i].split("").filter(function(a){return a == x[i][y]}).length;
if (amount > 1){
sum += amount
}
}
x[i] = sum;
}
var max = Math.max.apply(Math,x);
if(max == 0)
return -1;
var index = x.indexOf(max);
return(word_arr[index]);
};
Here is another version as well.
You could use new Set in the following manner:
const letterCount = s => {
const res = s.split(' ')
.map(s => [s, (s.length - new Set([...s]).size)])
.reduce((p, c) => (!p.length) ? c
: (c[1] > p[1]) ? c : p, []);
return !res[1] ? -1 : res.slice(0,1).toString()
}
Note: I have not tested this solution (other than the phrases presented here), but the idea is to subtract unique characters from the total characters in each word of the phrase.
An input element contains numbers a where comma or dot is used as decimal separator and space may be used to group thousands like this:
'1,2'
'110 000,23'
'100 1.23'
How would one convert them to a float number in the browser using JavaScript?
jQuery and jQuery UI are used. Number(string) returns NaN and parseFloat() stops on first space or comma.
Do a replace first:
parseFloat(str.replace(',','.').replace(' ',''))
I realise I'm late to the party, but I wanted a solution for this that properly handled digit grouping as well as different decimal separators for currencies. As none of these fully covered my use case I wrote my own solution which may be useful to others:
function parsePotentiallyGroupedFloat(stringValue) {
stringValue = stringValue.trim();
var result = stringValue.replace(/[^0-9]/g, '');
if (/[,\.]\d{2}$/.test(stringValue)) {
result = result.replace(/(\d{2})$/, '.$1');
}
return parseFloat(result);
}
This should strip out any non-digits and then check whether there was a decimal point (or comma) followed by two digits and insert the decimal point if needed.
It's worth noting that I aimed this specifically for currency and as such it assumes either no decimal places or exactly two. It's pretty hard to be sure about whether the first potential decimal point encountered is a decimal point or a digit grouping character (e.g., 1.542 could be 1542) unless you know the specifics of the current locale, but it should be easy enough to tailor this to your specific use case by changing \d{2}$ to something that will appropriately match what you expect to be after the decimal point.
The perfect solution
accounting.js is a tiny JavaScript library for number, money and currency formatting.
Check this for ref
You could replace all spaces by an empty string, all comas by dots and then parse it.
var str = "110 000,23";
var num = parseFloat(str.replace(/\s/g, "").replace(",", "."));
console.log(num);
I used a regex in the first one to be able to match all spaces, not just the first one.
This is the best solution
http://numeraljs.com/
numeral().unformat('0.02'); = 0.02
What about:
parseFloat(str.replace(' ', '').replace('.', '').replace(',', '.'));
All the other solutions require you to know the format in advance. I needed to detect(!) the format in every case and this is what I end up with.
function detectFloat(source) {
let float = accounting.unformat(source);
let posComma = source.indexOf(',');
if (posComma > -1) {
let posDot = source.indexOf('.');
if (posDot > -1 && posComma > posDot) {
let germanFloat = accounting.unformat(source, ',');
if (Math.abs(germanFloat) > Math.abs(float)) {
float = germanFloat;
}
} else {
// source = source.replace(/,/g, '.');
float = accounting.unformat(source, ',');
}
}
return float;
}
This was tested with the following cases:
const cases = {
"0": 0,
"10.12": 10.12,
"222.20": 222.20,
"-222.20": -222.20,
"+222,20": 222.20,
"-222,20": -222.20,
"-2.222,20": -2222.20,
"-11.111,20": -11111.20,
};
Suggestions welcome.
Here's a self-sufficient JS function that solves this (and other) problems for most European/US locales (primarily between US/German/Swedish number chunking and formatting ... as in the OP). I think it's an improvement on (and inspired by) Slawa's solution, and has no dependencies.
function realParseFloat(s)
{
s = s.replace(/[^\d,.-]/g, ''); // strip everything except numbers, dots, commas and negative sign
if (navigator.language.substring(0, 2) !== "de" && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(s)) // if not in German locale and matches #,###.######
{
s = s.replace(/,/g, ''); // strip out commas
return parseFloat(s); // convert to number
}
else if (/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(s)) // either in German locale or not match #,###.###### and now matches #.###,########
{
s = s.replace(/\./g, ''); // strip out dots
s = s.replace(/,/g, '.'); // replace comma with dot
return parseFloat(s);
}
else // try #,###.###### anyway
{
s = s.replace(/,/g, ''); // strip out commas
return parseFloat(s); // convert to number
}
}
Here is my solution that doesn't have any dependencies:
return value
.replace(/[^\d\-.,]/g, "") // Basic sanitization. Allows '-' for negative numbers
.replace(/,/g, ".") // Change all commas to periods
.replace(/\.(?=.*\.)/g, ""); // Remove all periods except the last one
(I left out the conversion to a number - that's probably just a parseFloat call if you don't care about JavaScript's precision problems with floats.)
The code assumes that:
Only commas and periods are used as decimal separators. (I'm not sure if locales exist that use other ones.)
The decimal part of the string does not use any separators.
try this...
var withComma = "23,3";
var withFloat = "23.3";
var compareValue = function(str){
var fixed = parseFloat(str.replace(',','.'))
if(fixed > 0){
console.log(true)
}else{
console.log(false);
}
}
compareValue(withComma);
compareValue(withFloat);
This answer accepts some edge cases that others don't:
Only thousand separator: 1.000.000 => 1000000
Exponentials: 1.000e3 => 1000e3 (1 million)
Run the code snippet to see all the test suite.
const REGEX_UNWANTED_CHARACTERS = /[^\d\-.,]/g
const REGEX_DASHES_EXEPT_BEGINNING = /(?!^)-/g
const REGEX_PERIODS_EXEPT_LAST = /\.(?=.*\.)/g
export function formatNumber(number) {
// Handle exponentials
if ((number.match(/e/g) ?? []).length === 1) {
const numberParts = number.split('e')
return `${formatNumber(numberParts[0])}e${formatNumber(numberParts[1])}`
}
const sanitizedNumber = number
.replace(REGEX_UNWANTED_CHARACTERS, '')
.replace(REGEX_DASHES_EXEPT_BEGINING, '')
// Handle only thousands separator
if (
((sanitizedNumber.match(/,/g) ?? []).length >= 2 && !sanitizedNumber.includes('.')) ||
((sanitizedNumber.match(/\./g) ?? []).length >= 2 && !sanitizedNumber.includes(','))
) {
return sanitizedNumber.replace(/[.,]/g, '')
}
return sanitizedNumber.replace(/,/g, '.').replace(REGEX_PERIODS_EXEPT_LAST, '')
}
function formatNumberToNumber(number) {
return Number(formatNumber(number))
}
const REGEX_UNWANTED_CHARACTERS = /[^\d\-.,]/g
const REGEX_DASHES_EXEPT_BEGINING = /(?!^)-/g
const REGEX_PERIODS_EXEPT_LAST = /\.(?=.*\.)/g
function formatNumber(number) {
if ((number.match(/e/g) ?? []).length === 1) {
const numberParts = number.split('e')
return `${formatNumber(numberParts[0])}e${formatNumber(numberParts[1])}`
}
const sanitizedNumber = number
.replace(REGEX_UNWANTED_CHARACTERS, '')
.replace(REGEX_DASHES_EXEPT_BEGINING, '')
if (
((sanitizedNumber.match(/,/g) ?? []).length >= 2 && !sanitizedNumber.includes('.')) ||
((sanitizedNumber.match(/\./g) ?? []).length >= 2 && !sanitizedNumber.includes(','))
) {
return sanitizedNumber.replace(/[.,]/g, '')
}
return sanitizedNumber.replace(/,/g, '.').replace(REGEX_PERIODS_EXEPT_LAST, '')
}
const testCases = [
'1',
'1.',
'1,',
'1.5',
'1,5',
'1,000.5',
'1.000,5',
'1,000,000.5',
'1.000.000,5',
'1,000,000',
'1.000.000',
'-1',
'-1.',
'-1,',
'-1.5',
'-1,5',
'-1,000.5',
'-1.000,5',
'-1,000,000.5',
'-1.000.000,5',
'-1,000,000',
'-1.000.000',
'1e3',
'1e-3',
'1e',
'-1e',
'1.000e3',
'1,000e-3',
'1.000,5e3',
'1,000.5e-3',
'1.000,5e1.000,5',
'1,000.5e-1,000.5',
'',
'a',
'a1',
'a-1',
'1a',
'-1a',
'1a1',
'1a-1',
'1-',
'-',
'1-1'
]
document.getElementById('tbody').innerHTML = testCases.reduce((total, input) => {
return `${total}<tr><td>${input}</td><td>${formatNumber(input)}</td></tr>`
}, '')
<table>
<thead><tr><th>Input</th><th>Output</th></tr></thead>
<tbody id="tbody"></tbody>
</table>
From number to currency string is easy through Number.prototype.toLocaleString. However the reverse seems to be a common problem. The thousands separator and decimal point may not be obtained in the JS standard.
In this particular question the thousands separator is a white space " " but in many cases it can be a period "." and decimal point can be a comma ",". Such as in 1 000 000,00 or 1.000.000,00. Then this is how i convert it into a proper floating point number.
var price = "1 000.000,99",
value = +price.replace(/(\.|\s)|(\,)/g,(m,p1,p2) => p1 ? "" : ".");
console.log(value);
So the replacer callback takes "1.000.000,00" and converts it into "1000000.00". After that + in the front of the resulting string coerces it into a number.
This function is actually quite handy. For instance if you replace the p1 = "" part with p1 = "," in the callback function, an input of 1.000.000,00 would result 1,000,000.00