Идиоматический «First Last» => «Последний, первый» в Java

#java #string #sorting #substring

#java #строка #сортировка #подстрока

Вопрос:

Я в основном программист на Python, немного изучаю Java. Мне нужна была функция для преобразования строк, содержащих имена в форме «First Last», в «Last, First» (мне также нужно это, чтобы иметь возможность обрабатывать отдельные имена: «Cher» => «Cher» и первое среднее последнее: «John F. Kennedy» => «Кеннеди, Джон Ф.»), и я начал думать о решении этого, как я бы сделал в Python:

 def tolastfirst(astring):
    parts = astring.split()
    return parts[-1]   ", "   " ".join(parts[:-1])
  

Нарезка строк и разделение списков и объединение настолько распространены и лаконичны в Python, что это было первое, что пришло на ум. Это перевело на Java что-то вроде этого:

 public static String toLastFirst(String firstLast) {
    List<String> parts = Arrays.asList(firstLast.split(" "));
    if (parts.size() > 1) {
        String last = parts.get(parts.size() - 1);
        List<String> frontParts = parts.subList(0, parts.size() - 1);
        String front = org.apache.commons.lang.StringUtils.join(frontParts.toArray(), " ");
        return last   ", "   front;
    } else {
        return firstLast;
    }
}
  

Так что это кажется излишне сложным, и, вероятно, из кода очевидно, что я не понимаю, почему и для чего используется массив по сравнению со списком. Преобразование из массива в список и обратно казалось глупым, поэтому я попробовал это:

 public static String toLastFirst(String firstLast) {
    String[] parts = firstLast.split(" ");
    if (parts.length > 1) {
        String last = parts[parts.length - 1];
        String[] frontParts = Arrays.copyOfRange(parts, 0, parts.length - 1);
        String front = org.apache.commons.lang.StringUtils.join(frontParts, " ");
        return last   ", "   front;
    } else {
        return firstLast;
    }
}
  

Наконец, я спросил коллегу, который действительно знает Java, и он предположил, что он, скорее всего, просто найдет последний пробел и разрежет строку, пропустив весь бизнес списка / массива:

 public static String toLastFirst(String firstLast) {
    if (firstLast.indexOf(" ") != -1) {
        Integer lastSpace = firstLast.lastIndexOf(" ");
        String first = firstLast.substring(0, lastSpace);
        String last = firstLast.substring(lastSpace, firstLast.length());
        return last   ", "   first;
    } else {
        return firstLast;
    }
}
  

Мои вопросы:

  • Правильно ли я интуитивно переключился с использования списка на массив?
  • Является ли предложение моего коллеги о прямом манипулировании строками наиболее идиоматичным способом решения этой проблемы в Java?
  • Является ли подход к обработке строк более эффективным (память, скорость?) чем подход списка / массива?

В основном меня интересует второй вопрос; Я хотел бы научиться писать Java как родной, а не как экспат Python.

Ответ №1:

Вы можете сделать это намного короче, используя replaceAll() :

 public static String toLastFirst(String firstLast) {
    return firstLast.replaceAll("(.*) (.*)", "$2, $1");
}
  

Это должно работать точно так же, как ваши реализации. Если строка содержит пробел, выражение будет совпадать, и поменяет местами первую и вторую части, и добавит , . Если он не содержит пробела, строка остается неизменной.

Комментарии:

1. Я отредактировал вопрос, чтобы отметить, что мой вариант использования включает в себя отдельные имена, такие как «Cher» => «Шер», а также имена со средним именем: «Филип Сеймур Хоффман» => «Хоффман, Филип Сеймур» … охватывает ли ваше решение эти случаи?

2. Изменение регулярного выражения на «(.*) (.*)» делает то, что мне здесь нужно. Прохладный.

3. @ben: Да, я как раз собирался это сказать. 🙂 Делая первую часть жадной, убедитесь, что единственный обмен сделан в последнем пространстве.

Ответ №2:

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

Прямая манипуляция — это то, как я бы решил это, поскольку у вас есть конкретный случай только из двух половин. ответ @ Keppil, вероятно, является самым аккуратным решением Java.

Прямое управление строкой будет немного быстрее, поскольку массив / список не нуждается в создании, инициализации, обновлении и т.д. Разница была бы крошечной в абсолютном выражении, хотя.

Ответ №3:

Вы можете сделать это следующим образом:

 public static String toLastFirst(String firstLast) {
    //split on one ore more space chars
    String[] data = firstLast.split("\s ");
    //need to check length of array etc.
    return data[1]   ", "   data[0];  //build new string
}
  

Комментарии:

1. Распространяется ли это на вариант использования «Филип Сеймур Хоффман» => «Хоффман, Филип Сеймур»?

2. Это очень похоже на мою вторую попытку, нет?

3. @benauthor — для решения этой проблемы рассмотрите возможность циклического перехода data от i<data.length-1 к i=0 , создавая StringBuilder объект, инициализированный с помощью data[data.length-1]