繁体   English   中英

打开字符串

[英]Switching on Strings

我很想知道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个可能的关键字进行比较。

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

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

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

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

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)
}

(这个记忆代码来自这里

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

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示例(包括这一个)都是玩具,并且无法在大多数用例中存活。 真实世界的记忆应该包括你愿意缓存的数量的上限 ,如果你的代码中有少量可能的有效案例和大量的无效案例,我会考虑制作一个预先构建地图的类,并有一个专门的查找,说“在我的缓存中,你赢了,而不是在我的缓存中,默认。” 这可以很容易地通过调整memoizer来获取预先缓存的输入List并更改“not-in-cache”代码以返回默认值。

这个问题激发了我对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))
  }
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM