Простая идентификация языка с помощью LINQ

#c# #linq #lambda #functional-programming #nlp

#c# #linq #лямбда #функциональное программирование #nlp

Вопрос:

Я впервые экспериментирую с LINQ и решил попробовать базовую идентификацию человеческого языка. Вводимый текст проверяется на соответствие HashSet s из наиболее распространенных 10 000 слов в языке и получает оценку.

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

 public PolyAnalyzer() {
    Dictionaries = new Dictionary<string, AbstractDictionary>();
    Dictionaries.Add("Bulgarian", new BulgarianDictionary());
    Dictionaries.Add("English", new EnglishDictionary());
    Dictionaries.Add("German", new GermanDictionary());
    Dictionaries.Values.Select(n => new Thread(() => n.LoadDictionaryAsync())).ToList().ForEach(n => n.Start());            
}  

public string getResults(string text) {
    int total = 0;
    return string.Join(" ",
        Dictionaries.Select(n => new {
            Language = n.Key,
            Score = new Regex(@"W ").Split(text).AsQueryable().Select(m => n.Value.getScore(m)).Sum()
        }).
        Select(n => { total  = n.Score; return n; }).
        ToList().AsQueryable(). // Force immediate evaluation
        Select(n =>
        "["   n.Score * 100 / total   "% "   n.Language   "]").
        ToArray());
}
  

P.S. Я знаю, что это чрезвычайно упрощенный подход к идентификации языка, меня просто интересует сторона LINQ.

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

1. Принадлежит codereview. Итак, нет. Кстати., обнаружение языка на n-граммах на уровне символов имеет тенденцию быть более надежным.

Ответ №1:

Я бы реорганизовал его следующим образом:

     public string GetResults(string text)
    {
        Regex wordRegex = new Regex(@"W ");
        var scores = Dictionaries.Select(n => new
            {
                Language = n.Key,
                Score = wordRegex.Split(text)
                                 .Select(m => n.Value.getScore(m))
                                 .Sum()
            });

        int total = scores.Sum(n => n.Score);
        return string.Join(" ",scores.Select(n => "["   n.Score * 100 / total   "% "   n.Language   "]");
    }
  

Несколько моментов:

  1. AsQueryAble() В этом нет необходимости — это все Linq to Objects, который IEnumerable<T> достаточно хорош.

  2. Удалены некоторые ToList() — также ненужные и позволяющие избежать быстрой загрузки результатов, когда они не нужны.

  3. Хотя приятно иметь всего один запрос LINQ, это не соревнование — стремитесь к удобочитаемости в целом и подумайте о том, как вы (и другие) должны поддерживать код. Я разделил ваш запрос на три более удобочитаемые (imo) части.

  4. Избегайте побочных эффектов всеми возможными способами — я удалил тот, который у вас был, из переменной total — это сбивает с толку — Запросы LINQ не должны иметь побочных эффектов, потому что выполнение одного и того же запроса дважды может привести к разным результатам. В вашем случае вы можете просто вычислить общее количество в отдельном запросе Linq.

  5. Не создавайте заново и не пересчитывайте переменные внутри проекции Linq, если в этом нет необходимости — Я удалил регулярное выражение из запроса Linq и инициализировал переменную один раз снаружи — в противном случае вы повторно создаете экземпляр Regex N раз, а не только один раз. Это может иметь огромные последствия для производительности в зависимости от запроса.

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

1. Действительно, я могу удалить AsQueryable s! Спасибо за замечания, я буду иметь их в виду (особенно немного о побочных эффектах).

2. Соглашаюсь на улучшение кода при сохранении функциональности. Спасибо за советы 🙂

Ответ №2:

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

 public PolyAnalyzer()
{
    Dictionaries = new Dictionary<string, AbstractDictionary>();
    Dictionaries.Add("Bulgarian", new BulgarianDictionary());
    Dictionaries.Add("English", new EnglishDictionary());
    Dictionaries.Add("German", new GermanDictionary());

    //Tip: Use the Parallel library to to multi-core, multi-threaded work.
    Parallel.ForEach(Dictionaries.Values, d =>
    {
        d.LoadDictionaryAsync();
    });            
}  

public Dictionary<string, int> GetResults(string text)
{
    //1) Split the words.
    //2) Calculate the score per dictionary (per language).
    //3) Return the scores.
    string[] words = new Regex(@"w ").Split().ToArray();
    Dictionary<string, int> scores = this.Dictionaries.Select(d => new
    {
        Language = d.Key,
        Score = words.Sum(w => d.Value.GetScore(w))
    }));

    return scores;
}
  

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

1. Спасибо за немного о words.Sum , это намного лучше, чем в моем решении!

2. Всегда пожалуйста. И чтобы добавить к этому, вы не должны «делать вещи» внутри Select . Вы должны выбирать только то, что находится внутри него. (Например, не вычисляйте общее количество там.) Я надеюсь, что этот пример достаточно понятен и работает для вас.

3. Есть ли какой-либо другой способ выполнить что-либо для каждого элемента?

4. Конечно: elements.ForEach(e => DoSomethingWith(e))

5. Не работает … работает только с List , по крайней мере, с .NET 3.5 (у меня только Visual Studio 2008), и я полагаю, что он не возвращается обратно e .