63

I'm still fairly new to Scala, and I'm discovering new and interesting ways for doing things on an almost daily basis, but they're not always sensible, and sometimes already exist within the language as a construct and I just don't know about them. So, with that preamble, I'm checking to see if a given string is comprised entirely of digits, so I'm doing:

def isAllDigits(x: String) = x.map(Character.isDigit(_)).reduce(_&&_)

is this sensible or just needlessly silly? It there a better way? Is it better just to call x.toInt and catch the exception, or is that less idiomatic? Is there a performance benefit/drawback to either?

harschware
  • 13,006
  • 17
  • 55
  • 87
PlexQ
  • 3,154
  • 2
  • 21
  • 26
  • 1
    Note that the answer to your question will determine if the `String` contains only digits, but not if it will fit in an `Int` or `Long`. The answer by @Tvaroh will guarantee that the result will "fit" in the appropriate numeric type. – Ralph Oct 01 '14 at 12:14

10 Answers10

103

Try this:

def isAllDigits(x: String) = x forall Character.isDigit

forall takes a function (in this case Character.isDigit) that takes an argument that is of the type of the elements of the collection and returns a Boolean; it returns true if the function returns true for all elements in the collection, and false otherwise.

Jesper
  • 202,709
  • 46
  • 318
  • 350
  • Ah! brilliant, that's the special sauce I wanted, I had to believe there was a construct to do this in the language! Now I'm wondering if there is a similar construct that might be something for any, so instead of reducing logical AND, reducing logical OR? – PlexQ Mar 30 '12 at 16:29
  • 1
    @PlexQ See the [Scala API documentation](http://www.scala-lang.org/docu/files/collections-api/collections.html) (especially of trait `Traversable`) and these docs about the [Scala collections library](http://www.scala-lang.org/docu/files/collections-api/collections.html) for more. – Jesper Mar 30 '12 at 19:34
  • 7
    `isAllDigits("")` returns `true`. This code returns `false` for the empty string: `def isAllDigits(x: String) = (x!="") && (x forall Character.isDigit)`. – Powers Aug 23 '16 at 14:59
  • 1
    returns false for a string like "9.0" – Mohan Aug 26 '17 at 17:25
  • @Phoenix of course, because the `.` (decimal dot) is not a digit. – Jesper Aug 28 '17 at 02:18
  • Use regular expression, i.e. x.matches("^-?[0-9]+(\\.[0-9]+)?$") always return proper result. – abanmitra Nov 21 '17 at 06:02
38

Do you want to know if the string is an integer? Then .toInt it and catch the exception. Do you instead want to know if the string is all digits? Then ask one of:

s.forall(_.isDigit)
s matches """\d+"""
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • The last one only checks if the string contains any digits, I guess. The correct regex would be """^\d+$""", or """^\d*$""" if an empty string also should count as "digit". – User Feb 08 '13 at 21:35
  • 1
    @Ixx - I see you don't understand how Java regexes work with various methods. – Rex Kerr Feb 08 '13 at 22:02
  • what do you mean with various methods? I just see \d+ there and that usually checks for digits somewhere in the string. But yes I haven't worked a lot with regexes in Java or Scala particularly, so maybe I'm missing that your regex works like ^\d+$ – User Feb 08 '13 at 22:12
  • 3
    @Ixx - `matches` requires a complete match. There are other methods that will bind the regex to e.g. the first occurrence. – Rex Kerr Feb 08 '13 at 22:14
  • Note this gives `false` for negative values: `scala> "-3".forall(_.isDigit) res62: Boolean = false` – dbau Feb 10 '14 at 12:32
  • 1
    @dbau - Indeed. "All digits" is not the same as "is an integer". – Rex Kerr Feb 11 '14 at 20:55
25

You also may consider something like this:

import scala.util.control.Exception.allCatch

def isLongNumber(s: String): Boolean = (allCatch opt s.toLong).isDefined
// or
def isDoubleNumber(s: String): Boolean = (allCatch opt s.toDouble).isDefined
Tvaroh
  • 6,645
  • 4
  • 51
  • 55
  • How would you go about differentiating decimals and round numbers using this? – dbau Feb 23 '14 at 12:17
  • Is there some advantage to (allCatch opt s.toLong).isDefined as opposed to Try(s.toLong).isSuccess ? – sean_robbins Mar 02 '16 at 16:30
  • @sean_robbins they are equivalent. Though, currently I would use `nonFatalCatch`. Not sure if `Try` catches fatal exceptions. – Tvaroh Mar 02 '16 at 17:38
  • Very nice. Typically this is what we need. `isDoubleNumber` works with negatives, fractions, scientific notations (1e-8), which would otherwise require special handling. – Amir Dec 03 '17 at 09:54
  • @dbau `def isDecimal(s: String) = isDoubleNumber(s) && !isLongNumber(s)` `def isRoundNumber(s: String) = isLongNumber(s)` to the size limits of `Long`, at any rate. – tilde May 02 '18 at 17:11
8

You could simply use a regex for this.

val onlyDigitsRegex = "^\\d+$".r

def isAllDigits(x: String) = x match {
  case onlyDigitsRegex() => true
  case _ => false
}

Or simply

def isAllDigits(x: String) = x.matches("^\\d+$")

And to improve this a little bit, you can use the pimp my library pattern to make it a method on your string:

implicit def AllDigits(x: String) = new { def isAllDigits = x.matches("^\\d+$") }

"12345".isAllDigits // => true
"12345foobar".isAllDigits // => false
drexin
  • 24,225
  • 4
  • 67
  • 81
7

Starting Scala 2.13 we can use String::toDoubleOption, to determine whether a String is a decimal number or not:

"324.56".toDoubleOption.isDefined // true
"4.06e3".toDoubleOption.isDefined // true
"9w01.1".toDoubleOption.isDefined // false

Similar option to determine if a String is a simple Int:

"324".toIntOption.isDefined // true
"à32".toIntOption.isDefined // false
"024".toIntOption.isDefined // true
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
6
import scala.util.Try


object NumCruncher {

  def isShort(aString: String): Boolean = Try(aString.toLong).isSuccess
  def isInt(aString: String): Boolean = Try(aString.toInt).isSuccess
  def isLong(aString: String): Boolean = Try(aString.toLong).isSuccess
  def isDouble(aString: String): Boolean = Try(aString.toDouble).isSuccess
  def isFloat(aString: String): Boolean = Try(aString.toFloat).isSuccess

  /**
   *
   * @param x the string to check
   * @return true if the parameter passed is a Java primitive number
   */
  def isNumber(x: String): Boolean = {
    List(isShort(x), isInt(x), isLong(x), isDouble(x), isFloat(x))
      .foldLeft(false)(_ || _)
  }

}
Yordan Georgiev
  • 5,114
  • 1
  • 56
  • 53
2

Try might not performance-wise be the optimal choice, but otherwise it's neat:

scala> import scala.util.Try
scala> Try{ "123x".toInt }
res4: scala.util.Try[Int] = Failure(java.lang.NumberFormatException: For input string: "123x")
scala> Try{ "123x".toInt }.isSuccess
res5: Boolean = false
akauppi
  • 17,018
  • 15
  • 95
  • 120
1

@Jesper's answer is spot on.

Do NOT do what I'm suggesting below (explanation follows)

Since you are checking if a given string is numeric (title states you want a decimal), the assumption is that you intend to make a conversion if the forall guard passes.

A simple implicit in scope will save a whopping 9 key strokes ;-)

implicit def str2Double(x: String) = x.toDouble

Why this is dangerous

def takesDouble(x: Double) = x

The compiler will now allow takesDouble("runtime fail") since the implicit tries to convert whatever string you use to Double, with zero guarantee of success, yikes.

implicit conversions then seem better suited to situations where an acceptable default value is supplied on conversion failure (which is not always the case; therefore implicit with caution)

virtualeyes
  • 11,147
  • 6
  • 56
  • 91
0

Here is one more:

  import scala.util.Try

  val doubleConverter: (String => Try[Double]) = (s: String) => Try{ s.map(c => if ((Character.isDigit(c) == true) || (c == '.')) Some(c) else None).flatten.mkString.toDouble }

  val d1: Try[Double] = doubleConverter("+ 1234.0%")
  val d2: Try[Double] = doubleConverter("+ 1234..0%")
ajmnsk
  • 31
  • 5
0

Based on brilliant Jexter's solution, in this piece of code I take care of the NullPointerException using Option:

    def isValidPositiveNumber(baseString: Option[String]): Boolean = baseString match {
        case Some(code) => !code.isEmpty && (code forall Character.isDigit)
        case None => false
      }