С# — разделить строку полностью в верхнем регистре на отдельные слова (без пробелов)

В настоящее время я работаю над проектом, в котором мне нужно будет отделить отдельные слова от строки. Загвоздка в том, что все слова в строке написаны с большой буквы и не содержат пробелов. Ниже приведен пример типа входных данных, которые получает программа:

"КОМПЬЮТЕР ПЯТЬЕКОДЕКОЛОР"

Это должно быть разделено на следующий результат:

"КОМПЬЮТЕР" "ПЯТЬ" "КОД" "ЦВЕТ"

До сих пор я использовал следующий метод для разделения строк (и он работал для всех сценариев, кроме этого пограничного случая):

private static List<string> NormalizeSections(List<string> wordList)
        {
            var modifiedList = new List<string>();
            foreach (var word in wordList)
            {
                int index = wordList.IndexOf(word);
                var split = Regex.Split(word, @"(\p{Lu}\p{Ll}+)").ToList();
                split.RemoveAll(i => i == "");

                modifiedList.AddRange(split);
            }
            return modifiedList;
        }

Если у кого-то есть какие-либо идеи о том, как справиться с этим, я был бы более чем рад их услышать. Также, пожалуйста, дайте мне знать, если я могу предоставить дополнительную информацию.


person CodePull    schedule 14.11.2017    source источник
comment
Если нет ничего, определяющего, что такое слово, как вы определяете слово? У вас есть какие-то ограничения на слова или словарь разрешенных слов? Даже в этом случае представьте, что у вас есть GRAINSTOP. Следует ли это анализировать как GRAIN STOP или GRAINS TOP?   -  person Evan Trimboli    schedule 14.11.2017
comment
Что-то, что следует учитывать в вашем алгоритме, это то, как обрабатывать комбинации символов, которые сами являются словами, но также являются подстроками другого слова. Например, если «ТЕЛЕВИДЕНИЕ» появляется как часть вашего ввода, «ВИДЕНИЕ» в вашем списке слов может потреблять буквы и оставить «ТЕЛЕ» само по себе, чтобы оставаться несопоставленным. Если вас беспокоит этот случай, вы можете отсортировать исходный список слов по убыванию длины.   -  person Kyle Burns    schedule 14.11.2017


Ответы (2)


Я делаю некоторые предположения о том, как вы хотите искать совпадающие слова. Во-первых, при заданном индексе символов предпочтение будет отдано самому длинному совпадающему слову в словаре. Во-вторых, если по данному индексу символа не найдено ни одного слова, мы переходим к следующему символу и снова ищем.

В приведенной ниже реализации используется Trie для индексации словаря всех допустимых слов. Вместо того, чтобы перебирать каждое слово в словаре, мы просматриваем каждый символ во входной строке, ища самое длинное слово.

Я поднял реализацию Trie на С# из этого очень удобного ответа SO: https://stackoverflow.com/a/6073004

Редактировать: исправлена ​​ошибка в Trie при добавлении слова, которое является подстрокой существующего слова, например Emergency, затем Emerge.

Код доступен на DotNetFiddle.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {

        var words = new[] { "COMPUTE", "FIVE", "CODE", "COLOR", "PUT", "EMERGENCY", "MERGE", "EMERGE" };

        var trie = new Trie(words);

        var input = "COMPUTEEMERGEFIVECODECOLOR";

        for (var charIndex = 0; charIndex < input.Length; charIndex++)
        {
            var longestWord = FindLongestWord(trie.Root, input, charIndex);

            if (longestWord == null)
            {
                Console.WriteLine("No word found at char index {0}", charIndex);
            }
            else
            {
                Console.WriteLine("Found {0} at char index {1}", longestWord, charIndex);

                charIndex += longestWord.Length - 1;
            }
        }

    }

    static private string FindLongestWord(Trie.Node node, string input, int charIndex)
    {
        var character = char.ToUpper(input[charIndex]);

        string longestWord = null;

        foreach (var edge in node.Edges)
        {
            if (edge.Key.ToChar() == character)
            {
                var foundWord = edge.Value.Word;

                if (!edge.Value.IsTerminal)
                {
                    var longerWord = FindLongestWord(edge.Value, input, charIndex + 1);

                    if (longerWord != null) foundWord = longerWord;
                }

                if (foundWord != null && (longestWord == null || edge.Value.Word.Length > longestWord.Length))
                {
                    longestWord = foundWord;
                }
            }
        }

        return longestWord;
    }
}

//Trie taken from: https://stackoverflow.com/a/6073004
public struct Letter
{
    public const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public static implicit operator Letter(char c)
    {
        return new Letter() { Index = Chars.IndexOf(c) };
    }
    public int Index;
    public char ToChar()
    {
        return Chars[Index];
    }
    public override string ToString()
    {
        return Chars[Index].ToString();
    }
}

public class Trie
{
    public class Node
    {
        public string Word;
        public bool IsTerminal { get { return Edges.Count == 0 && Word != null; } }
        public Dictionary<Letter, Node> Edges = new Dictionary<Letter, Node>();
    }

    public Node Root = new Node();

    public Trie(string[] words)
    {
        for (int w = 0; w < words.Length; w++)
        {
            var word = words[w];
            var node = Root;
            for (int len = 1; len <= word.Length; len++)
            {
                var letter = word[len - 1];
                Node next;
                if (!node.Edges.TryGetValue(letter, out next))
                {
                    next = new Node();

                    node.Edges.Add(letter, next);
                }

                if (len == word.Length)
                {
                    next.Word = word;
                }

                node = next;
            }
        }
    }

}

Выход:

Found COMPUTE at char index 0
Found EMERGE at char index 7
Found FIVE at char index 13
Found CODE at char index 17    
Found COLOR at char index 21
person Mark Glasgow    schedule 14.11.2017
comment
Привет, Марк, спасибо за библиотеку Trie и пример кода. Я собираюсь попробовать это и следить за результатом. - person CodePull; 14.11.2017
comment
Отлично, дайте нам знать, как у вас дела. - person Mark Glasgow; 14.11.2017

Предполагая, что слова в словаре не содержат друг друга (например, «TOO» и «TOOK»), я не понимаю, почему эта проблема требует более сложного решения, чем эта однострочная функция:

static public List<string> Normalize(string input, List<string> dictionary)
{
    return dictionary.Where(a => input.Contains(a)).ToList();       
}

(Если слова ДЕЙСТВИТЕЛЬНО содержат друг друга, см. ниже.)

Полный пример:

using System;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    static public List<string> Normalize(string input, List<string> dictionary)
    {
        return dictionary.Where(a => input.Contains(a)).ToList();       
    }

    public static void Main()
    {
        List<string> dictionary = new List<string>
        {
            "COMPUTER","FIVE","CODE","COLOR","FOO"
        };
        string input = "COMPUTERFIVECODECOLORBAR";
        var normalized = Normalize(input, dictionary);
        foreach (var s in normalized)
        {
            Console.WriteLine(s);
        }
    }
}

Вывод:

COMPUTER
FIVE
CODE
COLOR

Код на DotNetFiddle

С другой стороны, если вы определили, что ваши ключевые слова ДЕЙСТВИТЕЛЬНО перекрываются, вам не повезло. Если вы уверены, что входная строка содержит только слова из словаря и что они непрерывны, вы можете использовать более сложную функцию.

    static public List<string> Normalize2(string input, List<string> dictionary)
    {
        var sorted = dictionary.OrderByDescending( a => a.Length).ToList();
        var results = new List<string>();
        bool found = false;

        do
        {
            found = false;
            foreach (var s in sorted)
            {
                if (input.StartsWith(s))
                {
                    found = true;
                    results.Add(s);
                    input = input.Substring(s.Length);
                    break;
                }
            }
        }
        while (input != "" && found);

        return results;
    }

    public static void Main()
    {
        List<string> dictionary = new List<string>
        {
            "SHORT","LONG","LONGER","FOO","FOOD"
        };
        string input = "FOODSHORTLONGERFOO";
        var normalized = Normalize2(input, dictionary);
        foreach (var s in normalized)
        {
            Console.WriteLine(s);
        }
    }

Это работает так, что он просматривает только начало строки и сначала ищет самые длинные ключевые слова. Когда он найден, он удаляет его из входной строки и продолжает поиск.

Вывод:

FOOD
SHORT
LONGER
FOO

Обратите внимание, что «LONG» не включено, потому что мы включили «LONGER», но «FOO» включено, потому что оно находится в строке отдельно от «FOOD».

Кроме того, при использовании этого второго решения ключевые слова появятся в словаре результатов в том же порядке, в котором они появились в исходной строке. Таким образом, если требуется фактически разделить фразу, а не просто определить ключевые слова в любом порядке, вам следует использовать вторую функцию.

Код

person John Wu    schedule 14.11.2017
comment
Это предполагает, что словарь находится в том же порядке, что и слова в тексте. - person Enigmativity; 14.11.2017
comment
что, если вы поместите слово PUT в свой словарь? - person Keith Nicholas; 14.11.2017
comment
@Enigmativity нет? - person Keith Nicholas; 14.11.2017
comment
большая проблема в том, что он вообще не разбивает слова, он просто пытается найти слова во входном тексте - person Keith Nicholas; 14.11.2017
comment
Как я прочитал эту задачу, нет необходимости разбивать слова. Требование заключается в обнаружении наличия ключевых слов. Разделение их является ненужным промежуточным шагом. - person John Wu; 14.11.2017
comment
в нем несколько раз упоминается разделение и разделение, попытка решения также пытается потреблять слова? - person Keith Nicholas; 14.11.2017
comment
ваша новая функция, вы не вставили версию с FOO в конце - person Keith Nicholas; 14.11.2017
comment
У людей не всегда есть правильные слова, чтобы описать то, что они имеют в виду. Из его примера кода совершенно очевидно, что сохранение исходного порядка не важно. Если порядок не важен, мы не разбиваем фразу, мы выявляем ключевые слова. Или, по крайней мере, нет никакой разницы в выводе, один по сравнению с другим. - person John Wu; 14.11.2017
comment
если это так, ПОЧЕМУ его пример не обнаружил PUT? - person Keith Nicholas; 14.11.2017
comment
@KeithNicholas - Извините, я был неоднозначен. Я имел в виду, что порядок вывода слов зависит от порядка в словаре, а не в исходном тексте. Я думаю, что ОП хочет, чтобы это было в порядке исходного текста. - person Enigmativity; 14.11.2017
comment
@Enigmativity ах, да. - person Keith Nicholas; 14.11.2017