Insérer des virgules dans la chaîne de nombres
Hey là, j'essaie d'effectuer une recherche d'expression régulière en arrière sur une chaîne pour la diviser en groupes de 3 chiffres. Pour autant que je puisse voir à partir de la documentation AS3 , la recherche en arrière n'est pas possible dans le moteur reg ex.
Le but de cet exercice est d'insérer des virgules De triplet dans un nombre comme ceci:
10000000 => 10,000,000
Je pense à le faire comme ça:
string.replace(/(d{3})/g, ",$1")
Mais ce n'est pas correct en raison de la recherche ne se passe pas à l'arrière et le replace $ 1 ne fonctionnera que pour le premier match.
J'ai le sentiment que je ferais mieux d'effectuer cette tâche en utilisant une boucle.
Mise à jour:
En raison de AS3 ne supportant pas lookahead, c'est ainsi que je l'ai résolu.
public static function formatNumber(number:Number):String
{
var numString:String = number.toString()
var result:String = ''
while (numString.length > 3)
{
var chunk:String = numString.substr(-3)
numString = numString.substr(0, numString.length - 3)
result = ',' + chunk + result
}
if (numString.length > 0)
{
result = numString + result
}
return result
}
10 réponses
Si votre langue prend en charge les assertions postives lookahead, alors je pense que la regex suivante fonctionnera:
(\d)(?=(\d{3})+$)
Démontré en Java:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class CommifyTest {
@Test
public void testCommify() {
String num0 = "1";
String num1 = "123456";
String num2 = "1234567";
String num3 = "12345678";
String num4 = "123456789";
String regex = "(\\d)(?=(\\d{3})+$)";
assertEquals("1", num0.replaceAll(regex, "$1,"));
assertEquals("123,456", num1.replaceAll(regex, "$1,"));
assertEquals("1,234,567", num2.replaceAll(regex, "$1,"));
assertEquals("12,345,678", num3.replaceAll(regex, "$1,"));
assertEquals("123,456,789", num4.replaceAll(regex, "$1,"));
}
}
Le lien suivant suggère que AS3 le fait?
Trouvé sur http://gskinner.com/RegExr/
Communauté > séparateur de Milliers
Motif: /\d{1,3}(?=(\d{3})+(?!\d))/g
Remplacer: $&,
trace ( String("1000000000").replace( /\d{1,3}(?=(\d{3})+(?!\d))/g , "$&,") );
Il a fait le travail!
Si votre moteur regex a des lookaheads positifs, vous pouvez faire quelque chose comme ceci:
string.replace(/(\d)(?=(\d\d\d)+$)/, "$1,")
Où le lookahead positif (?=...) signifie que l'expression rationnelle ne correspond que lorsque l'expression lookahead ... correspondre.
(notez que les expressions lookaround ne sont pas toujours très efficaces.)
Bien que beaucoup de ces réponses fonctionnent bien avec des entiers positifs, beaucoup de leurs entrées d'argument sont converties en nombres, ce qui implique qu'elles peuvent gérer des valeurs négatives ou contenir des décimales, et ici toutes les solutions échouent. Bien que la réponse actuellement sélectionnée ne suppose pas un nombre, j'étais curieux de trouver une solution qui pourrait et était également plus performante que RegExp (ce que AS3 ne fait pas bien).
J'ai rassemblé beaucoup de réponses ici dans une classe de test (et inclus un solution de ce blog et une réponse de ma part appelée commaify ) et les a formatés de manière cohérente pour faciliter la comparaison:
package
{
public class CommaNumberSolutions
{
public static function commaify( input:Number ):String
{
var split:Array = input.toString().split( '.' ),
front:String = split[0],
back:String = ( split.length > 1 ) ? "." + split[1] : null,
n:int = input < 0 ? 2 : 1,
commas:int = Math.floor( (front.length - n) / 3 ),
i:int = 1;
for ( ; i <= commas; i++ )
{
n = front.length - (3 * i + i - 1);
front = front.slice( 0, n ) + "," + front.slice( n );
}
if ( back )
return front + back;
else
return front;
}
public static function getCommaString( input:Number ):String
{
var s:String = input.toString();
if ( s.length <= 3 )
return s;
var i:int = s.length % 3;
if ( i == 0 )
i = 3;
for ( ; i < s.length; i += 4 )
{
var part1:String = s.substr(0, i);
var part2:String = s.substr(i, s.length);
s = part1.concat(",", part2);
}
return s;
}
public static function formatNumber( input:Number ):String
{
var s:String = input.toString()
var result:String = ''
while ( s.length > 3 )
{
var chunk:String = s.substr(-3)
s = s.substr(0, s.length - 3)
result = ',' + chunk + result
}
if ( s.length > 0 )
result = s + result
return result
}
public static function commaCoder( input:Number ):String
{
var s:String = "";
var len:Number = input.toString().length;
for ( var i:int = 0; i < len; i++ )
{
if ( (len-i) % 3 == 0 && i != 0)
s += ",";
s += input.toString().charAt(i);
}
return s;
}
public static function regex1( input:Number ):String
{
return input.toString().replace( /-{0,1}(\d)(?=(\d\d\d)+$)/g, "$1," );
}
public static function regex2( input:Number ):String
{
return input.toString().replace( /-{0,1}\d{1,3}(?=(\d{3})+(?!\d))/g , "$&,")
}
public static function addCommas( input:Number ):String
{
var negative:String = "";
if ( input < 0 )
{
negative = "-";
input = Math.abs(input);
}
var s:String = input.toString();
var results:Array = s.split(/\./);
s = results[0];
if ( s.length > 3 )
{
var mod:Number = s.length % 3;
var output:String = s.substr(0, mod);
for ( var i:Number = mod; i < s.length; i += 3 )
{
output += ((mod == 0 && i == 0) ? "" : ",") + s.substr(i, 3);
}
if ( results.length > 1 )
{
if ( results[1].length == 1 )
return negative + output + "." + results[1] + "0";
else
return negative + output + "." + results[1];
}
else
return negative + output;
}
if ( results.length > 1 )
{
if ( results[1].length == 1 )
return negative + s + "." + results[1] + "0";
else
return negative + s + "." + results[1];
}
else
return negative + s;
}
}
}
Ensuite, j'ai testé chacun pour la précision et la performance:
package
{
public class TestCommaNumberSolutions
{
private var functions:Array;
function TestCommaNumberSolutions()
{
functions = [
{ name: "commaify()", f: CommaNumberSolutions.commaify },
{ name: "addCommas()", f: CommaNumberSolutions.addCommas },
{ name: "getCommaString()", f: CommaNumberSolutions.getCommaString },
{ name: "formatNumber()", f: CommaNumberSolutions.formatNumber },
{ name: "regex1()", f: CommaNumberSolutions.regex1 },
{ name: "regex2()", f: CommaNumberSolutions.regex2 },
{ name: "commaCoder()", f: CommaNumberSolutions.commaCoder }
];
verify();
measure();
}
protected function verify():void
{
var assertions:Array = [
{ input: 1, output: "1" },
{ input: 21, output: "21" },
{ input: 321, output: "321" },
{ input: 4321, output: "4,321" },
{ input: 54321, output: "54,321" },
{ input: 654321, output: "654,321" },
{ input: 7654321, output: "7,654,321" },
{ input: 987654321, output: "987,654,321" },
{ input: 1987654321, output: "1,987,654,321" },
{ input: 21987654321, output: "21,987,654,321" },
{ input: 321987654321, output: "321,987,654,321" },
{ input: 4321987654321, output: "4,321,987,654,321" },
{ input: 54321987654321, output: "54,321,987,654,321" },
{ input: 654321987654321, output: "654,321,987,654,321" },
{ input: 7654321987654321, output: "7,654,321,987,654,321" },
{ input: 87654321987654321, output: "87,654,321,987,654,321" },
{ input: -1, output: "-1" },
{ input: -21, output: "-21" },
{ input: -321, output: "-321" },
{ input: -4321, output: "-4,321" },
{ input: -54321, output: "-54,321" },
{ input: -654321, output: "-654,321" },
{ input: -7654321, output: "-7,654,321" },
{ input: -987654321, output: "-987,654,321" },
{ input: -1987654321, output: "-1,987,654,321" },
{ input: -21987654321, output: "-21,987,654,321" },
{ input: -321987654321, output: "-321,987,654,321" },
{ input: -4321987654321, output: "-4,321,987,654,321" },
{ input: -54321987654321, output: "-54,321,987,654,321" },
{ input: -654321987654321, output: "-654,321,987,654,321" },
{ input: -7654321987654321, output: "-7,654,321,987,654,321" },
{ input: -87654321987654321, output: "-87,654,321,987,654,321" },
{ input: .012345, output: "0.012345" },
{ input: 1.012345, output: "1.012345" },
{ input: 21.012345, output: "21.012345" },
{ input: 321.012345, output: "321.012345" },
{ input: 4321.012345, output: "4,321.012345" },
{ input: 54321.012345, output: "54,321.012345" },
{ input: 654321.012345, output: "654,321.012345" },
{ input: 7654321.012345, output: "7,654,321.012345" },
{ input: 987654321.012345, output: "987,654,321.012345" },
{ input: 1987654321.012345, output: "1,987,654,321.012345" },
{ input: 21987654321.012345, output: "21,987,654,321.012345" },
{ input: -.012345, output: "-0.012345" },
{ input: -1.012345, output: "-1.012345" },
{ input: -21.012345, output: "-21.012345" },
{ input: -321.012345, output: "-321.012345" },
{ input: -4321.012345, output: "-4,321.012345" },
{ input: -54321.012345, output: "-54,321.012345" },
{ input: -654321.012345, output: "-654,321.012345" },
{ input: -7654321.012345, output: "-7,654,321.012345" },
{ input: -987654321.012345, output: "-987,654,321.012345" },
{ input: -1987654321.012345, output: "-1,987,654,321.012345" },
{ input: -21987654321.012345, output: "-21,987,654,321.012345" }
];
var i:int;
var len:int = assertions.length;
var assertion:Object;
var f:Function;
var s1:String;
var s2:String;
for each ( var o:Object in functions )
{
i = 0;
f = o.f;
trace( '\rVerify: ' + o.name );
for ( ; i < len; i++ )
{
assertion = assertions[ i ];
s1 = f.apply( null, [ assertion.input ] );
s2 = assertion.output;
if ( s1 !== s2 )
trace( 'Test #' + i + ' Failed: ' + s1 + ' !== ' + s2 );
}
}
}
protected function measure():void
{
// Generate random inputs
var values:Array = [];
for ( var i:int = 0; i < 999999; i++ ) {
values.push( Math.random() * int.MAX_VALUE * ( Math.random() > .5 ? -1 : 1) );
}
var len:int = values.length;
var stopwatch:Stopwatch = new Stopwatch;
var s:String;
var f:Function;
trace( '\rTesting ' + len + ' random values' );
// Test each function
for each ( var o:Object in functions )
{
i = 0;
s = "";
f = o.f;
stopwatch.start();
for ( ; i < len; i++ ) {
s += f.apply( null, [ values[i] ] ) + " ";
}
stopwatch.stop();
trace( o.name + '\t\ttook ' + (stopwatch.elapsed/1000) + 's' ); //(stopwatch.elapsed/len) + 'ms'
}
}
}
}
import flash.utils.getTimer;
class Stopwatch
{
protected var startStamp:int;
protected var stopStamp:int;
protected var _started:Boolean;
protected var _stopped:Boolean;
function Stopwatch( startNow:Boolean = true ):void
{
if ( startNow )
start();
}
public function start():void
{
startStamp = getTimer();
_started = true;
_stopped = false;
}
public function stop():void
{
stopStamp = getTimer();
_stopped = true;
_started = false;
}
public function get elapsed():int
{
return ( _stopped ) ? stopStamp - startStamp : ( _started ) ? getTimer() - startStamp : 0;
}
public function get started():Boolean
{
return _started;
}
public function get stopped():Boolean
{
return _stopped;
}
}
En raison du manque de précision D'AS3 avec des nombres plus grands, chaque classe a échoué à ces tests:
Test #15 Failed: 87,654,321,987,654,320 !== 87,654,321,987,654,321
Test #31 Failed: -87,654,321,987,654,320 !== -87,654,321,987,654,321
Test #42 Failed: 21,987,654,321.012344 !== 21,987,654,321.012345
Test #53 Failed: -21,987,654,321.012344 !== -21,987,654,321.012345
, Mais seulement deux fonctions passé tous les autres tests: commaify() et addCommas().
Les tests de performance montrent que commaify () {[11] } est le plus préformant de toutes les solutions:
Testing 999999 random values
commaify() took 12.411s
addCommas() took 17.863s
getCommaString() took 18.519s
formatNumber() took 14.409s
regex1() took 40.654s
regex2() took 36.985s
En outre commaify () peut être étendu à inclure des arguments pour la longueur décimale et le remplissage zéro sur la partie décimale - il surpasse également les autres à 13.128 s :
public static function cappedDecimal( input:Number, decimalPlaces:int = 2 ):Number
{
if ( decimalPlaces == 0 )
return Math.floor( input );
var decimalFactor:Number = Math.pow( 10, decimalPlaces );
return Math.floor( input * decimalFactor ) / decimalFactor;
}
public static function cappedDecimalString( input:Number, decimalPlaces:int = 2, padZeros:Boolean = true ):String
{
if ( padZeros )
return cappedDecimal( input, decimalPlaces ).toFixed( decimalPlaces );
else
return cappedDecimal( input, decimalPlaces ).toString();
}
public static function commaifyExtended( input:Number, decimalPlaces:int = 2, padZeros:Boolean = true ):String
{
var split:Array = cappedDecimalString( input, decimalPlaces, padZeros ).split( '.' ),
front:String = split[0],
back:String = ( split.length > 1 ) ? "." + split[1] : null,
n:int = input < 0 ? 2 : 1,
commas:int = Math.floor( (front.length - n) / 3 ),
i:int = 1;
for ( ; i <= commas; i++ )
{
n = front.length - (3 * i + i - 1);
front = front.slice( 0, n ) + "," + front.slice( n );
}
if ( back )
return front + back;
else
return front;
}
Donc, je dirais que commaify() répond aux exigences de polyvalence et de performance, mais certainement pas le plus compact ou élégant.
Ce n'est vraiment pas la meilleure utilisation de RegEx... Je ne suis pas au courant d'une fonction de formatage des nombres, mais Ce thread semble fournir une solution.
function commaCoder(yourNum):String {
//var yourNum:Number = new Number();
var numtoString:String = new String();
var numLength:Number = yourNum.toString().length;
numtoString = "";
for (i=0; i<numLength; i++) {
if ((numLength-i)%3 == 0 && i != 0) {
numtoString += ",";
}
numtoString += yourNum.toString().charAt(i);
trace(numtoString);
}
return numtoString;
}
Si vous insistez vraiment sur L'utilisation de RegEx, vous pouvez simplement inverser la chaîne,appliquer la fonction regex replace, puis l'Inverser.
Un sexeger est bon pour cela. En bref, un sexeger est une expression rationnelle inversée exécutée contre une chaîne inversée dont vous inversez la sortie. Il est généralement plus efficace que l'autre. Voici un pseudocode pour ce que vous voulez faire:
string = reverse string
string.replace(/(\d{3})(?!$)/g, "$1,")
string = reverse string
Voici une implémentation Perl
#!/usr/bin/perl
use strict;
use warnings;
my $s = 13_456_789;
for my $n (1, 12, 123, 1234, 12345, 123456, 1234567) {
my $s = reverse $n;
$s =~ s/([0-9]{3})(?!$)/$1,/g;
$s = reverse $s;
print "$s\n";
}
/ / C'est un code simple et cela fonctionne bien...:)
import java.util.Scanner;
public class NumberWithCommas {
public static void main(String a[]) {
Scanner sc = new Scanner(System.in);
String num;
System.out.println("\n enter the number:");
num = sc.next();
printNumber(num);
}
public static void printNumber(String ar) {
int len, i = 0, temp = 0;
len = ar.length();
temp = len / 3;
if (len % 3 == 0)
temp = temp - 1;
len = len + temp;
char[] ch = ar.toCharArray();
char[] ch1 = new char[len];
for (int j = 0, k = (ar.length() - 1); j < len; j++)
{
if (i < 3)
{
ch1[j] = ch[k];
i++;
k--;
}
else
{
ch1[j] = ',';
i = 0;
}
}
for (int j = len - 1; j >= 0; j--)
System.out.print(ch1[j]);
System.out.println("");
}
}
Si vous ne pouvez pas utiliser d'anticipation sur les expressions régulières, vous pouvez utiliser ceci:
string.replace(/^(.*?,)?(\d{1,3})((?:\d{3})+)$/, "$1$2,$3")
Dans une boucle jusqu'à ce qu'il n'y ait rien à remplacer.
Par exemple, une solution perlish ressemblerait à ceci:
my $num = '1234567890';
while ($num =~ s/^(.*?,)?(\d{1,3})((?:\d{3})+)$/$1$2,$3/) {}
Essayez ce code. c'est simple et la meilleure performance.
var reg:RegExp=/\d{1,3}(?=(\d{3})+(?!\d))/g;
var my_num:Number = 48712694;
var my_num_str:String = String(my_num).replace(reg,"$&,");
trace(my_num_str);
::sortie::
48,712,694