2015-03-30 48 views
14

我很好奇,想看看的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  

相比之下,斯卡拉似乎可以與所有情況進行比較:

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

我不認爲Java會一直這樣做,保證所有給出的字符串都有不同的哈希碼?這個檢查可能是在解釋器(JVM)中完成的,如果所有字符串都計算爲不同的散列值,則只選擇散列碼解決方案 – smac89 2015-03-30 20:12:19

+1

@ Smac89散列衝突根本沒有問題。 Java將簡單地跳轉到相同的地方,然後做2個字符串比較而不是1.另外,編譯器知道所有的字符串以及它們的所有hashcode。 JVM不需要動態分析情況。 – fredoverflow 2015-03-30 20:14:17

+0

我也很好奇......現在Java 8出來了,Scala仍然有用嗎? – 2015-03-30 20:21:09

回答

5

當然,這種情況似乎是缺乏來自Scala編譯器的優化。當然,match結構比Java中的開關/情況要強大得多,而且對它進行優化要困難得多,但是它可以檢測出這些特殊情況,在這些情況下可以應用簡單的散列比較。

另外,我不認爲這種情況會在慣用的Scala中多次顯示,因爲除了具有不同的值之外,您總是與具有一些含義的案例類匹配。

2

我認爲問題在於你從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) 
} 

(這memoizing代碼是從here採取

然後你就可以memoize的代碼是這樣的:!

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達到您願意緩存的數量,並且在代碼的情況下,您擁有一個小小的可能有效的案例數量和大量的無效案例,我會考慮製作一個預製地圖的普通類,並進行專門的查詢,說「在我的緩存中,你贏了,而不是在我的緩存中,默認值」,這可以做得很好可以通過調整備忘錄輕鬆地將輸入的List預緩存並更改「不進入緩存」代碼以返回默認值。

+0

是啊......或者我可以把字符串開關放在一個'.java'文件中並從Scala中使用它:) – fredoverflow 2015-03-30 20:40:04

+2

真誠的,和我一樣喜歡memoization,我不認爲這是一個很有價值的答案。案件... – 2015-03-30 21:07:53

2

這個問題激發了我學習Scala宏,我不妨分享我的解決方案。

這裏是我如何使用宏:

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

相關的值會自動累加。如果這不是你想要的,你可以改變實現來接受ArrowAssoc s,但這對我來說太複雜了。

這裏是宏是如何實現的:

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

請注意,此解決方案不處理哈希衝突。由於在我的實際問題中,我關心的特定字符串不會相互碰撞,所以我還沒有穿過那個特定的橋。