Scala字符串列表校验:仅含AA/BB前缀且需同时存在、排除CC
列表校验逻辑的优化方案
需求:校验一个String列表,要求必须同时包含以AA和BB开头的字符串,且列表里只能有AA/BB开头的元素,不能出现CC开头或其他不符合前缀的元素。以下是能正常运行的原实现,想知道有没有更优的写法?
原实现代码
private final val ValidPatternA: String = "^AA.*" private final val ValidPatternB: String = "^BB.*" private final val InvalidPatternC: String = "^CC.*" def main(args: Array[String]): Unit = { println(isValid(Seq())) // false println(isValid(Seq("AA0"))) // false println(isValid(Seq("BB1"))) // false println(isValid(Seq("CC2"))) // false println(isValid(Seq("AA0", "BB1", "CC2"))) // false println(isValid(Seq("AA0", "CC2"))) // false println(isValid(Seq("BB1", "CC2"))) // false println(isValid(Seq("AA0", "BB1"))) // true } private def isValid(listOfString: Seq[String]) = !listOfString.exists(_.matches(InvalidPatternC)) && listOfString.exists(_.matches(ValidPatternA)) && listOfString.exists(_.matches(ValidPatternB))
原代码的问题
- 逻辑漏洞:没校验所有元素都是AA/BB开头,比如
Seq("AA0", "BB1", "DD3")会被误判为有效 - 效率浪费:三次调用
exists会遍历列表三次 - 正则冗余:前缀匹配用正则有点大材小用,不如直接用
startsWith高效
优化后的实现
方案1:命令式风格(高效且直观)
一次遍历完成所有校验,遇到无效元素直接提前返回:
def main(args: Array[String]): Unit = { println(isValid(Seq())) // false println(isValid(Seq("AA0"))) // false println(isValid(Seq("BB1"))) // false println(isValid(Seq("CC2"))) // false println(isValid(Seq("AA0", "BB1", "CC2"))) // false println(isValid(Seq("AA0", "CC2"))) // false println(isValid(Seq("BB1", "CC2"))) // false println(isValid(Seq("AA0", "BB1"))) // true println(isValid(Seq("AA0", "BB1", "DD3"))) // false // 修复原代码的漏洞 } private def isValid(listOfString: Seq[String]): Boolean = { if (listOfString.isEmpty) return false var hasAA = false var hasBB = false for (s <- listOfString) { val isAA = s.startsWith("AA") val isBB = s.startsWith("BB") // 遇到CC直接无效 if (s.startsWith("CC")) return false // 既不是AA也不是BB,直接无效 if (!isAA && !isBB) return false if (isAA) hasAA = true if (isBB) hasBB = true } // 必须同时存在AA和BB hasAA && hasBB }
方案2:函数式风格(无变量)
如果偏好纯函数式写法,用foldLeft累积状态:
private def isValid(listOfString: Seq[String]): Boolean = { val (hasAA, hasBB, hasInvalid) = listOfString.foldLeft((false, false, false)) { case ((a, b, invalid), s) => if (invalid) (a, b, true) // 已有无效元素,直接传递状态 else if (s.startsWith("CC")) (a, b, true) else { val newA = a || s.startsWith("AA") val newB = b || s.startsWith("BB") val newInvalid = !(s.startsWith("AA") || s.startsWith("BB")) (newA, newB, newInvalid) } } // 非空、无无效元素、同时有AA和BB listOfString.nonEmpty && !hasInvalid && hasAA && hasBB }
优化点总结
- 性能提升:一次遍历替代三次遍历,
startsWith比正则前缀匹配更快 - 逻辑严谨:补上了原代码未校验非AA/BB元素的漏洞
- 提前终止:遇到无效元素立刻返回,不用遍历完整个列表
- 代码可读性:逻辑直白,比正则更容易理解
内容的提问来源于stack exchange,提问作者Wonderboy
相关产品推荐
相关产品推荐

