我很想知道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个可能的关键字进行比较。
答案 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))
}
}
请注意,此解决方案不处理哈希冲突。由于我在实际问题中关心的特定字符串不会发生碰撞,所以我还没有跨过那个特定的桥。