在Scala中执行字符串的条件拼接最惯用的方法是什么?

9

我很好奇什么是通过串联文本块来构建字符串值的最佳方式,如果其中一些块动态依赖于外部条件。解决方案应该符合Scala惯用法,没有太多的速度和内存惩罚。

例如,如何在Scala中重写以下Java方法?

public String test(boolean b) {
    StringBuilder s = new StringBuilder();
    s.append("a").append(1);
    if (b) {
        s.append("b").append(2);
    }
    s.append("c").append(3);
    return s.toString();
}

3
除了return语句等内容之外,我认为对于Scala来说这段代码相当符合惯用写法(而且易于理解代码流程)。 - om-nom-nom
我期望的是将Any类型的列表与可选成分串联起来,将它们展开并在结果上调用mkString。 - Jiří Vypědřík
有没有人能够提供类似以下代码的简化版本:(Some("a" :: 1 :: Nil) :: (if (b) Some("b" :: 2 :: Nil) else None) :: Some("c" :: 3 :: Nil) :: Nil).flatten.flatten.mkString - Jiří Vypědřík
5个回答

13

由于Scala既具有函数式特性又具备命令式特性,所以“惯用的”这个术语取决于您喜欢遵循哪种范例。您已经按照命令式范例解决了问题。以下是一种您可以使用函数式方式解决问题的方法:

def test( b : Boolean ) =
  "a1" + 
  ( if( b ) "b2" else "" ) +
  "c3"

5
实际上,这也使用了一个单一的 StringBuilder 来提高性能。从字节码的角度来看,这与您的Java代码几乎完全相同。 - gourlaysama
虽然这样做显然是可行的,但感觉你牺牲了一些可读性。 - Russell

9
这些天,成语化意味着字符串插值。
s"a1${if(b) "b2" else ""}c3"

你甚至可以嵌套使用字符串插值:

s"a1${if(b) s"$someMethod" else ""}"

如果 b 为 false,这似乎不起作用,它需要一个 else 或者否则它会打印“()”。 - jturcotte

5

将字符串函数的不同组件分别拆分出来是否可行?它们需要做出决策,这已足够成为一个函数的责任了。

def test(flag: Boolean) = {
  def a = "a1"
  def b = if (flag) "b2" else ""
  def c = "c3" 
  a + b + c
}

这样做的额外好处是,它清晰地分解了最终字符串的不同组成部分,同时在高层次上概述了它们如何相互配合,没有任何其他障碍物。

@collapsar 不确定为什么您编辑答案时在函数名称前添加下划线 - 但我已将它们删除,因为我看不到任何值得使代码变得丑陋的好处。如果您有理由,我们很乐意讨论。 - Russell
啊,看到b被使用了两次。我刚刚改变了布尔值的名称,而不是在所有地方使用下划线,因为我认为这样会让代码更加清晰。 - Russell
为什么不将部分定义为val? - om-nom-nom
没有什么特别的原因,我认为这是因为我主要使用 Ruby 进行编写,我习惯于使用一个类以及许多小方法完成这个功能。 - Russell
我也倾向于不使用局部赋值,因为它们不太实用,并且强制进行您可能不需要的评估。 - Russell

2
正如@om-nom-nom所说,您的代码已经足够符合惯用语。
def test(b: Boolean): String = {
  val sb = new StringBuilder
  sb.append("a").append(1)
  if (b) sb.append("b").append(2)
  sb.append("c").append(3)
  sb.toString
}

我可以提供一种替代版本,但它不一定更有效或“scala风格”

def test2(b: Boolean): String = "%s%d%s%s%s%d".format(
  "a", 
  1,
  if (b) "b" else "",
  if (b) 2 else "",
  "c",
  3)

1
使用scalaz: (Seq("a", 1) ++ b.option("b") ++ b.option(2) ++ Seq("c", 3)).mkString - senia
2
@senia Scalaz很棒,但对于这个特定的任务来说(以及其他一些提出的答案),它似乎是一个过度工程化的简单想法。我认为我们不应该仅仅因为我们能够而使事情变得复杂。 - om-nom-nom
@om-nom-nom:在我看来,这似乎比“if .. else”好一点。 - senia
@senia 为什么?我认为大多数读者更容易解析 if ... else,而且在性能方面,构建多个选项,然后从中构建序列等并不比它更好,最后,你并没有在任何重要程度上使用选项兼容性。但也许我漏掉了什么? - om-nom-nom
@om-nom-nom:我认为当else分支中没有有用的数据时,这是最声明式的方法。没有elseif是一种命令式风格。快速但冗长且具有可变状态。带有无用空字符串的if不是一种声明性方式-为什么我们要将""放入我们的集合中?if (...) Some(...) else None是声明性的但冗长的方式。b.option(...)if .... else None的缩写版本。 - senia

2
在Scala中,字符串可以被视为字符序列。因此,解决您的问题的惯用函数式方法是使用map
"abc".map( c => c match {
  case 'a' => "a1"
  case 'b' => if(b) "b2" else ""
  case 'c' => "c3"
  case _ =>
}).mkString("")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接