Алгоритмы и функции для обработки текстовых данных

обсуждение программы для работы с различными БД

Модераторы: konkar, deicide

Ответить
Аватара пользователя
ttm
профи
Сообщения: 140
Зарегистрирован: 09 июн 2022, 12:24
Благодарил (а): 435 раз
Поблагодарили: 218 раз

Алгоритмы и функции для обработки текстовых данных

Сообщение ttm »

Предлагаю сюда накидывать все, что облегчает обработку и чистку данных.
Последний раз редактировалось ttm 09 апр 2025, 13:48, всего редактировалось 1 раз.
Аватара пользователя
ttm
профи
Сообщения: 140
Зарегистрирован: 09 июн 2022, 12:24
Благодарил (а): 435 раз
Поблагодарили: 218 раз

ФИО из транслита в русский

Сообщение ttm »

Давно хотел, но все руки не доходили.
Алго не идеально, не учитывает все-все-все нюансы, но его можно дополнять и улучшать, что приветствуется.
translitToRussian.js

Код: [Local Link Removed for Guests]

function translitToRussian(text) {
  // Проверка: если строка пустая или состоит только из пробелов, возвращаем её без изменений
  if (!text.trim() || /^[а-яА-ЯёЁ\s]+$/.test(text)) {
    return text;
  }

  const exceptions = {
    // Примеры фамилий с польскими и чешскими окончаниями
    'gelajec': 'гелажец',
    'skoczylas': 'скочылас',
    'kachajec': 'кажец',
    'jeremic': 'жеремич',
    'kaczmarczyk': 'кацмарчик',
    'szczerbak': 'щербак',
    'poznajec': 'позняц',
    'szielezny': 'сзелезный',
    'kowalczyk': 'ковальчик',
    'czyzewski': 'чыжевский',
    'ziegler': 'циглер',
    'ziech': 'цих',
    'czycz': 'чиц',
    'wieczorek': 'вецерек',
    'leszczynski': 'лешчинский',
    'mikolajczak': 'миколайчак',
    'wojcik': 'войцик',
    'chruszcz': 'хрусч',
    'pietrzak': 'петржак',
    'wroblewski': 'врублевский',
    'cieslak': 'чешлак',

    // Общие имена
    'daria': 'дарья',
    'igor': 'игорь',
    'alex': 'александр', 
    'alexander': 'александр',
    'zavgorodniaia': 'завгородняя',
    'siniaev': 'синяев',
    'ganiaev': 'ганяев',
    'katiaev': 'катяев',
    'toliaev': 'толяев',
    'daniaev': 'даняев',
    'liapin': 'ляпин',
    'viakhirev': 'вяхирев',
    'tiaglov': 'тяглов',
    'riabov': 'рябов',
    'pianiov': 'пьяниов',
    'batiaev': 'батяев',
    'saniaev': 'саняев',
    'faniaev': 'фаняев',
    'natalia': 'наталья',
    'arnold': 'арнольд', 
    'ksenia': 'ксения',
    'kusowa': 'кусова'
  };

  const endings = [
    [/yev$/, 'ьев'],
    [/ii$/, 'ий'],
    [/ei$/, 'ей'],
    [/iy$/, 'ий'],
    [/iya$/, 'ия'],
    [/ckaia$/, 'цкая'],
    [/skaia$/, 'ская'],
    [/kaia$/, 'кая'],
    [/tskaia$/, 'цкая'],
    [/zhkaia$/, 'жкая'],
    [/shkaia$/, 'шкая'],
    [/chkaia$/, 'чкая'],
    [/([^кцсжчшщ])ia$/, '$1я'],
    [/ey$/, 'ей'],
    [/ai$/, 'ай'],
    [/oi$/, 'ой'],
    [/ui$/, 'уй'],
    [/ovaia$/, 'овая'],
    [/ova$/, 'ова'],
    [/evna$/, 'евна'],
    [/ovna$/, 'овна'],
    [/ich$/, 'ич'],
    [/ina$/, 'ина'],
    [/sky$/, 'ский'],
    [/skaya$/, 'ская'],
    [/ny$/, 'ний'],
    [/y$/, 'й']
  ];

  const rules = [
    ['shch', 'щ'],
    ['kh', 'х'],
    ['tck', 'ц'],
    ['ck', 'ц'],
    ['yo', 'ё'],
    ['zh', 'ж'],
    ['ch', 'ч'],
    ['sh', 'ш'],
    ['yu', 'ю'],
    ['ya', 'я'],
    ['ts', 'ц'],
    ['ye', 'е'],
    ['x', 'кс'],
    ['e', 'е'],
    ['a', 'а'],
    ['b', 'б'],
    ['v', 'в'],
    ['g', 'г'],
    ['d', 'д'],
    ['z', 'з'],
    ['i', 'и'],
    ['j', 'й'],
    ['k', 'к'],
    ['l', 'л'],
    ['m', 'м'],
    ['n', 'н'],
    ['o', 'о'],
    ['p', 'п'],
    ['r', 'р'],
    ['s', 'с'],
    ['t', 'т'],
    ['u', 'у'],
    ['f', 'ф'],
    ['h', 'х'],
    ['c', 'к']
  ];

  const replaceAllSafe = (str, search, replace) => {
    return str.replace(
      new RegExp(search.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'),
      replace
    );
  };

  const capitalize = (word) =>
    word.charAt(0).toUpperCase() + word.slice(1);

  return text.split(' ').map((word) => {
    let lower = word.toLowerCase();

    // 1. Очистка ошибочных окончаний
    lower = lower
      .replace(/iaia$/, 'ia')
      .replace(/iiia$/, 'ia')
      .replace(/iia$/, 'ia')
      .replace(/iiy$/, 'iy');

    // 2. Исключения
    if (exceptions[lower]) {
      return capitalize(exceptions[lower]);
    }

    // 3. Обработка tc / tce / vtce / iav
    lower = lower
      .replace(/vtce/g, 'вце')
      .replace(/tce/g, 'це')
      .replace(/tc/g, 'ц')
      .replace(/iav/g, 'яв');

    // 4. Спец. замены в середине слова
    lower = lower
      .replace(/([бвгджзклмнпрстфхцчшщ])ia([еёийоуаэыюя])/g, '$1я$2')
      .replace(/([бвгджзклмнпрстфхцчшщ])io/g, '$1ё')
      .replace(/([бвгджзклмнпрстфхцчшщ])ie/g, '$1ье')
      .replace(/([бвгджзклмнпрстфхцчшщ])ei/g, '$1ей');

    // 5. Заменяем "u" на "ю" в имени
    lower = lower.replace(/u([аеёиоуыэюя])/g, 'ю$1');

    // 6. Правило для "oi" в середине, если это не часть имени
    lower = lower.replace(/oi/g, 'ой');

    // 7. Окончания
    for (const [regex, replacement] of endings) {
      if (regex.test(lower)) {
        lower = lower.replace(regex, replacement);
        break;
      }
    }

    // 8. Общие правила
    for (const [lat, rus] of rules.sort((a, b) => b[0].length - a[0].length)) {
      lower = replaceAllSafe(lower, lat, rus);
    }

    return capitalize(lower);
  }).join(' ');
}
Потестить можно тут: [External Link Removed for Guests]
На входе получает: Bulochkina Ksenia
На выходе: Булочкина Ксения
Аватара пользователя
ttm
профи
Сообщения: 140
Зарегистрирован: 09 июн 2022, 12:24
Благодарил (а): 435 раз
Поблагодарили: 218 раз

Заменяет латинские буквы в русском тексте на русские

Сообщение ttm »

Заменяет латинские буквы в русском тексте на русские, если латинских меньше половины от общего числа.
fixMixedAlphabet.js

Код: [Local Link Removed for Guests]

function fixMixedAlphabet(text) {
  const latinToCyrillicMap = {
    'A': 'А', 'B': 'В', 'C': 'С', 'E': 'Е', 'H': 'Н',
    'K': 'К', 'M': 'М', 'O': 'О', 'P': 'Р', 'T': 'Т',
    'X': 'Х', 'Y': 'У',

    'a': 'а', 'c': 'с', 'e': 'е', 'o': 'о', 'p': 'р',
    'x': 'х', 'y': 'у',
  };

  const latinLetters = Object.keys(latinToCyrillicMap);
  let totalLetterCount = 0;
  let latinLetterCount = 0;

  for (let char of text) {
    if (/[a-zA-Zа-яА-ЯёЁ]/.test(char)) {
      totalLetterCount++;
      if (latinLetters.includes(char)) {
        latinLetterCount++;
      }
    }
  }

  const latinRatio = latinLetterCount / (totalLetterCount || 1); // делим на 1 чтобы избежать деления на 0

  if (latinRatio > 0.5) {
    // Похоже, что текст в основном латинский — не трогаем
    return text;
  }

  // Заменяем латинские буквы на похожие русские
  return text.split('').map(char => latinToCyrillicMap[char] || char).join('');
}
Аватара пользователя
Burg0mister
профи
Сообщения: 326
Зарегистрирован: 19 июл 2022, 17:07
Благодарил (а): 1794 раза
Поблагодарили: 697 раз

Re: ФИО из транслита в русский

Сообщение Burg0mister »

[Local Link Removed for Guests] писал(а): [Local Link Removed for Guests]09 апр 2025, 13:47 Давно хотел, но все руки не доходили.
Алго не идеально, не учитывает все-все-все нюансы, но его можно дополнять и улучшать, что приветствуется.
translitToRussian.js

Код: [Local Link Removed for Guests]

function translitToRussian(text) {
  // Проверка: если строка пустая или состоит только из пробелов, возвращаем её без изменений
  if (!text.trim() || /^[а-яА-ЯёЁ\s]+$/.test(text)) {
    return text;
  }

  const exceptions = {
    // Примеры фамилий с польскими и чешскими окончаниями
    'gelajec': 'гелажец',
    'skoczylas': 'скочылас',
    'kachajec': 'кажец',
    'jeremic': 'жеремич',
    'kaczmarczyk': 'кацмарчик',
    'szczerbak': 'щербак',
    'poznajec': 'позняц',
    'szielezny': 'сзелезный',
    'kowalczyk': 'ковальчик',
    'czyzewski': 'чыжевский',
    'ziegler': 'циглер',
    'ziech': 'цих',
    'czycz': 'чиц',
    'wieczorek': 'вецерек',
    'leszczynski': 'лешчинский',
    'mikolajczak': 'миколайчак',
    'wojcik': 'войцик',
    'chruszcz': 'хрусч',
    'pietrzak': 'петржак',
    'wroblewski': 'врублевский',
    'cieslak': 'чешлак',

    // Общие имена
    'daria': 'дарья',
    'igor': 'игорь',
    'alex': 'александр', 
    'alexander': 'александр',
    'zavgorodniaia': 'завгородняя',
    'siniaev': 'синяев',
    'ganiaev': 'ганяев',
    'katiaev': 'катяев',
    'toliaev': 'толяев',
    'daniaev': 'даняев',
    'liapin': 'ляпин',
    'viakhirev': 'вяхирев',
    'tiaglov': 'тяглов',
    'riabov': 'рябов',
    'pianiov': 'пьяниов',
    'batiaev': 'батяев',
    'saniaev': 'саняев',
    'faniaev': 'фаняев',
    'natalia': 'наталья',
    'arnold': 'арнольд', 
    'ksenia': 'ксения',
    'kusowa': 'кусова'
  };

  const endings = [
    [/yev$/, 'ьев'],
    [/ii$/, 'ий'],
    [/ei$/, 'ей'],
    [/iy$/, 'ий'],
    [/iya$/, 'ия'],
    [/ckaia$/, 'цкая'],
    [/skaia$/, 'ская'],
    [/kaia$/, 'кая'],
    [/tskaia$/, 'цкая'],
    [/zhkaia$/, 'жкая'],
    [/shkaia$/, 'шкая'],
    [/chkaia$/, 'чкая'],
    [/([^кцсжчшщ])ia$/, '$1я'],
    [/ey$/, 'ей'],
    [/ai$/, 'ай'],
    [/oi$/, 'ой'],
    [/ui$/, 'уй'],
    [/ovaia$/, 'овая'],
    [/ova$/, 'ова'],
    [/evna$/, 'евна'],
    [/ovna$/, 'овна'],
    [/ich$/, 'ич'],
    [/ina$/, 'ина'],
    [/sky$/, 'ский'],
    [/skaya$/, 'ская'],
    [/ny$/, 'ний'],
    [/y$/, 'й']
  ];

  const rules = [
    ['shch', 'щ'],
    ['kh', 'х'],
    ['tck', 'ц'],
    ['ck', 'ц'],
    ['yo', 'ё'],
    ['zh', 'ж'],
    ['ch', 'ч'],
    ['sh', 'ш'],
    ['yu', 'ю'],
    ['ya', 'я'],
    ['ts', 'ц'],
    ['ye', 'е'],
    ['x', 'кс'],
    ['e', 'е'],
    ['a', 'а'],
    ['b', 'б'],
    ['v', 'в'],
    ['g', 'г'],
    ['d', 'д'],
    ['z', 'з'],
    ['i', 'и'],
    ['j', 'й'],
    ['k', 'к'],
    ['l', 'л'],
    ['m', 'м'],
    ['n', 'н'],
    ['o', 'о'],
    ['p', 'п'],
    ['r', 'р'],
    ['s', 'с'],
    ['t', 'т'],
    ['u', 'у'],
    ['f', 'ф'],
    ['h', 'х'],
    ['c', 'к']
  ];

  const replaceAllSafe = (str, search, replace) => {
    return str.replace(
      new RegExp(search.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'),
      replace
    );
  };

  const capitalize = (word) =>
    word.charAt(0).toUpperCase() + word.slice(1);

  return text.split(' ').map((word) => {
    let lower = word.toLowerCase();

    // 1. Очистка ошибочных окончаний
    lower = lower
      .replace(/iaia$/, 'ia')
      .replace(/iiia$/, 'ia')
      .replace(/iia$/, 'ia')
      .replace(/iiy$/, 'iy');

    // 2. Исключения
    if (exceptions[lower]) {
      return capitalize(exceptions[lower]);
    }

    // 3. Обработка tc / tce / vtce / iav
    lower = lower
      .replace(/vtce/g, 'вце')
      .replace(/tce/g, 'це')
      .replace(/tc/g, 'ц')
      .replace(/iav/g, 'яв');

    // 4. Спец. замены в середине слова
    lower = lower
      .replace(/([бвгджзклмнпрстфхцчшщ])ia([еёийоуаэыюя])/g, '$1я$2')
      .replace(/([бвгджзклмнпрстфхцчшщ])io/g, '$1ё')
      .replace(/([бвгджзклмнпрстфхцчшщ])ie/g, '$1ье')
      .replace(/([бвгджзклмнпрстфхцчшщ])ei/g, '$1ей');

    // 5. Заменяем "u" на "ю" в имени
    lower = lower.replace(/u([аеёиоуыэюя])/g, 'ю$1');

    // 6. Правило для "oi" в середине, если это не часть имени
    lower = lower.replace(/oi/g, 'ой');

    // 7. Окончания
    for (const [regex, replacement] of endings) {
      if (regex.test(lower)) {
        lower = lower.replace(regex, replacement);
        break;
      }
    }

    // 8. Общие правила
    for (const [lat, rus] of rules.sort((a, b) => b[0].length - a[0].length)) {
      lower = replaceAllSafe(lower, lat, rus);
    }

    return capitalize(lower);
  }).join(' ');
}
Потестить можно тут: [External Link Removed for Guests]
На входе получает: Bulochkina Ksenia
На выходе: Булочкина Ксения
Приветствую!
Отличная идея, тоже об этом задумывался, но потом в профильном разделе уже кто-то создал похожую тему с советами по обработке. Но не в этом суть.

Хочу предложить небольшое усовершенствование для работы по транслиту, потому что в некоторых базах есть хитрость, которая не все строчки позволит исправить. Знаете подставу с перемешиванием латиницы и кириллицы в автомобильных номерах, VIN и ФИО?
Это когда
Александр Иванов
и
Aлeкcaндр Ивaнoв
Читаются одинаково, но на самом деле часть букв написана латиницей, а часть кириллицей.

Перед конвертированием латиницы в кирилицу стоит провести проверку и исправление в рамках выделенного столбца и заменить кириллические символы на латинские, а уже потом конвертировать все полностью в кириллицу.
Спойлер
Если на раздачи ставлю личный пароль - делюсь им безвозмездно, так же он есть и у администрации! Приму в дар БД с контактами, ФИО, адресами по СНГ/РФ. Интересуют: Современные ГИБДД, банки, блэклисты банков, телефоны, спецучет и т. п.
Ответить