Общие обсуждения > Компьютеры и интернет
Продвинутый транслитератор
(1/1)
cetsalcoatle:
Захотелось попробовать сделать свой транслитератор, нашёл примеры кода, а как задать функцию на выбор символа(ов) в зависимости от позиции символа в слове и сочетания символов?
Bhudh:
Регулярными выражениями.
Квас:
Очень общая постановка задачи. Алгоритмы могут быть довольно заковыристыми - например, бета-код для древнегреческого.
В качестве противоположного к регуляркам полюса можно предложить побить слово на буквы и для каждой буквы собрать "метаданные", имеющие смысл для конкретной схемы транслитирации ("позиция в слове", "сочетание символов"), и применять замену не побуквенно, а к букве + метаданные. Скорее всего получится несколько громоздко и, вероятно, не очень эффективно, зато решение концептуально простое, гибкое и конфигурируемое. (Оверинжиниринг - моё второе имя.) Могу игрушечный пример набросать.
Квас:
Вот пример на TS (с одним хаком, где TS не справляется). Если игнорировать типы, он превращается в пример на JS. :) Зато с типами работает автокомплит, и компилятор подскажет, если в конфиге чего-то не хватает.
Для простоты я сделал транслитерацию одного слова. Можно читать сразу пример - там конфигурация транслита и пример использования. А "фреймворк" обеспечивает магию, превращающую конфиг в рабочую функцию.
--- Code: ---// "Фреймворк"
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"]
--- End code ---
Navigation
[0] Message Index
Go to full version