Обработка строки с фамилиями и инициалами

rr33rr

Подскажите оптимальное решение такой задачи:

Из строки вида ‘Фамилия И.О., И.О. Фамилия, Surname Fr. P.’
Т.е. из списка фамилий с инициалами (допустимы русские и англ. символы), через запятую.
В исходной строке инициалы могут быть до и после фамилии
Инициалы могут быть отделены от фамилий и друг от друга неизвестным числом пробелов (0 или более)
Инициалов может быть 1 или 2
Длина инициалов неизвестна (т.е. они могут состоять из 1 и более символов)
Нужно получить массив из строк вида ‘Фамилия И.О.’ или ‘Фамилия И.’ (если инициал только один)
Т.е. первой идет фамилия, а за ней через 1 пробел инициал(ы)
После чего отсортировать результат по алфавиту
Т.е. из примерно такой исходной строки:
$authors = «Шишкин И.И.,Иванов А.Б., Петров-Водкин К. С. ,В.А.Сидоров-Ивановский, M.Yu. Ivanov, D. Vrel, Dash Jh. B.»;
должно получиться так:
‘Dash Jh.B.’
‘Ivanov M.Yu.’
‘Vrel D.’
‘Иванов А.Б.’
‘Петров-Водкин К.С.’
‘Сидоров-Ивановский В.А.’
‘Шишкин И.И.’

Ниже попытка ее решить. Вроде нужный результат достигнут. НО т.к. я — новичок в PHP и всяких регулярках, 100% код не оптимальный и наверняка можно лучше и проще. ))

Код (Text):
  1. <?php
  2. //Функция для сортировки
  3. function cmp($a, $b) {
  4.     return strcmp($a, $b);
  5. }
  6. //Исходная строка
  7. $authors = «Шишкин И.И.,Иванов А.Б.,  Петров-Водкин К.  С. ,В.А.Сидоров-Ивановский, M.Yu. Ivanov, D.     Vrel, Dash Jh. B.»;
  8.  
  9. $author = explode(‘,’, $authors);
  10. echo «<b>Input:</b>
  11. <pre>n»;
  12. echo «‘», $authors, «‘n»;
  13. echo «</pre>n»;
  14. $total = count($author);
  15. for ($i = 0; $i < $total; $i++) {
  16.     //убираем ведущие и завершающие пробелы
  17.     $author[$i] = trim($author[$i]);
  18.     //вырезаем подстроку до последнего вхождения точки
  19.     $tmp = preg_replace(‘/[^.]+$/u’, », $author[$i]);
  20.     //если полученная подстрока не совпадает с исходной, т.е. инициалы шли до фамилии
  21.     if ($author[$i] != $tmp){
  22.         //то удаляем инициалы из исходной строки
  23.         $author[$i] = trim(str_replace($tmp, », $author[$i]));
  24.         //убираем все пробелы из инициалов
  25.         $tmp = preg_replace(‘/s+/’,»,$tmp);
  26.         //дописываем инициалы через пробел после фамилии
  27.         $author[$i] = $author[$i] . » » . $tmp;
  28.     }
  29.     //если инициалы шли после фамилии
  30.     else {
  31.         //вырезаем подстроку с первого пробела
  32.         $tmp = substr($author[$i], strpos($author[$i],’ ‘)+1, strlen($author[$i]));
  33.         // до последней точки включительно
  34.         $tmp = substr($tmp, 0, strrpos($tmp,’.’)+1);
  35.         //удаляем инициалы из ФИО и убираем ведущие и завершающие пробелы
  36.         $author[$i] = trim(str_replace($tmp, », $author[$i]));
  37.         //убираем пробелы из инициалов
  38.         $tmp = preg_replace(‘/s+/’,»,$tmp);
  39.         //дописываем полученные инициалы через пробел после фамилии
  40.         $author[$i] = $author[$i] . » » . $tmp;
  41.     }
  42. }
  43.  
  44. //Сортируем результат
  45. usort($author, ‘cmp’);
  46. echo «<b>Output:</b>n<pre>n»;
  47. foreach ($author as $a) {
  48.     echo «‘», $a, «‘n»;
  49. }
  50. echo «</pre>n»;
  51. ?>

Оно же в php-песочнице: https://sandbox.onlinephpfunctions.com/code/304950b353c471999ee355359ddc3da6c0d1b438

 

Drunkenmunky

Можно и проще.

PHP:
  1. <pre>
  2. <?php
  3. $authors = «Шишкин И.И.,И Андрей,   ,Сан-Антонио,Петров-Водкин К. С. ,Вл.А.Сидоров-Ивановский, M.Yu. Ivanov, D. Vrel, Dash Jh. B.»;
  4.  
  5. $array = explode(‘,’, $authors);
  6.  
  7. $names = array();
  8.  
  9. foreach($array as $name)
  10. {
  11. $values = preg_split(«/[s.]+/», trim($name, ‘. ‘));
  12. $count = count($values);
  13.  
  14. if(!isset($values[1]))
  15. {
  16.    if(!empty($values[0]))
  17.    $names[] = $values[0];
  18. }
  19. elseif(strlen($values[0]) > strlen($values[$count1]))
  20. {
  21.   $lastname = array_shift($values);
  22.   $names[] = sprintf(‘%s %s.’, $lastname, join(‘. ‘, $values));
  23. }
  24. else
  25. {
  26.   $lastname = array_pop($values);
  27.   $names[] = sprintf(‘%s %s.’, $lastname, join(‘. ‘, $values));
  28. }
  29. }
  30.  
  31. print_r($names);
  32. ?>

Но корректно работать будет, если фамилия длиннее имени или первого инициала.
Для примера проблемного имени добавил Андрея И

 

rr33rr

Небольшое уточнение условий задачи:

Инициал(ы) — _всегда_ заканчиваются точками и присутствуют всегда. Их может быть либо 1 либо 2. И они отделяются от фамилии либо минимум 1 пробелом (если идут после фамилии — ‘Фамилия И.О.’) либо могут не отделяться (если идут до фамилии — ‘И.О.Фамилия’).

Т.е. в исходном списке (который через запятую) не может быть элементов вроде ‘И Андрей’ (инициал не завершается точкой), ‘Сан-Антонио’ (инициалов нет) или пустых/пробельных — ‘ ‘.

Примечание:
Смысл задачи в том, что при публикации данных о различных книгах и статьях инициалы авторов могут указываться в разных форматах:
* с инициалами в начале (И.О. Фамилия) или в конце (Фамилия И.О.)
* с пробелами между инициалами (Фамилия И. О.) или без (И.О.Фамилия)

И нужно привести все ФИО авторов к единому формату, чтобы было удобно сортировать авторов (по алфавиту), для последующей генерации авторского указателя.

 

Drunkenmunky

У автора может не быть инициалов. Псевдоним например. Как Сан-Антонио.
У автора помимо инициалов могут быть другие атрибуты. Например ‘ибн», «фон», «акад.», «док.», «md» и прочее.
Поэтому рекомендуется в код вставить дополнительное условие.
Если разобранная в массив $name состоит не из трех элементов, то оставлять её как есть, но помещать в дополнительный массив с некондицией. Для последующей правки вручную например.

 

rr33rr

Благодарю за замечания, но
* На данный момент (несколько сотен научных книжек и статей в базе) псевдонимов без инициалов не встречалось и крайне маловероятно, что будут
* Приставки вроде ибн, фон, ван и т.п. возможны. И они, очевидно, входят в результирующий список как часть фамилии. Вроде такого: ‘van der Waals J.D.’
* Ученые звания и степени вроде «акад.», «док.» и т.п. обычно не используются в списках авторов публикаций. По крайней мере, ничего похожего не встречалось.

Поэтому усложнять структуру вывода пока нет необходимости. Хотелось бы решить эту задачу в рамках существующих условий. А усложнять только по мере возникновения необходимости. ))

 

Drunkenmunky

Так она в скрипте выше решена.
Имена типа «Фамилия И.О.» и «И.О. Фамилия» с любым количеством пробельных сиволов и точек разбиваются в массив, если первый его элемент длиннее последнего, то он считается фамилией, если нет, то наоборот. Оставшиеся элементы объединяются в строку через разделитель ‘. ‘, плюс еще одна точка в конец.

 

Дюран

Я бы так сделал:
— искал бы регуляркой инициалы
— а потом их вырезал, это стала бы фамилия

PHP:
  1. $authors = «Ив. Шишкин , ибн Иванов А.Б., Петров-Водкин К. С. ,В.А.Сидоров-Ивановский, M.Yu. Ivanov, D. Vrel, Dash Jh. B.»;
  2. $arr = explode(‘,’, $authors);
  3. $res = [];
  4. foreach ($arr as $val) {
  5.     if ($fio = process($val)) {
  6.         $res[] = $fio;
  7.     }
  8. }
  9. var_dump($res);
  10.  
  11. function process($str)
  12. {
  13.     $match = [];
  14.     if (preg_match(‘/(w+.s*(w+.)?)/iu’, $str, $match)) {
  15.         if (isset($match[1])) {
  16.             return trim(str_replace($match[1], », $str)) . ‘ ‘ . str_replace(‘ ‘, », $match[1]);
  17.         }
  18.     }
  19.     return false;
  20. }
 

rr33rr

Спасибо.
Примерно в эту сторону я и думал, но незнание регулярок мешало грамотно реализовать. ))

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *