[英]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.