Author Topic: Продвинутый транслитератор  (Read 235 times)

Offline cetsalcoatle

  • Герой
  • *
  • Posts: 3994
  • Карма: +319/-274
  • Благородный муж прям и твёрд, но не упрям.
    • View Profile
Продвинутый транслитератор
« on: 25 April 2024, 11:18:01 »
  • 0
  • 0
Захотелось попробовать сделать свой транслитератор, нашёл примеры кода, а как задать функцию на выбор символа(ов) в зависимости от позиции символа в слове и сочетания символов?
Останься прост, беседуя с царями,
Останься честен, говоря с толпой;
Будь прям и тверд с врагами и друзьями,
Пусть все, в свой час, считаются с тобой;

Simba mwenda pole ndiye mla nyama

Offline Bhudh

  • Дважды герой
  • **
  • Posts: 5683
  • Карма: +2059/-211
    • View Profile
Re: Продвинутый транслитератор
« Reply #1 on: 25 April 2024, 11:33:29 »
  • 1
  • 1
Регулярными выражениями.
Jestem dokładny i dociekliwy. (Wg Pinii.)
Всё, что нужно для торжества зла — это бездействие добрых людей. Поэтому бездействовать не надо. Алексей Навальный

Online Квас

  • Ветеран
  • *****
  • Posts: 1289
  • Карма: +370/-9
    • View Profile
Re: Продвинутый транслитератор
« Reply #2 on: 25 April 2024, 13:49:49 »
  • 1
  • 0
Очень общая постановка задачи. Алгоритмы могут быть довольно заковыристыми - например, бета-код для древнегреческого.

В качестве противоположного к регуляркам полюса можно предложить побить слово на буквы и для каждой буквы собрать "метаданные", имеющие смысл для конкретной схемы транслитирации ("позиция в слове", "сочетание символов"), и применять замену не побуквенно, а к букве + метаданные. Скорее всего получится несколько громоздко и, вероятно, не очень эффективно, зато решение концептуально простое, гибкое и конфигурируемое. (Оверинжиниринг - моё второе имя.) Могу игрушечный пример набросать.

Online Квас

  • Ветеран
  • *****
  • Posts: 1289
  • Карма: +370/-9
    • View Profile
Re: Продвинутый транслитератор
« Reply #3 on: 25 April 2024, 16:41:42 »
  • 1
  • 0
Вот пример на TS (с одним хаком, где TS не справляется). Если игнорировать типы, он превращается в пример на JS. :) Зато с типами работает автокомплит, и компилятор подскажет, если в конфиге чего-то не хватает.

Для простоты я сделал транслитерацию одного слова. Можно читать сразу пример - там конфигурация транслита и пример использования. А "фреймворк" обеспечивает магию, превращающую конфиг в рабочую функцию.

Code: [Select]
// "Фреймворк"

interface CharAnalyzerContext {
  word: string;
  position: number;
  wordLength: number;
}

type CharAnalyzer<T> = (char: string, context: CharAnalyzerContext) => T;

type Analyzer<T> = {
  [K in keyof T]: CharAnalyzer<T[K]>;
};

// Хак с кастами типа, зато одна реализация работает для любого T
function applyAnalyzer<T>(analyzer: Analyzer<T>): CharAnalyzer<T> {
  return (char, context) => {
    const metadata: any = {};
    Object.entries(analyzer).forEach(([key, analyzerFn]) => {
      metadata[key] = (analyzerFn as CharAnalyzer<any>)(char, context)
    });
    return metadata as T;
  }
}

interface TranslitConfig<T> {
  analyzer: Analyzer<T>;
  substitutions: Record<string, string | ((metadata: T, char: string) => string)>;
  defaultSubstitution: (metadata: T, char: string) => string;
}

function createTranslit<T>(config: TranslitConfig<T>): (word: string) => string {
  const analyze = applyAnalyzer(config.analyzer);
  return word => {
    const substituted: string[] = [];
    const wordLength = word.length;
    for (let position = 0; position < wordLength; ++position) {
      const char = word[position];
      const substitution = config.substitutions[char] ?? config.defaultSubstitution;
      let substitutedString: string;
      if (typeof substitution === 'string') {
        substitutedString = substitution;
      } else {
        const context: CharAnalyzerContext = { word, position, wordLength };
        const metadata = analyze(char, context);
        substitutedString = substitution(metadata, char);
      }
      substituted.push(substitutedString);
    }
    return substituted.join('');
  }
}

// Пример

interface Metadata {
  isFirst: boolean;
  precededBy: string | null;
}

const translitConfig: TranslitConfig<Metadata> = {
  analyzer: {
    isFirst: (char, context) => context.position === 0,
    precededBy: (char, context) => context.position === 0 ? null : context.word[context.position - 1],
  },
  substitutions: {
    'с': 's',
    'л': 'l',
    'ъ': '',
    'е': ({ isFirst, precededBy }) => isFirst || precededBy === 'ъ' ? 'je' : 'e',
  },
  defaultSubstitution: (_, char) => char,
}

const translit = createTranslit(translitConfig);

console.log(['сел', 'съел', 'ел'].map(translit));
// ["sel", "sjel", "jel"]
« Last Edit: 27 April 2024, 22:01:28 by Квас »