web-dev-qa-db-fra.com

Meilleure implémentation pour une méthode isNumber (string)

Dans mon expérience limitée, j'ai été sur plusieurs projets qui ont eu une sorte de classe utilitaire de chaîne avec des méthodes pour déterminer si une chaîne donnée est un nombre. L'idée a toujours été la même, mais la mise en œuvre a été différente. Certains entourent une tentative d'analyse avec try/catch 

public boolean isInteger(String str) {
    try {
        Integer.parseInt(str);
        return true;
    } catch (NumberFormatException nfe) {}
    return false;
}

et d'autres correspondent avec regex

public boolean isInteger(String str) {
    return str.matches("^-?[0-9]+(\\.[0-9]+)?$");
}

L'une de ces méthodes est-elle meilleure que l'autre? Personnellement, je préfère utiliser l'approche regex, car elle est concise, mais fonctionnera-t-elle à égalité si elle est appelée en parcourant, par exemple, une liste de plusieurs centaines de milliers de chaînes?

Remarque: comme je suis un peu nouveau sur le site, je ne comprends pas tout à fait cette affaire de wiki de communauté. Par conséquent, si cette adresse appartient à cet utilisateur, faites-le-moi savoir et je le déplacerai avec plaisir.

EDIT: Avec toutes les suggestions TryParse, j’ai porté le code de référence d’Asaph (merci pour cet excellent post!) En C # et ajouté une méthode TryParse. Et comme il semble, le TryParse gagne haut la main. Cependant, l’approche try catch a pris un temps fou. Au point de penser que j'ai fait quelque chose de mal! J'ai également mis à jour regex pour gérer les points négatifs et décimaux. 

Résultats du code de référence C # mis à jour:

00:00:51.7390000 for isIntegerParseInt
00:00:03.9110000 for isIntegerRegex
00:00:00.3500000 for isIntegerTryParse

En utilisant:

static bool isIntegerParseInt(string str) {
    try {
        int.Parse(str);
        return true;
    } catch (FormatException e){}
    return false;
}

static bool isIntegerRegex(string str) {
    return Regex.Match(str, "^-?[0-9]+(\\.[0-9]+)?$").Success;
}

static bool isIntegerTryParse(string str) {
    int bob;
    return Int32.TryParse(str, out bob);
}
18
thorncp

Je viens de lancer quelques tests sur les performances de ces 2 méthodes (Sur Macbook Pro OSX Leopard Java 6). ParseInt est plus rapide. Voici la sortie:

This operation took 1562 ms.
This operation took 2251 ms.

Et voici mon code de référence:


public class IsIntegerPerformanceTest {

    public static boolean isIntegerParseInt(String str) {
        try {
            Integer.parseInt(str);
            return true;
        } catch (NumberFormatException nfe) {}
        return false;
    }

    public static boolean isIntegerRegex(String str) {
        return str.matches("^[0-9]+$");
    }

    public static void main(String[] args) {
        long starttime, endtime;
        int iterations = 1000000;
        starttime = System.currentTimeMillis();
        for (int i=0; i<iterations; i++) {
            isIntegerParseInt("123");
            isIntegerParseInt("not an int");
            isIntegerParseInt("-321");
        }
        endtime = System.currentTimeMillis();
        System.out.println("This operation took " + (endtime - starttime) + " ms.");
        starttime = System.currentTimeMillis();
        for (int i=0; i<iterations; i++) {
            isIntegerRegex("123");
            isIntegerRegex("not an int");
            isIntegerRegex("-321");
        }
        endtime = System.currentTimeMillis();
        System.out.println("This operation took " + (endtime - starttime) + " ms.");
    }
}

Notez également que votre expression rationnelle rejettera les nombres négatifs et que la méthode parseInt les acceptera.

13
Asaph

Voici notre façon de faire ceci:

public boolean isNumeric(String string) throws IllegalArgumentException
{
   boolean isnumeric = false;

   if (string != null && !string.equals(""))
   {
      isnumeric = true;
      char chars[] = string.toCharArray();

      for(int d = 0; d < chars.length; d++)
      {
         isnumeric &= Character.isDigit(chars[d]);

         if(!isnumeric)
         break;
      }
   }
   return isnumeric;
}
4
Jeremy Cron

Si les performances absolues sont essentielles et si vous recherchez uniquement des entiers (et non des nombres à virgule flottante), je suppose que l'itération sur chaque caractère de la chaîne, renvoyant la valeur false si vous rencontrez un problème ne se situant pas dans la plage 0-9, sera la plus rapide.

RegEx est une solution plus générale et ne fonctionnera donc probablement pas aussi vite pour ce cas particulier. Une solution qui lève une exception aura une charge supplémentaire dans ce cas. TryParse sera légèrement plus lent si vous ne vous souciez pas de la valeur du nombre, que ce soit ou non un nombre, car la conversion en nombre doit également avoir lieu.

Pour tout sauf une boucle interne appelée à plusieurs reprises, les différences entre toutes ces options doivent être insignifiantes.

3
Eric J.

J'avais besoin de refactoriser le code comme le vôtre pour me débarrasser de NumberFormatException. Le code refactorisé:

public static Integer parseInteger(final String str) {
    if (str == null || str.isEmpty()) {
        return null;
    }
    final Scanner sc = new Scanner(str);
    return Integer.valueOf(sc.nextInt());
}

En tant que gars de Java 1.4, je ne connaissais pas Java.util.Scanner. J'ai trouvé cet article intéressant:

http://rosettacode.org/wiki/Determine_if_a_string_is_numeric#Java

J'ai personnellement aimé la solution avec le scanner, très compacte et toujours lisible.

3
Heiner

Certaines langues, comme le C #, ont un TryParse (ou équivalent) qui fonctionne assez bien pour quelque chose comme ça.

public boolean IsInteger(string value)
{
  int i;
  return Int32.TryParse(value, i);
}
2
Brandon

Personnellement, je le ferais si vous voulez vraiment le simplifier.

public boolean isInteger(string myValue)
{
    int myIntValue;
    return int.TryParse(myValue, myIntValue)
}
2
Mitchel Sellers

Vous pouvez créer une méthode d'extension pour une chaîne et donner à l'ensemble du processus une apparence plus nette ...

public static bool IsInt(this string str)
{
    int i;
    return int.TryParse(str, out i);
}

Vous pouvez alors faire ce qui suit dans votre code actuel ...

if(myString.IsInt())....
2
RSolberg
public static boolean CheckString(String myString) {

char[] digits;

    digits = myString.toCharArray();
    for (char div : digits) {// for each element div of type char in the digits collection (digits is a collection containing div elements).
        try {
            Double.parseDouble(myString);
            System.out.println("All are numbers");
            return true;
        } catch (NumberFormatException e) {

            if (Character.isDigit(div)) {
                System.out.println("Not all are chars");

                return false;
            }
        }
    }

    System.out.println("All are chars");
    return true;
}
1
Naim

J'aime le code:

public static boolean isIntegerRegex(String str) {
    return str.matches("^[0-9]+$");
}

Mais cela fera bien plus quand créer Pattern avant de l’utiliser:

public static Pattern patternInteger = Pattern.compile("^[0-9]+$");
public static boolean isIntegerRegex(String str) {
  return patternInteger.matcher(str).matches();
}

Appliquer par test nous avons résultat:

This operation isIntegerParseInt took 1313 ms.
This operation isIntegerRegex took 1178 ms.
This operation isIntegerRegexNew took 304 ms.

Avec:

public class IsIntegerPerformanceTest {
  private static Pattern pattern = Pattern.compile("^[0-9]+$");

    public static boolean isIntegerParseInt(String str) {
    try {
      Integer.parseInt(str);
      return true;
    } catch (NumberFormatException nfe) {
    }
    return false;
  }

  public static boolean isIntegerRegexNew(String str) {
    return pattern.matcher(str).matches();
  }

  public static boolean isIntegerRegex(String str) {
    return str.matches("^[0-9]+$");
  }

    public static void main(String[] args) {
        long starttime, endtime;
    int iterations = 1000000;
    starttime = System.currentTimeMillis();
    for (int i = 0; i < iterations; i++) {
      isIntegerParseInt("123");
      isIntegerParseInt("not an int");
      isIntegerParseInt("-321");
    }
    endtime = System.currentTimeMillis();
    System.out.println("This operation isIntegerParseInt took " + (endtime - starttime) + " ms.");
    starttime = System.currentTimeMillis();
    for (int i = 0; i < iterations; i++) {
      isIntegerRegex("123");
      isIntegerRegex("not an int");
      isIntegerRegex("-321");
    }
    endtime = System.currentTimeMillis();
    System.out.println("This operation took isIntegerRegex " + (endtime - starttime) + " ms.");
    starttime = System.currentTimeMillis();
    for (int i = 0; i < iterations; i++) {
      isIntegerRegexNew("123");
      isIntegerRegexNew("not an int");
      isIntegerRegexNew("-321");
    }
    endtime = System.currentTimeMillis();
    System.out.println("This operation took isIntegerRegexNew " + (endtime - starttime) + " ms.");
  }
}
1
Hong Xanh

En utilisant .NET, vous pourriez faire quelque chose comme:

private bool isNumber(string str)
{
    return str.Any(c => !char.IsDigit(c));
}
1
Cleiton

Je pense que cela pourrait être plus rapide que les solutions précédentes si vous procédez comme suit (Java):

public final static boolean isInteger(String in)
{
    char c;
    int length = in.length();
    boolean ret = length > 0;
    int i = ret && in.charAt(0) == '-' ? 1 : 0;
    for (; ret && i < length; i++)
    {
        c = in.charAt(i);
        ret = (c >= '0' && c <= '9');
    }
    return ret;
}

J'ai exécuté le même code qu'Asaph et le résultat était:

Cette opération a pris 28 ms.

Une différence énorme (contre 1691 ms et 2049 ms sur mon ordinateur). Tenez compte du fait que cette méthode ne valide pas si la chaîne est null, vous devez donc le faire précédemment (y compris le découpage de chaîne).

1
Barenca

C'est mon implémentation pour vérifier si une chaîne est composée de chiffres:

public static boolean isNumeric(String string)
{
    if (string == null)
    {
        throw new NullPointerException("The string must not be null!");
    }
    final int len = string.length();
    if (len == 0)
    {
        return false;
    }
    for (int i = 0; i < len; ++i)
    {
        if (!Character.isDigit(string.charAt(i)))
        {
            return false;
        }
    }
    return true;
}
1
pschichtel

Je pense que les gens ici manquent un point. L'utilisation du même motif à plusieurs reprises a une optimisation très facile. Utilisez simplement un singleton du motif. En le faisant, dans tous mes tests, l'approche try-catch n'a jamais un meilleur point de repère que l'approche pattern. Avec un test de réussite, try-catch prend deux fois plus de temps, avec un test d'échec, il est 6 fois plus lent.

public static final Pattern INT_PATTERN= Pattern.compile("^-?[0-9]+(\\.[0-9]+)?$");

public static boolean isInt(String s){
  return INT_PATTERN.matcher(s).matches();
}
1
user1844655

Je viens d'ajouter cette classe à mes utils:

public class TryParseLong {
private boolean isParseable;

private long value;

public TryParseLong(String toParse) {
    try {
        value = Long.parseLong(toParse);
        isParseable = true;
    } catch (NumberFormatException e) {
        // Exception set to null to indicate it is deliberately
        // being ignored, since the compensating action
        // of clearing the parsable flag is being taken.
        e = null;

        isParseable = false;
    }
}

public boolean isParsable() {
    return isParseable;
}

public long getLong() {
    return value;
}
}

Pour l'utiliser:

TryParseLong valueAsLong = new TryParseLong(value);

if (valueAsLong.isParsable()) {
    ...
    // Do something with valueAsLong.getLong();
} else {
    ...
}

Ceci n’analyse la valeur qu’une fois.

Il utilise toujours l'exception et le flux de contrôle par exceptions, mais au moins, il encapsule ce type de code dans une classe d'utilitaires, et le code qui l'utilise peut fonctionner de manière plus normale.

Le problème avec Java versus C #, c'est que C # a des valeurs et passe par référence, ce qui lui permet de renvoyer efficacement 2 informations; le drapeau pour indiquer que quelque chose est analysable ou non, et la valeur réelle analysée. Lorsque nous récupérons> 1 valeur en Java, nous devons créer un objet pour les conserver. J'ai donc adopté cette approche et mis le drapeau et la valeur analysée dans un objet.

L'analyse d'évasion est susceptible de gérer cela efficacement, et de créer la valeur et l'indicateur sur la pile, et de ne jamais créer cet objet sur le tas, donc je pense que cela aura un impact minimal sur les performances.

À mon avis, cela donne le compromis optimal entre conserver le code par contrôle, des performances optimales et ne pas analyser le nombre entier plus d'une fois.

0
user2800708

Pour les nombres longs, utilisez ceci: (Java)

public static boolean isNumber(String string) {
    try {
        Long.parseLong(string);
    } catch (Exception e) {
        return false;
    }
    return true;
}
0
ssamuel68

J'utilise cela, mais j'ai aimé la rigueur d'Asaph dans son post.

public static bool IsNumeric(object expression)
{
if (expression == null)
return false;

double number;
return Double.TryParse(Convert.ToString(expression, CultureInfo.InvariantCulture),   NumberStyles.Any,
NumberFormatInfo.InvariantInfo, out number);
}
0
Daver
 public static boolean isNumber(String str){
      return str.matches("[0-9]*\\.[0-9]+");
    }

vérifier si nombre (y compris float, integer) ou non

0
Wong edan

public statique booléen CheckIfNumber (Numéro de chaîne) {

    for(int i = 0; i < number.length(); i++){
        try{
            Double.parseDouble(number.substring(i));

        }catch(NumberFormatException ex){
            return false;
        }
    }
    return true;     
}

J'avais déjà eu ce problème auparavant, mais lorsque j'avais saisi un nombre, puis un caractère, il restait toujours vrai, je pense que c'est la meilleure façon de le faire. Il suffit de vérifier si chaque caractère est un nombre. Un peu plus longtemps mais cela prend soin si vous avez la situation d'un utilisateur qui entre "1abc". Pour une raison quelconque, lorsque j’ai essayé d’essayer d’attraper sans itérer, je pensais toujours que c’était un chiffre alors ..

0
Daniel Kim

Une version modifiée de ma réponse précédente:

public static boolean isInteger(String in)
{
    if (in != null)
    {
        char c;
        int i = 0;
        int l = in.length();
        if (l > 0 && in.charAt(0) == '-')
        {
            i = 1;
        }
        if (l > i)
        {
            for (; i < l; i++)
            {
                c = in.charAt(i);
                if (c < '0' || c > '9')
                    return false;
            }
            return true;
        }
    }
    return false;
}
0
Barenca