Есть ли более простой способ добавить тире в строку?

У меня есть следующий код, который работает, но мне интересно, есть ли способ" groovier " сделать это:

/**
   * 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
  }
}

4 ответов


Не знаю, нравится ли мне это больше. Я бы сделал карту положения статическим финалом.

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("-")
}

С впрыскивать-с-2-аккумуляторами должно быть легко сделать версию список -- черточк-положений, слишком.


можно использовать [] строковый оператор, чтобы получить подстроки вместо 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 == 13 это то же самое, что и для length == 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? По крайней мере, для меня это не похоже на функцию, которая должна беспокоиться о ничтожности своего аргумента, или, по крайней мере, это не ясно, читая его имя (его следует называть чем-то вроде formatIsbnOrNull вместо этого, если принимаются как строки ISBN, так и значения null). Если значения 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++] }
}

попробуйте добавить метод в класс String, как показано здесь. Обратите внимание, что этот ответ является спином на умное предложение в ответе epidemian (re: collect).

Примечание:

этот код увеличивает строку с asIsbn().

диапазон [0..2] не требуется вызов asIsbn (), но симметрия использования collect дважды неотразимо.

Groovy возвращает последнее выражение в if/else, поэтому "возвращение" не обязательно

/**
 * 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

Я бы попробовал использовать Regex... Я думаю, что это довольно читабельно, если вы знаете, как использовать регулярное выражение, и это синтаксис, вдохновленный javascript в groovy, тоже довольно круто.

еще одна вещь: довольно ясно, глядя на группы захвата, как выглядит ваша строка для желаемого форматирования.

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 предложение с использованием backreferences отлично! Я думаю, что код будет выглядеть так:

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