加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

在Scala中用占位符替换字符串中的值

发布时间:2020-12-16 09:20:27 所属栏目:安全 来源:网络整理
导读:我刚刚开始使用Scala,并希望更好地了解解决问题的功能方法. 我有一对字符串,第一个有参数的占位符,它的对有值替换.例如 “从tab1选择col1,其中id $1,名称为$2” “参数:$1 =’250′,$2 =’some%’” 可能有两个以上的参数. 我可以通过遍历并在每行上使用re
我刚刚开始使用Scala,并希望更好地了解解决问题的功能方法.
我有一对字符串,第一个有参数的占位符,它的对有值替换.例如
“从tab1选择col1,其中id> $1,名称为$2”
“参数:$1 =’250′,$2 =’some%’”

可能有两个以上的参数.

我可以通过遍历并在每行上使用regex.findAllIn(line)来构建正确的字符串,然后通过迭代器来构造替代,但这似乎相当不合理和程序化的驱动.

任何人都可以指出一种功能性的方法,它会更加干净,更容易出错.

解决方法

严格来说,替换问题,我的首选解决方案是一个功能,可以在即将到来的Scala 2.8中提供,这是使用功能来替换正则表达式模式的功能.使用它,可以将问题简化为:

def replaceRegex(input: String,values: IndexedSeq[String]) =  
  """$(d+)""".r.replaceAllMatchesIn(input,{
    case Regex.Groups(index) => values(index.toInt)
  })

这将问题减少到您实际想要做的事情:将所有$N模式替换为列表的相应第N个值.

或者,如果您可以设置输入字符串的标准,则可以这样做:

"select col1 from tab1 where id > %1$s and name like %2$s" format ("one","two")

如果这是你想要的,你可以在这里停下来.但是,如果您有兴趣如何以功能性方式解决这些问题,则缺少聪明的图书馆功能,请继续阅读.

功能上的思考意味着思考功能.你有一个字符串,一些值,你想要一个字符串.在静态类型的功能语言中,这意味着你想要这样的东西:

(String,List[String]) => String

如果认为这些价值观可以按任何顺序使用,我们可能会要求更适合的类型:

(String,IndexedSeq[String]) => String

这对我们的功能应该是足够好的.现在,我们如何分解工作?有几种标准的做法:递归,理解,折叠.

递推

我们从递归开始吧.递归意味着将问题分成第一步,然后在剩余的数据上重复.对我来说,这里最明显的部分是:

>替换第一个占位符
>与剩余的占位符重复

这实际上很简单,所以让我们进一步细节.如何替换第一个占位符?我无法避免的一件事是,我需要知道这个占位符是什么,因为我需要从中获取索引.所以我需要找到它:

(String,Pattern) => String

一旦找到,我可以替换它的字符串,并重复:

val stringPattern = "$(d+)"
val regexPattern = stringPattern.r
def replaceRecursive(input: String,values: IndexedSeq[String]): String = regexPattern findFirstIn input match {
  case regexPattern(index) => replaceRecursive(input replaceFirst (stringPattern,values(index.toInt)))
  case _ => input // no placeholder found,finished
}

这是低效的,因为它反复产生新的字符串,而不是连接每个部分.让我们试一试更聪明.

要通过连接有效地构建一个字符串,我们需要使用StringBuilder.我们也想避免创建新的字符串. StringBuilder可以接受CharSequence,我们可以从String获取.我不知道是否创建了一个新的字符串 – 如果是,我们可以以一种视图的形式滚动我们自己的CharSequence,而不是创建一个新的String.确保我们可以轻松地改变这个,如果需要,我会继续假设它不是.

所以,让我们考虑一下我们需要的功能.当然,我们需要一个将索引返回到第一个占位符的函数:

String => Int

但是我们也想跳过我们已经看过的字符串的任何部分.这意味着我们也想要一个起始索引:

(String,Int) => Int

但是有一个小细节.如果有进一步的占位符怎么办?那么就不会有任何索引返回. Java重用索引以返回该异常.当进行功能编程时,总是最好返回你的意思.我们的意思是我们可能会返回一个索引,否则我们可能不会.这个签名是这样的:

(String,Int) => Option[Int]

我们来构建这个功能:

def indexOfPlaceholder(input: String,start: Int): Option[Int] = if (start < input.lengt) {
  input indexOf ("$",start) match {
    case -1 => None
    case index => 
      if (index + 1 < input.length && input(index + 1).isDigit)
        Some(index)
      else
        indexOfPlaceholder(input,index + 1)
  }
} else {
  None
}

这是相当复杂的,主要是处理边界条件,如索引超出范围,或者在寻找占位符时出现误报.

要跳过占位符,我们还需要知道它的长度,签名(String,Int)=>诠释:

def placeholderLength(input: String,start: Int): Int = {
  def recurse(pos: Int): Int = if (pos < input.length && input(pos).isDigit)
    recurse(pos + 1)
  else
    pos
  recurse(start + 1) - start  // start + 1 skips the "$" sign
}

接下来,我们也想知道占位符所代表的价值的指标.这个签名有点不明确:

(String,Int) => Int

第一个Int是输入的索引,而第二个是值的索引.我们可以做一些事情,但不是那么容易或有效率,所以让我们忽略它.这是一个实现:

def indexOfValue(input: String,start: Int): Int = {
  def recurse(pos: Int,acc: Int): Int = if (pos < input.length && input(pos).isDigit)
    recurse(pos + 1,acc * 10 + input(pos).asDigit)
  else
    acc
  recurse(start + 1,0) // start + 1 skips "$"
}

我们也可以使用长度,并实现一个更简单的实现:

def indexOfValue2(input: String,start: Int,length: Int): Int = if (length > 0) {
  input(start + length - 1).asDigit + 10 * indexOfValue2(input,start,length - 1)
} else {
  0
}

作为一个注释,使用诸如上述的简单表达式的大括号围绕着传统的Scala风格,但我在这里使用它,因此可以很容易地粘贴在REPL上.

所以我们可以得到下一个占位符的索引,它的长度和值的索引.几乎所有需要更高效的replaceRecursive版本的一切:

def replaceRecursive2(input: String,values: IndexedSeq[String]): String = {
  val sb = new StringBuilder(input.length)
  def recurse(start: Int): String = if (start < input.length) {
    indexOfPlaceholder(input,start) match {
      case Some(placeholderIndex) =>
        val placeholderLength = placeholderLength(input,placeholderIndex)
        sb.append(input subSequence (start,placeholderIndex))
        sb.append(values(indexOfValue(input,placeholderIndex)))
        recurse(start + placeholderIndex + placeholderLength)
      case None => sb.toString
    }
  } else {
    sb.toString
  }
  recurse(0)
}

效率更高,功能也可以使用StringBuilder.

理解

在最基本的层次上,理解意味着将T [A]转换为T [B],给出函数A =>这是一个monad的东西,但它可以很容易地了解,当涉及到收藏.例如,我可以通过函数String =>将名称的List [String]转换为名称长度的List [Int] Int返回字符串的长度.这是列表的理解.

还有其他可以通过理解完成的操作,给定具有签名的函数A => T [B]或A =>布尔.

这意味着我们需要将输入字符串看作T [A].我们不能使用Array [Char]作为输入,因为我们要替换大于单个char的整个占位符.因此,我们建议这种类型的签名:

(List[String],String => String) => String

由于我们收到的输入是String,我们需要一个函数String =>首先列出[String],将我们的输入划分为占位符和非占位符.我建议:

val regexPattern2 = """((?:[^$]+|$(?!d))+)|($d+)""".r
def tokenize(input: String): List[String] = regexPattern2.findAllIn(input).toList

另一个问题是我们得到一个IndexedSeq [String],但是我们需要一个String =>串.有很多方法,但我们来解决这个问题:

def valuesMatcher(values: IndexedSeq[String]): String => String = (input: String) => values(input.substring(1).toInt - 1)

我们还需要一个函数List [String] => String,但是List的mkString已经这样做了.所以没有什么可以做,除了组成所有这些东西:

def comprehension(input: List[String],matcher: String => String) = 
  for (token <- input) yield (token: @unchecked) match {
    case regexPattern2(_,placeholder: String) => matcher(placeholder)
    case regexPattern2(other: String,_) => other
  }

我使用@unchecked,因为除了这两个以外,如果我的正则表达式正确构建,不应该有任何模式.编译器不知道,然而,所以我使用该注释来沉默它会产生的警告.如果抛出异常,则在正则表达式中有一个错误.

那么最终的功能就是统一所有的:

def replaceComprehension(input: String,values: IndexedSeq[String]) =
  comprehension(tokenize(input),valuesMatcher(values)).mkString

此解决方案的一个问题是我应用正则表达式模式两次:一次分解字符串,另一个用于标识占位符.另一个问题是令牌列表是不必要的中间结果.我们可以通过这些改变来解决这个问题:

def tokenize2(input: String): Iterator[List[String]] = regexPattern2.findAllIn(input).matchData.map(_.subgroups)

def comprehension2(input: Iterator[List[String]],matcher: String => String) = 
  for (token <- input) yield (token: @unchecked) match {
    case List(_,placeholder: String) => matcher(placeholder)
    case List(other: String,_) => other
  }

def replaceComprehension2(input: String,values: IndexedSeq[String]) =
  comprehension2(tokenize2(input),valuesMatcher(values)).mkString

折叠

折叠与递归和理解有点类似.折叠后,我们可以看到可以理解的T [A]输入,B“种子”和函数(B,A)=>我们使用该函数来理解列表,总是从处理最后一个元素(第一个元素获取种子)得到的B.最后,我们返回上一个理解元素的结果.

我承认,我几乎不能以较不模糊的方式解释它.当您尝试保持抽象时,会发生什么.我这样解释,所以涉及的类型签名变得清晰.但是让我们看一个简单的折叠示例来了解它的用法:

def factorial(n: Int) = {
  val input = 2 to n
  val seed = 1
  val function = (b: Int,a: Int) => b * a
  input.foldLeft(seed)(function)
}

或者,作为单行:

def factorial2(n: Int) = (2 to n).foldLeft(1)(_ * _)

好的,我们如何解决折叠问题?结果当然应该是我们要生产的字符串.因此,种子应该是一个空字符串.我们使用tokenize2的结果作为可理解的输入,并且这样做:

def replaceFolding(input: String,values: IndexedSeq[String]) = {
  val seed = new StringBuilder(input.length)
  val matcher = valuesMatcher(values)
  val foldingFunction = (sb: StringBuilder,token: List[String]) => {
    token match {          
      case List(_,placeholder: String) => sb.append(matcher(placeholder))
      case List(other: String,_) => sb.append(other)
    }
    sb
  }
  tokenize2(input).foldLeft(seed)(foldingFunction).toString
}

而且,通过这个功能,我完成了最常见的方式.我已经诉诸于StringBuilder,因为String的连接速度很慢.如果不是这样,我可以很容易地用String替换StringBuilder.我也可以将Iterator转换成Stream,并完全消除可变性.

这是Scala,而Scala是关于平衡需求和手段,而不是纯粹的解决方案.虽然你当然可以自由去纯粹主义.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读