C#将具有混合语言的字符串拆分为不同的语言块

时间:2017-08-10 16:55:21

标签: c# split multilingual

我正在尝试解决一个问题,即我有一个混合语言的字符串作为输入。

  

E.g。 “Hyundai Motor Company현대자동차现代其他一些英语单词”

我想将字符串拆分为不同的语言块

  

E.g。 [“现代汽车公司”,“현대자동차”,“现代”,“其他一些英文单词”]

OR(空格/标点符号和顺序无关紧要)

  

[“HyundaiMotorCompany”,“현대자동차”,“现代”,“SomeotherEnglishwords”]

有没有简单的方法来解决这个问题? 或者我可以使用任何组装/ nuget包?

由于

编辑: 我认为我的“语言块”含糊不清。 我想要的“语言块”是语言字符集。

  

例如“现代汽车公司”是英文字符集,“현대자동차”在韩文集中,“现代”在中文集中,“其他一些英文单词”在英文集中。

澄清我的问题要求的补充是:

1:输入可以有空格或任何其他标点符号,但我总是可以使用正则表达式来忽略它们。

2:我将预处理输入以忽略变音符号。所以“å”在我的输入中变成了“a”。所以所有英文字符都会成为英文字符。

我真正想要的是找到一种方法来将输入解析为不同的语言字符集,忽略空格&标点符号。

  

E.g。来自“HyundaiMotorCompany현대자동차现代SomeotherEnglishwords”

     

至[“HyundaiMotorCompany”,“현대자동차”,“现代”,“其他英语词汇”]

3 个答案:

答案 0 :(得分:4)

这是一个language identification problem。您需要使用适当的库。有C# package支持在维基百科和Twitter上培训的78种语言。但一般来说Python更适合解决此类问题。对于Python,我可以推荐this package

因此,您需要将文本拆分为句子或单词,并应用文本检测算法来识别语言。接下来,您可以按语言对结果进行分组。

答案 1 :(得分:4)

可以使用UNICODE块定义

语言块。当前的UNICODE块列表可在ftp://www.unicode.org/Public/UNIDATA/Blocks.txt处获得。以下是摘录中的摘录:

0000..007F; Basic Latin
0080..00FF; Latin-1 Supplement
0100..017F; Latin Extended-A
0180..024F; Latin Extended-B
0250..02AF; IPA Extensions
02B0..02FF; Spacing Modifier Letters
0300..036F; Combining Diacritical Marks
0370..03FF; Greek and Coptic
0400..04FF; Cyrillic
0500..052F; Cyrillic Supplement

我们的想法是使用UNICODE块对字符进行分类。属于同一UNICODE块的连续字符定义语言块

此定义的第一个问题是您可能会考虑单个脚本(或语言)跨越多个块,例如 Cyrillic Cyrillic Supplement 。要处理此问题,您可以合并包含相同名称的块,以便将所有 Latin 块合并为单个 Latin 脚本等。

但是,这会产生一些新问题:

  1. 希腊语和科普特语科普特希腊语补充是否应合并到一个脚本中,或者您应该尝试区分希腊文和科普特文字?
  2. 您应该合并所有 CJK 块。但是,因为这些块包含中文以及汉字(日文)和汉字(韩文)字符,所以当使用CJK字符时,您将无法区分这些脚本。
  3. 假设您有一个如何使用UNICODE块将字符分类为脚本的计划,那么您必须决定如何处理间距和标点符号。空格字符和几种标点符号属于 Basic Latin 块。但是,其他块也可能包含非字母字符。

    处理这个问题的策略是“忽略”" UNICODE非字母字符块,但包含在块中。在您的示例中,您有两个非拉丁块,恰好不包含空格或标点符号,但许多脚本将使用在拉丁文脚本中使用的空格,例如西里尔。即使一个空格被分类为 Latin ,你仍然希望用空格分隔的西里尔字母序列被认为是使用西里尔字母的单个块而不是西里尔字,后跟拉丁空间然后是另一个西里尔字等。

    最后,您需要决定如何处理数字。您可以将它们视为空格和标点符号,或将它们分类为它们所属的块,例如:拉丁数字是拉丁语,而梵文数字是梵文等。

    以下是将所有这些放在一起的代码。首先是一个代表脚本的类(基于UNICODE块,如"希腊语和科普特语":0x0370 - 0x03FF):

    public class Script
    {
        public Script(int from, int to, string name)
        {
            From = from;
            To = to;
            Name = name;
        }
    
        public int From { get; }
        public int To { get; }
        public string Name { get; }
    
        public bool Contains(char c) => From <= (int) c && (int) c <= To;
    }
    

    接下来是一个用于下载和解析UNICODE块文件的类。此代码下载构造函数中可能不太理想的文本。相反,您可以使用该文件的本地副本或类似的东西。

    public class Scripts
    {
        readonly List<Script> scripts;
    
        public Scripts()
        {
            using (var webClient = new WebClient())
            {
                const string url = "ftp://www.unicode.org/Public/UNIDATA/Blocks.txt";
                var blocks = webClient.DownloadString(url);
                var regex = new Regex(@"^(?<from>[0-9A-F]{4})\.\.(?<to>[0-9A-F]{4}); (?<name>.+)$");
                scripts = blocks
                    .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(line => regex.Match(line))
                    .Where(match => match.Success)
                    .Select(match => new Script(
                        Convert.ToInt32(match.Groups["from"].Value, 16),
                        Convert.ToInt32(match.Groups["to"].Value, 16),
                        NormalizeName(match.Groups["name"].Value)))
                    .ToList();
            }
        }
    
        public string GetScript(char c)
        {
            if (!char.IsLetterOrDigit(c))
                // Use the empty string to signal space and punctuation.
                return string.Empty;
            // Linear search - can be improved by using binary search.
            foreach (var script in scripts)
                if (script.Contains(c))
                    return script.Name;
            return string.Empty;
        }
    
        // Add more special names if required.
        readonly string[] specialNames = new[] { "Latin", "Cyrillic", "Arabic", "CJK" };
    
        string NormalizeName(string name) => specialNames.FirstOrDefault(sn => name.Contains(sn)) ?? name;
    }
    

    请注意,忽略UNICODE代码点0xFFFF以上的块。如果你必须使用这些字符,你将不得不扩展我提供的代码,它假定UNICODE字符由16位值表示。

    下一个任务是将字符串拆分为UNICODE块。它将返回由属于同一脚本(元组的第二个元素)的一串连续字符组成的单词。 scripts变量是上面定义的Scripts类的实例。

    public IEnumerable<(string text, string script)> SplitIntoWords(string text)
    {
        if (text.Length == 0)
            yield break;
        var script = scripts.GetScript(text[0]);
        var start = 0;
        for (var i = 1; i < text.Length - 1; i += 1)
        {
            var nextScript = scripts.GetScript(text[i]);
            if (nextScript != script)
            {
                yield return (text.Substring(start, i - start), script);
                start = i;
                script = nextScript;
            }
        }
        yield return (text.Substring(start, text.Length - start), script);
    }
    

    对您的文字执行SplitIntoWords将返回以下内容:

    Text      | Script
    ----------+----------------
    Hyundai   | Latin
    [space]   | [empty string]
    Motor     | Latin
    [space]   | [empty string]
    Company   | Latin
    [space]   | [empty string]
    현대자동차 | Hangul Syllables
    [space]   | [empty string]
    现代      | CJK
    ...
    

    下一步是连接属于同一脚本的连续单词,忽略空格和标点符号:

    public IEnumerable<string> JoinWords(IEnumerable<(string text, string script)> words)
    {
        using (var enumerator = words.GetEnumerator())
        {
            if (!enumerator.MoveNext())
                yield break;
            var (text, script) = enumerator.Current;
            var stringBuilder = new StringBuilder(text);
            while (enumerator.MoveNext())
            {
                var (nextText, nextScript) = enumerator.Current;
                if (script == string.Empty)
                {
                    stringBuilder.Append(nextText);
                    script = nextScript;
                }
                else if (nextScript != string.Empty && nextScript != script)
                {
                    yield return stringBuilder.ToString();
                    stringBuilder = new StringBuilder(nextText);
                    script = nextScript;
                }
                else
                    stringBuilder.Append(nextText);
            }
            yield return stringBuilder.ToString();
        }
    }
    

    此代码将包含使用相同脚本的前面单词的任何空格和标点符号。

    全部放在一起:

    var chunks = JoinWords(SplitIntoWords(text));
    

    这将导致这些块:

    • 现代汽车公司
    • 현대자동차
    • 现代
    • 其他一些英文单词

    除了最后一个块之外的所有块都有一个尾随空格。

答案 2 :(得分:0)

据我所知,您希望区分英语和非英语(Unicode)字符。我们可以在这里使用[\x00-\x7F]+正则表达式。请注意,^用于非英语字符。

string input = "Hyundai Motor Company 현대자동차 现代 Some other English words";

string englishCharsPattern = "[\x00-\x7F]+";
var englishParts = Regex.Matches(input, englishCharsPattern)
                        .OfType<Match>()
                        .Where(m => !string.IsNullOrWhiteSpace(m.Groups[0].Value))
                        .Select(m => m.Groups[0].Value.Trim())
                        .ToList();

string nonEnglishCharsPattern = "[^\x00-\x7F]+";
var nonEnglishParts = Regex.Matches(input, nonEnglishCharsPattern)
                            .OfType<Match>()
                            .Select(m => m.Groups[0].Value)
                            .ToList();

var finalParts = englishParts;
finalParts.AddRange(nonEnglishParts);

Console.WriteLine(string.Join(",", finalParts.ToArray()));  

这给了我们:

Hyundai Motor Company,Some other English words,현대자동차,现代