【问题标题】:Compare two numbers比较两个数字
【发布时间】:2013-03-31 22:34:21
【问题描述】:

如何比较两个数字。任何数字。喜欢IntFloat?我对与复数或类似的东西进行比较不感兴趣。我只想比较那些具有可比性的。 Float 和 Int 是。

假设你有:

def compareTwoNumbers[???](array:Array[???], number:???) = {
    array(0) > number // this has to compile
}

我写什么而不是???

到目前为止我尝试过的事情:

  1. Number
  2. T <: Number
  3. Numeric(对不起,我不明白在这种情况下如何使用它,没有示例/文档太差了)。

【问题讨论】:

  • 所以当您比较00.0 时,您的效果还可以吗?
  • @om-nom-nom 又是什么问题?
  • 实际上我提供了一个不好的例子(是的,我已经看到 与复数和类似的部分无法比较)。我想说的是,对浮点数 may be tricky 的比较以及通过使用 Number 或像 compareTwoNumbers 之类的一些一般抽象来覆盖它,您很可能会留下一个可以轻松隐藏错误的空间。
  • 您的array:Array[???] 中有什么内容?您知道某些数字是 IntFloat 并可能混合在一起?或者你知道有FloatInt吗?
  • @huynhjl 它可以有任何你从数字派生的东西,更好的是,如果它可以是任何为它定义了方法 > 的东西,但这听起来要求太多了。

标签: scala generics


【解决方案1】:

我认为主要的问题是数字类型的转换。所以让我们对其进行编码:

trait NumericConversion[X, Y] {
  def convert(x: X): Y
}

当然必须指定那个抽象概念:(例如)

implicit object Int2IntNumericConversion extends NumericConversion[Int, Int] {
  def convert(i: Int): Int = i
}
implicit object Double2DoubleNumericConversion extends NumericConversion[Double, Double] {
  def convert(d: Double): Double = d
}
implicit object Int2DoubleNumericConversion extends NumericConversion[Int, Double] {
  def convert(i: Int): Double = i.toDouble
}

现在比较方法如下:

def compareTwoNumbers1[N1, N2, N3](n1: N1, n2: N2)
                                  (implicit conv1: NumericConversion[N1, N3], 
                                            conv2: NumericConversion[N2, N3], 
                                              ord: Ordering[N3]): Int = {
  ord compare (conv1 convert n1, conv2 convert n2)
}

用法:

compareTwoNumbers1[Int, Double, Double](3, 8D)  // -1

可惜了,我们必须显式声明类型参数,所以我尝试了:

def compareTwoNumbers2[N3] = new {
  def apply[N1, N2](n1: N1, n2: N2)(implicit conv1: NumericConversion[N1, N3],
                                             conv2: NumericConversion[N2, N3], 
                                               ord: Ordering[N3]): Int = {
    ord compare (conv1 convert n1, conv2 convert n2)
  }
}

这简化为一种类型的参数:

compareTwoNumbers2[Double](3, 8D)  // -1

不满意,所以我尝试了这个:

trait NumericUpperBound[Num1, Num2, UpperBound]
implicit object NumericUpperBoundIDD extends NumericUpperBound[Int, Double, Double]
implicit object NumericUpperBoundDID extends NumericUpperBound[Double, Int, Double]

使用新的比较方法:

def compareTwoNumbers3[N1, N2, N3](n1: N1, n2: N2)
                                 (implicit nub: NumericUpperBound[N1, N2, N3], 
                                         conv1: NumericConversion[N1, N3], 
                                         conv2: NumericConversion[N2, N3], 
                                           ord: Ordering[N3]): Int = {
  ord compare (conv1 convert n1, conv2 convert n2)
}

现在可以了:

compareTwoNumbers3(3, 8D)  // -1

当然,必须为所有基元创建类型类。但是以后可以灵活地扩展到BigInt等。

编辑

@wvxvw 的评论提到了NumericUpperBounds 的矩阵,这启发了我绕过矩阵,这是一个运行示例(目前不包括ByteShort):

trait ==>[X, Y] extends (X => Y)

object ==> {
  def apply[X, Y](f: X => Y): X ==> Y = {
    new (X ==> Y) {
      def apply(x: X): Y = f(x)
    }
  }
}

implicit val Int2LongNumericConversion = ==> { x: Int => x.toLong }
implicit val Int2FloatNumericConversion = ==> { x: Int => x.toFloat }
implicit val Int2DoubleNumericConversion = ==> { x: Int => x.toDouble }
implicit val Long2FloatNumericConversion = ==> { x: Long => x.toFloat }
implicit val Long2DoubleNumericConversion = ==> { x: Long => x.toDouble }
implicit val Float2DoubleNumericConversion = ==> { x: Float => x.toDouble }
implicit def reflexiveNumericConversion[X]: X ==> X = new (X ==> X) { def apply(x: X): X = x }

trait NumericUpperBound[Num1, Num2, UpperBound]

implicit def reflexiveNumericUpperBound[X]: NumericUpperBound[X, X, X] = new NumericUpperBound[X, X, X] {}
implicit def inductiveNumericUpperBound1[X, Y](implicit ev: X ==> Y): NumericUpperBound[Y, X, Y] = new NumericUpperBound[Y, X, Y] {}
implicit def inductiveNumericUpperBound2[X, Y](implicit ev: X ==> Y): NumericUpperBound[X, Y, Y] = new NumericUpperBound[X, Y, Y] {}

def compareTwoNumbers[N1, N2, N3](n1: N1, n2: N2)
                                 (implicit nub: NumericUpperBound[N1, N2, N3], 
                                         conv1: N1 ==> N3, 
                                         conv2: N2 ==> N3, 
                                           ord: Ordering[N3]): Int = {
  ord compare (n1, n2)
}

compareTwoNumbers(9L, 13) // -1

【讨论】:

  • 方法调用时的具体类型并不是我所展示的那样。我认为像 Miles Sabin 这样的人可以改进我的方法。但你是对的,周围有很多东西只是为了与数字进行比较。我很期待看到那个讲座!
  • @wvxvw 我添加了一个示例来防止NumericUpperBounds 的矩阵。
【解决方案2】:

只需使用排序类型类:

def cmp[A](arr: Array[A], number: A)(implicit ord: Ordering[A]) =
  ord.gt(arr(0), number)

// or

def cmp[A](arr: Array[A], number: A)(implicit ord: Ordering[A]) = {
  import ord._
  arr(0) > number
}

scala> cmp(Array(4), 2)
res9: Boolean = true

scala> cmp(Array(BigInt(4)), BigInt(2))
res15: Boolean = true

scala> cmp(Array(4), 2.0)
<console>:9: error: type mismatch;
 found   : Array[Int]
 required: Array[AnyVal]

scala> cmp(Array(4.0), 2)
<console>:9: error: type mismatch;
 found   : Array[Double]
 required: Array[AnyVal]

【讨论】:

    【解决方案3】:

    我目前正在学习 Scala,因此请不要太认真地回答我的问题,但我认为 View BoundsType Constraints 可以帮助您。 View Bound 允许您比较任何标准数字,因为它们之间存在隐式视图,但它不会更进一步,因为没有从 BigInt 到 BigDecimal 的隐式视图。

    def compareTwoNumbers[A <% Double, B <% Double](array:Array[A], number:B) = array(0) > number
    
    scala> compareTwoNumbers(Array(1, 2, 0), 0.99)
    res1: Boolean = true
    
    scala> compareTwoNumbers(Array(1.0, 2, 0), 0.99)
    res2: Boolean = true
    
    scala> compareTwoNumbers(Array(1.0, 2, 0), 1)
    res3: Boolean = false
    

    我很想看看是否可以支持 BigInt 和 BigDecimal,并想知道我的解决方案有哪些缺点。

    【讨论】:

      【解决方案4】:

      这里是:

      def cmp[T1, T2](arr: Array[T1], num: T2)
             (implicit v12: T1 => T2 = null, v21: T2 => T1 = null,
              ord1: Ordering[T1], ord2: Ordering[T2]): Boolean = v12 match {
          case null => ord1.gt(arr(0), num)
          case _    => ord2.gt(arr(0), num)
        }
      

      一些用例:

      scala> cmp(Array(1,2), 0.1)  //T1 = Int, T2 = Double 
      res3: Boolean = true
      
      scala> cmp(Array(1.2, 2.3), 1) //T1 = Double, T2 = Int 
      res4: Boolean = true
      
      scala> cmp(Array(1,2), BigInt(100))  //T1 = Int, T2 = BigInt 
      res5: Boolean = false
      
      scala> cmp(Array(123.5 ,2233.9), BigDecimal(100)) //T1 = Double, T2 = BigDecimal 
      res6: Boolean = true
      
      scala> cmp(Array(123.5 ,2233.9), 200.toByte) 
      res7: Boolean = true
      

      【讨论】:

        【解决方案5】:
        def compareTwoNumbers(a: Number, b: Number) = {
          a.floatValue() > b.floatValue() // this has to compile
        }
        

        【讨论】:

          【解决方案6】:

          采用这段代码并不难:

          def gt[A : Numeric, B: Numeric](first: A, second: B) = {
              val a = implicitly[Numeric[A]].toDouble(first)
              val b = implicitly[Numeric[B]].toDouble(second)
              a > b
          }
          

          应该使用不同类型的数字:

          scala> gt(4, 4.0)
          res3: Boolean = false
          
          scala> gt(4, 3.0)
          res4: Boolean = true
          
          scala> gt(3L, 4.0)
          res5: Boolean = false
          

          【讨论】:

          • @wvxvw 2. 那么您可以为 biginteger/bigdecimal 提供一个特殊情况(以减轻一般的性能缺陷),或者从 BigInteger 的角度查看所有内容。两者都是不完美的解决方案,IMO。 1. 那是异类比较的代价。我认为可以编写减少转换次数的代码(隐式值类 + 双分派 ftw?),但它肯定会简洁。
          猜你喜欢
          • 2012-04-04
          • 2018-12-03
          • 2010-11-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-10
          • 1970-01-01
          • 2013-03-23
          相关资源
          最近更新 更多