СТРОКА DELPHI: извлеките фамилию из полного имени

#delphi #string #delphi-2009

#delphi #строка #delphi-2009

Вопрос:

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

Пример: SQL-запрос извлекает полное поле «Мэри Эллен Джонс». Мне нужно извлечь только имя Джонса из строки, чтобы я мог сохранить его в переменной для дальнейшей обработки.

Я подумал, что, возможно, AnsiRightStr сработал бы, но проблема в том, что нужно присвоить ему заданное целое число для извлечения справа. Может быть, есть способ подсчета символов после последнего пробела, позволяющий мне использовать AnsiRightStr (string, int) для этого? Приветствуется любая помощь.

Дополнительная мысль: была бы возможна замена пробелов разделителем, скажем ::, а затем синтаксический анализ этих данных в Stringlist с последующим разрешением мне извлекать последний индекс списка строк?

На данный момент было представлено несколько допустимых вариантов. Ни один из них не решает ситуацию, если, скажем, имя что-то вроде «Джон Сент-Джеймс-младший». Это невозможно?

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

1. Вы также можете разбить строку на пробелы при использовании stringlist. Но нет способа определить, состоит ли фамилия только из последнего слова. Если вы хотите решить эту проблему правильно, сохраните имя и фамилию в отдельных полях в базе данных. Это также обеспечивает лучшие возможности сортировки и поиска.

2. Пока ваши имена достаточно англоцентричны, с шаблоном «Первый Средний последний», разрыв на пробелах должен работать. Как насчет людей, в имени которых есть 4 слова? Это 2 «средних» имени или 2 «последних» имени или двойная фамилия без дефиса? С именами трудно иметь дело и анализировать их во всех ситуациях.

3. @afraizer: Я вижу это по мере развития этого вопроса и ответов.

4. @Golez: Проблема в том, что я работаю в компании, которая пишет программное обеспечение для банкротства, в прошлом разработчикам кода нужно было указывать только в нескольких местах. Компания решила включить полное имя разработчика кода в одну запись, в то время как должник получил разделенные записи. Теперь, когда суды продвигают более защищенные данные, мы обнаруживаем необходимость отредактировать имя и отчество codebtor. Изменить это сейчас невозможно для того, что нам нужно сделать.

5. @JamesW: Погрузитесь в кроличью нору этого вопроса достаточно глубоко, и вы поймете, что единственное реальное решение — исправить вашу базу данных и методы ввода данных, чтобы использовать отдельные поля, содержимое которых может быть даже не сразу видно. (например, используя поля «Полное имя» и «Приветствие» для хранения «Мэри Эллен Джонс» и «Ms. Поля имени «Джонс», а не «Имя / Отчество /фамилия».) Вероятно, вы можете использовать достаточно суффиксов специального регистра и соединителей фамилий, чтобы либо работать с вашим текущим набором данных, либо сделать переход на новую схему менее болезненным.

Ответ №1:

вы можете использовать LastDelimiter функцию, чтобы получить последнюю позицию пробела, а затем с помощью copy функции извлечь подстроку.

 uses
  SysUtils;


var
  Name      : string;
  p         : Integer;
  ShortName : string;
begin
  Name:='Mary Ellen Jones';
  //You can call trim to avoid problems with ending spaces in this case is not necesary, just is a test
  //Name:=Trim(Name); 
  //get the last space position
  p:=LastDelimiter(' ',Name);
  //get the name
  ShortName:=Copy(Name,p 1,length(Name)-p);
end;
  

или с помощью функции

 function GetLast(const Name:string) : string;
var
  p : Integer;
begin
  Result:=Trim(Name);
  p:=LastDelimiter(' ',Result);
  Result:=Copy(Result,p 1,length(Result)-p);
end;
  

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

1. Ваш способ, похоже, также очень правильный способ сделать то, что мне нужно сделать. Любой способ справиться с ситуацией, упомянутой в комментариях к ответу Андреаса, чтобы учесть префиксы и суффиксы, как в случае с именем «Бобби Сент-Джонс-младший».

2. @JamesW, эти случаи можно обработать, создав более сложную функцию, но прежде вы должны определить область ее действия, добавив все префиксы типа St .

3. @JamesW: Единственный способ — это жестко закодировать наиболее распространенные префиксы или использовать некоторые эвристические методы, чтобы угадать, является ли ‘word’ названием или суффиксом или нет. Это было бы не слишком сложно сделать, но это, вероятно, никогда не сработает на 100%, и это требует некоторого размышления.

Ответ №2:

 function GetLastWord(const Str: string): string;
var
  p: integer;
  i: Integer;
const
  SPACE = #$20;
begin
  p := 1;
  for i := length(Str) downto 1 do
    if Str[i] = SPACE then
    begin
      p := i   1;
      break;
    end;
  result := Copy(Str, p, MaxInt);
end;
  

Это приведет к сбою, если строка заканчивается (случайным) пробелом, как ‘Andreas Rejbrand’. Эта более надежная версия также справится с этим случаем:

 function GetLastWord(const Str: string): string;
var
  p: integer;
  i: Integer;
  FoundNonSpace: boolean;
const
  SPACE = #$20;
begin
  p := 1;
  FoundNonSpace := false;
  for i := length(Str) downto 1 do
    if (Str[i] = SPACE) and FoundNonSpace then
    begin
      p := i   1;
      break
    end
    else if Str[i] <> SPACE then
      FoundNonSpace := true;
  result := TrimRight(Copy(Str, p, MaxInt));
end;
  

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

1. Я как раз собирался опубликовать это! 😛

2. Потрясающе, спасибо. Это именно то, что мне было нужно. Я приму ответ через пару минут, когда мне это позволит SO.

3. @JamesW, поторопись, или Андреас убежит :-).

4. Хотя один вопрос. Что, если фамилия, скажем, «Сент-Джеймс», как-нибудь это объяснить?

5. @JamesW, серьезно, вам следует не торопиться с ответом, кто-нибудь может придумать что-нибудь еще более блестящее.

Ответ №3:

Что, если фамилия, скажем, «Сент-Джеймс», как-нибудь это объяснить?

Вот мой подход.

  1. Составьте список маркеров фамилий
  2. Выполните поиск в этом списке в порядке предпочтения
  3. Как только будет найдено совпадение, отметьте его как начало фамилии
  4. Верните подстроку, начинающуюся с этого поз.
var 
 LastNameMarkers: TStringList = nil;
 Исправление суффикса: TStringList = nil;
 procedure InitLists;
begin
  LastNameMarkers:= TStringList.Create;
  //LastNameMarkers.LoadFromFile('c:markers.txt');
  LastNameMarkers.Add(' St.');
  LastnameMarkers.Add(' Mc');
  LastNameMarkers.Add(' '); //Marker of last resort.
  SuffixFix:= TStringList.Create;
  SuffixFix.Add(' Jr.');
  SuffixFix.Add(' Sr.');
end;

function GetLastName(FullName: string): string;
var
  i: integer;
  start: integer;
  found: boolean;
  ReplaceWith: string;
begin
  if LastNameMarkers = nil then InitLists;

  //Fix suffixes
  i:= 0;
  found:= false;
  while (i < SuffixFix.Count) and not found do begin
    start:= pos(lower(LastNameMarkers[i]),lower(Fullname));
    found:= Start > 0;
    Inc(i);
  end; {while}
  if Found then begin 
    Dec(i);
    ReplaceWith:= StringReplace(Suffix[i], ' ', '_',[]);
    FullName:= StringReplace(FullName, SuffixFix[i], ReplaceWith,[]);
  end; {if}

  //Look for lastnames 
  i:= 0;
  found:= false;
  while (i < LastNameMarkers.Count) and not found do begin
    start:= pos(LastNameMarkers[i],Fullname);
    found:= Start > 0;
    Inc(i);
  end; {while}

  if found then Result:= RightStr(FullName, Length(FullName)- Start   2)
  else Result:= '';

  StringReplace(Result, '_', ' ',[]);
end;
  

Я не разобрался должным образом с верхним и нижним регистром, но я надеюсь, что вы поняли идею.

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

1. Замените на:= StringReplace(суффикс[i], ‘ ‘, ‘_’,[]); выдает ошибку «Индекс выходит за пределы».

2. Хотя в конечном итоге я не использовал именно этот код, я использовал очень похожую вещь. Я опубликую это как ответ на этот вопрос для ознакомления. На самом деле это включает в себя эту логику (своего рода) с получением последнего слова ответа Андреаса.

3. Этот ответ все еще очень ориентирован на англо. Я бы не доверял такой системе в системе баз данных реального мира.

4. @warren, конечно, нет, это отчаянный взлом, чтобы спасти дизайн borken db. Я бы ни в коем случае не рекомендовал это. Я помню, как читал статью Кнута об этом, его вывод, что эта проблема не имеет решения. Единственная причина, по которой люди могут это делать, заключается в том, что они используют массивную базу данных, расширенное сопоставление с образцом и эвристику

5. Даже люди допустили бы ошибку. И затем возникает сложность, заключающаяся в том, что в некоторых культурах фамилии стоят на первом месте, а собственное имя человека является последним. Так что даже «имя» — это этоцентризм.

Ответ №4:

 function TfrmCal.GetLastName(FullName: string): string;
var
    i: integer;
    found: boolean;
    suffix: string;
    marker: string;
begin
    // Build the lists for the compare.
    InitLists;

    // Look at Suffixes and attach them to the LastName
    i := 0;
    found := False;
    while (i < SuffixFix.Count) do
    begin
        if AnsiContainsStr(FullName, SuffixFix[i]) then
        begin
            suffix := '::'   trim(SuffixFix[i]);
            FullName := ReplaceStr(FullName, SuffixFix[i], suffix);
            found := True;
        end;
        inc(i);
        if found then
            break;
    end;
    // Look for LastName Markers
    i := 0;
    found := False;
    while (i < LastNameMarkers.Count) do
    begin
        if AnsiContainsStr(FullName, LastNameMarkers[i]) then
        begin
            marker := trimright(LastNameMarkers[i])   '::';
            FullName := ReplaceStr(FullName, LastNameMarkers[i], marker);
            found := True;
        end;
        inc(i);
        if found then
            break;
    end;

    FullName := GetLastWord(FullName);
    FullName := ReplaceStr(FullName, '::', ' ');
    LastNameMarkers.Clear;
    SuffixFix.Clear;
    Result := FullName;
end;

function TfrmCal.GetLastWord(const Str: string): string;
var
    p: integer;
    i: integer;
const
    SPACE = #$20;
begin
    p := 1;
    for i := Length(Str) downto 1 do
        if Str[i] = SPACE then
        begin
            p := i   1;
            break;
        end;
    Result := Copy(Str, p, MaxInt);
end;
  

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

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

1. вы не смогли учесть верхний / нижний регистр. В остальном все выглядит нормально. Используйте идентификатор if AnsiContainsStr(lower(FullName), lower(SuffixFix[i])) then ... , чтобы сделать сравнение нечувствительным к регистру

2. Я могу посмотреть на этот вариант, учитываются меньшие значения, это просто сделано в TStringList и в уродливом коде, который я здесь не показывал.