打开字符串

时间:2015-03-30 20:07:32

标签: java string scala switch-statement hashcode

我很想知道Java和Scala如何在字符串上实现切换:

class Java
{
    public static int java(String s)
    {
        switch (s)
        {
        case "foo": return 1;
        case "bar": return 2;
        case "baz": return 3;
        default: return 42;
        }
    }
}
object Scala {
  def scala(s: String): Int = {
    s match {
      case "foo" => 1
      case "bar" => 2
      case "baz" => 3
      case _ => 42
    }
  }
}

似乎Java会在哈希码上切换,然后进行单个字符串比较:

 0: aload_0       
 1: dup           
 2: astore_1      
 3: invokevirtual #16    // Method java/lang/String.hashCode:()I
 6: lookupswitch  { // 3
           97299: 40
           97307: 52
          101574: 64
         default: 82
    }
40: aload_1       
41: ldc           #22    // String bar
43: invokevirtual #24    // Method java/lang/String.equals:(Ljava/lang/Object;)Z
46: ifne          78
49: goto          82
52: aload_1       
53: ldc           #28    // String baz
55: invokevirtual #24    // Method java/lang/String.equals:(Ljava/lang/Object;)Z
58: ifne          80
61: goto          82
64: aload_1       
65: ldc           #30    // String foo
67: invokevirtual #24    // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifne          76
73: goto          82
76: iconst_1      
77: ireturn       
78: iconst_2      
79: ireturn       
80: iconst_3      
81: ireturn       
82: bipush        42
84: ireturn       

相比之下,Scala似乎与所有案例进行比较:

 0: aload_1       
 1: astore_2      
 2: ldc           #16    // String foo
 4: aload_2       
 5: invokevirtual #20    // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
 8: ifeq          16
11: iconst_1      
12: istore_3      
13: goto          47
16: ldc           #22    // String bar
18: aload_2       
19: invokevirtual #20    // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
22: ifeq          30
25: iconst_2      
26: istore_3      
27: goto          47
30: ldc           #24    // String baz
32: aload_2       
33: invokevirtual #20    // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
36: ifeq          44
39: iconst_3      
40: istore_3      
41: goto          47
44: bipush        42
46: istore_3      
47: iload_3       
48: ireturn       

是否有可能说服Scala使用哈希码技巧?我宁愿选择O(1)解决方案来解决O(n)问题。在我的真实代码中,我需要与33个可能的关键字进行比较。

3 个答案:

答案 0 :(得分:5)

看来这个案例似乎是Scala编译器缺乏优化。当然,match构造比Java中的switch / case强大得多(并且非常强大),并且对它进行优化要困难得多,但它可以检测到这些特殊情况,其中将应用简单的哈希比较。

此外,我不认为这个案例会在惯用的Scala中多次出现,因为除了具有不同的价值之外,你总是与具有某种意义的案例类相匹配。

答案 1 :(得分:2)

我认为问题在于你从Java的角度思考Scala(我认为你还过早地优化了,但是嘿)。

我认为您想要的解决方案是记住您的映射。 你有一个从String映射的函数 - >对,对吗?所以这样做:

class Memoize1[-T, +R](f: T => R) extends (T => R) {
  import scala.collection.mutable
  private[this] val vals = mutable.Map.empty[T, R]

  def apply(x: T): R = {
    if (vals.contains(x)) {
      vals(x)
    }
    else {
      val y = f(x)
      vals += ((x, y))
      y
    }
  }
}

object Memoize1 {
  def apply[T, R](f: T => R) = new Memoize1(f)
}

(此记忆代码取自here

然后你可以像这样记住你的代码:

object Scala {
  def scala(s: String): Int = {
    s match {
      case "foo" => 1
      case "bar" => 2
      case "baz" => 3
      case _ => 42
    }
  }

  val memoed = Memoize1(Scala.scala)

  val n = memoed("foo")
}

多田!现在,您正在进行哈希值比较。虽然我会补充一点,大多数的memoization示例(包括这一个)都是玩具,并且无法在大多数用例中存活。真实世界memoization should include an upper limit到您愿意缓存的金额,如果您的代码中存在少量可能的有效案例和大量无效案例,我会考虑制作一个预先构建地图的一般类,并且具有一个专门的查找,在我的缓存中说,"你赢了,而不是在我的缓存中,默认。"这可以通过调整memoizer来获取List输入到预先缓存并更改" not-in-cache"代码返回默认值。

答案 2 :(得分:2)

这个问题激励我学习Scala宏,我也可以分享我的解决方案。

以下是我使用宏的方法:

switch(s, 42, "foo", "bar", "baz")

相关值会自动计算。如果这不是您想要的,您可以将实现更改为接受ArrowAssoc,但这对我来说太复杂了。

以下是宏的实现方式:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
import scala.collection.mutable.ListBuffer

object StringSwitch {

  def switch(value: String, default: Long, cases: String*): Long =
    macro switchImpl

  def switchImpl(c: Context)(value: c.Expr[String], default: c.Expr[Long],
                             cases: c.Expr[String]*): c.Expr[Long] = {
    import c.universe._

    val buf = new ListBuffer[CaseDef]
    var i = 0
    for (x <- cases) {
      x match {
        case Expr(Literal(Constant(y))) =>
          i += 1
          buf += cq"${y.hashCode} => if ($x.equals($value)) $i else $default"

        case _ => throw new AssertionError("string literal expected")
      }
    }
    buf += cq"_ => $default"

    c.Expr(Match(q"$value.hashCode", buf.toList))
  }
}

请注意,此解决方案不处理哈希冲突。由于我在实际问题中关心的特定字符串不会发生碰撞,所以我还没有跨过那个特定的桥。