Случайный набор не на том языке, автоисправление

311
12 августа 2017, 00:55

Необходима функция исправления случайно набранных символов на другом языке, в данном случае EN->RU, также удаление букв ё и Ё. Вот что написал я:

string eng = "qwertyuiop[]asdfghjkl;'zxcvbnm,.QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>`~ёЁ";
string ru =  "йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮеЕеЕ";
for (int i = 0; i < eng.Length; ++i)
  if (query.Contains(eng[i]))
    query = query.Replace(eng[i], ru[i]);

Очень не нравится код, кто бы мог помочь?

Answer 1
public sealed class Replacer
{
    private readonly Dictionary<Char, Char> _dictionary;
    public Replacer(String sourceSymbols, String targetSymbols)
    {
        if (sourceSymbols.Length != targetSymbols.Length)
            throw new NotSupportedException("sourceSymbols.Length != targetSymbols.Length");
        Int32 count = sourceSymbols.Length;
        Dictionary<Char, Char> dictionary = new Dictionary<Char, Char>(count);
        for (int i = 0; i < count; i++)
            dictionary.Add(sourceSymbols[i], targetSymbols[i]);
        _dictionary = dictionary;
    }
    public void FixCharacters(ref String query)
    {
        if (String.IsNullOrEmpty(query))
            return;
        if (String.IsInterned(query) == null)
        {
            FixNotInternedString(query);
        }
        else
        {
            FixInternedString(ref query);
        }
    }
    private unsafe void FixNotInternedString(String query)
    {
        Int32 index = query.Length - 1;
        fixed (Char* chPtr = query)
        {
            while (index >= 0)
            {
                Char oldChar = chPtr[index];
                Char newChar;
                if (_dictionary.TryGetValue(oldChar, out newChar))
                    chPtr[index] = newChar;
                index--;
            }
        }
    }
    private void FixInternedString(ref String query)
    {
        StringBuilder sb = new StringBuilder(query.Length);
        foreach (Char c in query)
        {
            Char fixedChar;
            if (_dictionary.TryGetValue(c, out fixedChar))
                sb.Append(fixedChar);
            else
                sb.Append(c);
        }
        query = sb.ToString();
    }
}

Использование:

static void Main(string[] args)
{
    String eng = "qwertyuiop[]asdfghjkl;'zxcvbnm,.QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>`~ёЁ";
    String rus = "йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮеЕеЕ";
    Replacer replacer = new Replacer(eng, rus);
    for (int i = 0; i < 10; i++)
    {
        String query = $"Hello World {i}";
        replacer.FixCharacters(ref query);
        Console.WriteLine(query); // "Руддщ Цщкдв"
    }
}
Answer 2

1). Можно создать из строк eng и ru словарь "EN->RU", после чего проходить по заданной строке и заменять каждый символ в соответствии со словарём. В этом случае не придётся многократно просматривать изначальную строку, а создаваться будет всего одна новая строка:

public static class LangConversion
{
    private static readonly Dictionary<char, char> engToRu = new Dictionary<char, char>();
    static LangConversion()
    {
        var eng = "qwertyuiop[]asdfghjkl;'zxcvbnm,.QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>`~ёЁ";
        var ru = "йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮеЕеЕ";
        for (var i = 0; i < eng.Length; i++)
            engToRu[eng[i]] = ru[i];
    }
    public static string Fix(string str)
    {
        var sb = new StringBuilder(str.Length);
        foreach (char c in str)
        {
            char fixedChar;
            sb.Append(engToRu.TryGetValue(c, out fixedChar) ? fixedChar : c);
        }
        return sb.ToString();
    }
}

2). Если производительность важнее изящества кода, то можно замену реализовать с помощью switch-case:

public static class LangConversion2
{
    public static string Fix(string str)
    {
        var sb = new StringBuilder(str.Length);
        foreach (char c in str)
        {
            sb.Append(Replace(c));
        }
        return sb.ToString();
    }
    private static char Replace(char c)
    {
        switch (c)
        {
            case 'q': return 'й';
            ...
            case 'Ё': return 'Е';
            default: return c;
        }
    }
}

Полное содержимое switch в fiddle.

3). Ещё один способ провести замену при небольших дополнительных затратах памяти - с помощью массива:

public static class LangConversion3
{
    private static readonly char[] engToRu;
    static LangConversion3()
    {
        var eng = "qwertyuiop[]asdfghjkl;'zxcvbnm,.QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>`~ёЁ";
        var ru = "йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮеЕеЕ";
        int maxCharCode = 0;
        foreach (char c in eng)
            maxCharCode = c > maxCharCode ? c : maxCharCode;
        engToRu = new char[maxCharCode + 1];
        for (var i = 0; i < eng.Length; i++)
            engToRu[eng[i]] = ru[i];
    }
    public static string Fix(string str)
    {
        var sb = new StringBuilder(str.Length);
        foreach (char c in str)
        {
            sb.Append(Replace(c));
        }
        return sb.ToString();
    }
    private static char Replace(char c)
    {
        if (c >= engToRu.Length)
            return c;
        var fixedChar = engToRu[c];
        return fixedChar != 0 ? fixedChar : c;
    }
}

Замеры скорости работы на строке в 100 тысяч символов при 1000 итераций:

Исходный вариант:               6480мс
Dictionary:                     2550мс
Pointers (ответ @LunarWhisper): 1560мс
Switch-case:                    1520мс
Array:                          1310мс
Pointers + array:                720мс
Pointers + switch-case:          580мс
Answer 3

The purpose of software engineering is to control complexity, not to create it

Отсутствие четких критериев красоты "по виду" обычно приводит к исправлению в сторону "красивого" сложного кода. Отдельные классы-реплейсеры, нативная работа с Char (которая неизвестно как отработает на суррогатных парах), string builder-ы - вот это все.

Отсутсвие четких критериев красоты "по скорости" обычно приводит к "оптимизации" там, где она не нужна. У вас код исправления "случайно набранных символов на другом языке". Давайте проверять его на строках длиной в 100 000 символов на 1000 итераций! Пользователи же каждый день пишут Войну и Мир, забыв переключить раскладку. И потом огорчаются, что код работает аж 6 миллисекунд для исправления этой ошибки. 0.5 ms для них - ощутимая разница!

В вашем случае:

  • К коду нет строгих (реально обоснованных, а не "было бы круто") требований по производительности, нет четких требований по красоте (imho, код, который эту задачу решает больше, чем за 10 строк - ужасен!)
  • Нет требований по памяти (ваш вариант, например, потратит (размер строки)^2 байт, причем мусор будет выбрасываться сразу же, и дальше gen 0 не уйдет).

Единственное оставшееся требование - читабельность. Она же поддерживаемость.

Читабельность можно измерить стадартными метриками. Та же студия умеет считать Code Metrics.

Так вот, при условии что eng / ru объявлены как статика, по Maintainability Index выигрывают два варианта:

for (int i = 0; i < eng.Length; ++i)
    query = query.Replace(eng[i], ru[i]);

и чуть более новый, но такой же по сути

// считаем один раз, оптимизация!
static (char, char)[] dictionary = eng.Zip(ru, (a, b) => (a, b)).ToArray();
//....
foreach (var (e, r) in dictionary)
    query = query.Replace(e, r);

Оно же с инлайном:

foreach (var (e, r) in eng.Zip(ru, (a, b) => (a, b)))
    query = query.Replace(e, r);

В поставленных вами рамках, все остальные варианты - баловство.

READ ALSO
Отследить изменение элемента

Отследить изменение элемента

Есть элемент, аттрибут которого периодически меняется на случайное значениеКак отследить момент изменения и выполнить код?

313
Entity framework, поле DbGeography в сущности

Entity framework, поле DbGeography в сущности

Использую codeFirst и хочу загонять в бд пространственные данные, например полигон, поэтому есть таблица типа

256
Отображение данных из бд в xtraGrid

Отображение данных из бд в xtraGrid

Есть таблица в базе данных студентов: Id, имя, фамилия, год

201
EWS Managed API. Пользовательские свойства

EWS Managed API. Пользовательские свойства

В Outlook у Контакта есть свойства User1, User2, User3 и User4Нужно получить их значения через EWS, но они не включены в определение класса Microsoft

180