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

#1楼 票数:5 已采纳

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

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

#2楼 票数: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)
}

(这个记忆代码来自这里

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

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”代码以返回默认值。

#3楼 票数: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))
  }
}

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

  ask by fredoverflow translate from so

未解决问题?本站智能推荐:

3回复

测试字符串的hashCode

我正在编写如下代码: 当我运行代码时,所有变量都打印相同的哈希码: 这是上述代码的正确输出吗?
1回复

相同的字符串,不同的哈希码

我正在编写一个简单的查找,但总是以某种方式失败。 进一步调查后,我发现了我无法解释的内容:似乎虽然我的字符串是相同的,但它们没有得到相同的哈希码。 重复Java 6的实验,我遇到了类似的问题。 请注意,无论哪种方式,事情都不是恒定的: 我的代码依赖于此匹配,并且我不想运行.e
2回复

短字符串的哈希码可以相同吗?

我有短String (少于10个字符)。 我将它转换为int并将其用作主键。 (由于小问题,我不能使用String主键。)我知道无限长度的字符串的哈希码可以冲突,但是短字符串也可以冲突吗?
1回复

为什么我从字符串中得到错误的HashCode?

我有这个结构: 这样填充: {1={1=2, 2=3, 3=4, -999=-999, -998=-998}} 从此代码: 我的HashMap是通用类型<String,Integer> 但是,每当我在msgContent上调用get方法时,都会得到一个哈
1回复

java字符串证明平等

上述代码的输出是 69609650 true 69609650 69609650 false 17351095 9318325 由于文字池s1和s2指向同一个对象,因此s1==s2为true。 s3是使用new关键字创建的String对象。因此s2==s3给出false。 我试图
5回复

打开以数字开头的双字符串

我正在尝试编写一段代码,该代码采用两位十六进制数字,例如“0C”,并将其与列表进行比较。 我正在使用Java 6,因此无法打开字符串,并且最初计划在Enums上使用开关,但没有意识到Enums必须以字母开头。 有没有一个简单的方法来实现类似下面的内容而没有一整套“if,else if
3回复

为什么字符串类基于给定的字符串值创建哈希码?

哈希码是每个对象的唯一值,但是为什么只有String类会创建这样的哈希码。 结果 但是这个程序给出了不同的哈希码值 结果:
2回复

如何使用字符串类型作为参数?

完成hack#1和#2的工作的正确方法是什么? 我无法弄清楚为什么convert2(“ 1​​”,String)会给出编译时错误(消息“对象java.lang.String不是值”),而convert2(“ 1​​”,Int)会编译并运行很好。 它与Int是Scala本机类型并且Str