Is there an idiomatic, functional way in Scala to compare two dotted version strings, potentially of varying length?
For example:
1.0 == 1.0.0
1.2.4 > 1.2
1.10 > 1.2
(The Java solutions are typically quite imperative style).
Kinda same thing, but without recursion:
version1.split("\\.")
.zipAll(version2.split("\\."), "0", "0")
.find {case(a, b) => a != b }
.fold(0) { case (a, b) => a.toInt - b.toInt }
Also, FWIW, I think, this is a duplicate, because the accepted answer in the linked question, answers this one fairly well too.
Assuming the version strings have been obtained so they only contain dot-separated integers (i.e. "1.2.3" not "1.2.3-foo-beta-1234-etc"), this can be done by splitting on ".", zipping together the two sequences of numbers (zero-padding the least-significant positions using zipAll
) then processing the pairs in turn recursively.
As soon as we find a number that is different from its counterpart in the other string, we have an answer; but if the digits are the same, we look further.
def versionComp(a: String, b: String) = {
def nums(s: String) = s.split("\\.").map(_.toInt)
val pairs = nums(a).zipAll(nums(b), 0, 0).toList
def go(ps: List[(Int, Int)]): Int = ps match {
case Nil => 0
case (a, b) :: t =>
if (a > b) 1 else if (a < b) -1 else go(t)
}
go(pairs)
}
Examples:
versionComp("2", "1.1") //> 1
versionComp("1.2", "1.1") //> 1
versionComp("1.2", "1.1.5") //> 1
versionComp("1.2.0", "1.1") //> 1
versionComp("1.2.0.1", "1.2.0.0") //> 1
versionComp("1.2.3", "1.2.3") //> 0
versionComp("1.2.0", "1.2") //> 0
versionComp("1.2.0.0", "1.2") //> 0
versionComp("1.2", "1.5") //> -1
versionComp("1.2.0", "1.20") //> -1
versionComp("2.20.345", "3.1") //> -1
Well, if you are prepared to write some library code, you could create a Version
type and use an Ordering
type class on it.
class Version(major: Int, minor: Int, patchOpt: Option[Int], buildOpt: Option[Int], suffixOpt: Option[String])
with a parser and the Ordering
in the companion object
object Version {
// NOTE: This here may well not be complete. If this is what you are lokking for I'll update with a tested version
// Also NOTE: This would be the place where you customize the ordering to your requirements
implicit val ordering: Ordering[Version] = Ordering.tuple(v =>(v.major, v.minor, v.patchOpt, v.buildOpt, v.suffixOpt))
def fromString(in: String): Option[Version] = {
// ... extract all the parts into the proper fields of version or return None
???
}
def apply(in String): Version = fromVersion(in).getOrElse(throw new Exception())
def unapply(...):...
}
Given that, you can use it like that
// This import enabels infix operations like '<, >, <=' on types
// for which an Ordering type class exists
import Ordering.Implicits.infixOrderingOps
val v1 = Version("1.22.3")
val v2 = Version("0.33.")
val v3 = Version("1.3.5.6")
v1 < v2 // false
v2 <= v3 // true
...
This may be overkill for your use case but it gives you a more expressive way to describe what you expect a version to be and how you want to order versions.
You can use
val versionOrdering =
Ordering.by { (_: String).split("""\.""").map(_.toInt).toIterable }
def versionComp(a: String, b: String) = versionOrdering.compare(a, b)
You probably don't want to make it implicit because it would be used instead of normal String
ordering as well! A work-around is using a value class:
case class Version(asString: String) extends AnyVal
object Version {
implicit val versionOrdering: Ordering[Version] =
Ordering.by { _.asString.split("""\.""").map(_.toInt).toIterable }
}
as a lighter-weight alternative to Sascha Kolberg's answer. It'll let you write e.g.
listOfStrings.sortBy(Version(_))
For one-off comparisons, you can use
import scala.math.Ordering.Implicits._
a.split("""\.""").map(_.toInt).toSeq > b.split("""\.""").map(_.toInt).toSeq
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With