【问题标题】:C# - Split Fully Uppercase String Into Separate Words (No Spaces)C# - 将完全大写的字符串拆分为单独的单词(无空格)
【发布时间】:2018-04-26 20:41:17
【问题描述】:

我目前正在从事一个项目,我需要将单个单词与字符串分开。问题是字符串中的所有单词都是大写的并且没有空格。以下是程序正在接收的输入类型的示例:

“电脑五码色”

这应该分成以下结果:

“电脑” “五” “代码” “颜色”

到目前为止,我一直在使用以下方法来拆分我的字符串(它适用于除此边缘情况之外的所有场景):

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;
        }

如果有人对如何处理这个问题有任何想法,我会很高兴听到他们的声音。另外,如果我可以提供更多信息,请告诉我。

【问题讨论】:

  • 如果没有任何东西来界定一个词是什么,你如何确定一个词?您对单词类型或允许单词的字典有某种限制吗?即便如此,想象一下你得到了“GRAINSTOP”。应该解析为“GRAIN”“STOP”还是“GRAINS”“TOP”?
  • 在您的算法中要考虑的是如何处理字符组合,这些字符本身就是单词,但也是另一个单词的子字符串。例如,如果“TELEVISION”作为您输入的一部分出现,那么您的单词列表中的“VISION”可能会消耗字母并留下“TELE”本身以保持不匹配。如果担心这种情况,您可能会受益于按长度降序对源单词列表进行排序。

标签: c# string split text-parsing


【解决方案1】:

我正在对您希望如何搜索匹配的单词做出一些假设。首先,在给定的字符索引处,将优先考虑字典中最长的匹配词。 其次,如果在给定的字符索引处没有找到单词,我们会继续下一个字符并再次搜索。

下面的实现使用Trie 来索引所有有效单词的字典。我们不是遍历字典中的每个单词,而是遍历输入字符串中的每个字符,寻找最长的单词。

我从这个非常方便的 SO 答案中提升了 C# 中 Trie 的实现:https://stackoverflow.com/a/6073004

编辑:修复了在 Trie 中添加作为现有单词的子字符串的单词时出现的错误,例如 Emergency then 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

【讨论】:

  • 嗨,马克,感谢 Trie 库和代码示例。我将尝试一下并跟进结果。
  • 太好了,让我们知道你的进展情况。
【解决方案2】:

假设字典中的单词不相互包含(例如“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

Code on 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”不同的字符串中。

此外,使用第二种解决方案,关键字将按照它们在原始字符串中出现的顺序出现在结果字典中。因此,如果要求实际拆分短语而不是仅以任何顺序检测关键字,则应使用第二个函数。

Code

【讨论】:

  • 这假定字典与在文本中找到单词的顺序相同。
  • 如果你把PUT这个词放到你的字典里呢?
  • @Enigmativity 不是吗?
  • 更大的问题是它根本不拆分单词,它只是尝试在输入文本中查找单词
  • 我读这个问题的方式,不需要“拆分”单词。要求是检测关键字的存在。拆分它们是不必要的中间步骤。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-02
  • 1970-01-01
  • 1970-01-01
  • 2011-06-12
  • 1970-01-01
相关资源
最近更新 更多