是否有一种Groovier方法将字符串添加到字符串?

时间:2012-07-06 00:17:43

标签: string groovy

我有以下代码,它有效,但我想知道是否有一种“更加常规”的方法:

/**
   * 10 digit - #-######-##-#
   * 13 digit - ###-#-######-##-#
   * */
private formatISBN(String isbn) {
  if (isbn?.length() == 10) {
    def part1 = isbn.substring(0, 1)
    def part2 = isbn.substring(1, 7)
    def part3 = isbn.substring(7, 9)
    def part4 = isbn.substring(9, 10)
    return "${part1}-${part2}-${part3}-${part4}"
  } else if (isbn?.length() == 13) {
    def part1 = isbn.substring(0, 3)
    def part2 = isbn.substring(3, 4)
    def part3 = isbn.substring(4, 10)
    def part4 = isbn.substring(10, 12)
    def part5 = isbn.substring(12, 13)
    return "${part1}-${part2}-${part3}-${part4}-${part5}"
  } else {
    return isbn
  }
}

5 个答案:

答案 0 :(得分:8)

您可以先使用[]字符串运算符来获取子字符串而不是substring并删除中间变量。例如,在length == 10

的情况下
"${isbn[0]}-${isbn[1..6]}-${isbn[7..8]}-${isbn[9]}"

现在,那里有一些重复。您可以首先获取所有isbn段,然后使用.join获取'-'段:

[isbn[0], isbn[1..6], isbn[7..8], isbn[9]].join('-')

而且,更进一步,您不必每次都引用isbn,而是可以列出您想要获得的范围,然后使用collect同时获取它们:

[0, 1..6, 7..8, 9].collect { isbn[it] }.join('-')

如果你打算打高尔夫球,你也可以这样做:

('-'+isbn)[1, 0, 2..7, 0, 8..9, 0, 10]

我会留给你弄清楚它是如何工作的,但我想将它留在生产代码上可能不是一个好主意,除非你想让未来的维护者感到惊讶。


另外,请注意length == 13length == 10相同但前缀不同的格式,然后您可以重复使用相同的函数。整个功能(有几个测试)将是:

/**
 * 10 digit - #-######-##-#
 * 13 digit - ###-#-######-##-#
 **/
def formatIsbn(isbn) {
    switch (isbn?.length()) {
        case 10: return [0, 1..6, 7..8, 9].collect { isbn[it] }.join('-')
        case 13: return isbn.take(3) + '-' + formatIsbn(isbn.drop(3))
        default: return isbn
    }
}

assert formatIsbn('abcdefghij') == 'a-bcdefg-hi-j'
assert formatIsbn('abcdefghijklm') == 'abc-d-efghij-kl-m'

现在,我认为该代码中有一些难闻的气味。 isbn可以null吗?至少对我来说,这看起来不像是一个需要打扰其论证的无效性的函数,或者至少通过阅读它的名称并不清楚(如果两个ISBN都应该被称为类似formatIsbnOrNull的函数接受字符串和空值)。如果null值无效,那么在访问NullPointerException时让它以isbn.length()爆炸,以便调用者知道他们传递了错误的参数,而不是静默返回相同的null。

最后return ISBN也是如此。是否希望该函数接收一个既不是10个也不是13个字符的字符串?如果不是,那就更好throw new IllegalArgumentException()并让来电者知道他们错误地调用了它。


最后,我不确定这是否是最“可读”的解决方案。另一种可能的解决方案是使用格式的字符串,例如'###-#-######-##-#',然后用#字符替换isbn。我认为它可能更具自我记录性:

def formatIsbn(isbn) {
    def format = [
        10: '#-######-##-#',
        13: '###-#-######-##-#'
    ][isbn.length()]
    def n = 0
    format.replaceAll(/#/) { isbn[n++] }
}

答案 1 :(得分:4)

考虑将方法添加到String类,如下所示。请注意,这个答案是关于流行病学家的答案(re:collect)的一个聪明建议。

注意:

此代码使用asIsbn()扩充String。

范围[0..2]不需要调用asIsbn(),但两次使用collect的对称性是不可抗拒的。

Groovy返回if/else中的最后一个表达式,因此不需要'return'

/**
 * 10 digit - #-######-##-#
 * 13 digit - ###-#-######-##-#
 **/
String.metaClass.asIsbn = { ->
    if (delegate.length() == 10) {
        [0, 1..6, 7..8, 9].collect { delegate[it] }.join('-')
    } else if (delegate.length() == 13) {
        [0..2, 3..12].collect { delegate[it].asIsbn() }.join('-')
    } else {
        delegate
    }
}

assert "abcdefghij".asIsbn() == 'a-bcdefg-hi-j'
assert "abcdefghijklm".asIsbn() == 'abc-d-efghij-kl-m'
assert "def".asIsbn() == "def"
String s = null 
assert s?.asIsbn() == null

答案 2 :(得分:3)

我会尝试使用Regex ...我认为如果您知道如何使用正则表达式,它几乎可读,而且它在groovy中的javascript启发语法也非常酷。

还有一件事:非常清楚,看看捕获组,你的字符串对于所需的格式是什么样的。

private formatISBN(String isbn) {
    if (isbn?.length() == 10) {
        m = isbn =~ /(\d{1})(\d{6})(\d{2})(\d{1})/
        return "${m[0][1]}-${m[0][2]}-${m[0][3]}-${m[0][4]}"
    } else if (isbn?.length() == 13) {
        m = isbn =~ /(\d{3})(\d{1})(\d{6})(\d{2})(\d{1})/
        return "${m[0][1]}-${m[0][2]}-${m[0][3]}-${m[0][4]}-${m[0][5]}"        
    } else {
        return isbn
    }
}

顺便说一下,@ epidemian使用反向引用的建议很棒!我认为代码看起来像:

private formatISBN(String isbn) {
    if (isbn?.length() == 10) {
        return isbn.replaceAll(/(\d{1})(\d{6})(\d{2})(\d{1})/, '$1-$2-$3-$4')
    } else if (isbn?.length() == 13) {
        return isbn.replaceAll(/(\d{3})(\d{1})(\d{6})(\d{2})(\d{1})/, '$1-$2-$3-$4-$5')
    } else {
        return isbn
    }
}

答案 3 :(得分:2)

如果我更喜欢这个,那就不知道。我也将位置图定为静态决赛。

private isbnify(String isbn) {
  def dashesAt = [ 10: [[0,1], [1,7], [7,9],  [9,10]],
                   13: [[0,3], [3,4], [4,10], [10,12], [12,13]]]
  def dashes = dashesAt[isbn?.length()]
  (dashes == null) ? isbn 
                   : dashes.collect { isbn.substring(*it) }.join('-')
}

范围使杂乱少一点,IMO:

private isbnify3(String isbn) {
  def dashesAt = [ 10: [0, 1..6, 7..8, 9],
                   13: [0..2, 3, 4..9, 10..11, 12]]
  def dashes = dashesAt[isbn?.length()]
  dashes == null ? isbn : dashes.collect { isbn[it] }.join("-")
}

使用两个注入式累加器,也很容易做一个破折号位置版本。

答案 4 :(得分:0)

这应该是@everton的评论,但我还没有做到这一点所需的50个声誉。因此,此答案实际上只是@everton答案的建议变体。

通过使前3位为可选来减少正则表达式。缺点是,如果ISBN为10个字符,则必须删除开头的“-”。 (与\d相比,我更喜欢\d{1}。)

private formatISBN(String isbn) {
  String result = isbn.replaceAll(/^(\d{3})?(\d)(\d{6})(\d{2})(\d)$/,
                                  '$1-$2-$3-$4-$5')
  if (result) {
    return result.startsWith('-') ? result[1..-1] : result
  } else {
    return isbn // return value unchanged, pattern didn't match
  }
}

println formatISBN('1234567890')
println formatISBN('9991234567890')
println formatISBN('123456789') // test an ISBN that's too short
println formatISBN('12345678901234') // test an ISBN that's too long