如何从字符串数组中删除非字母字符?

4
这是代码:

这是代码:

StringBuilder sb = new StringBuilder();
Regex rgx = new Regex("[^a-zA-Z0-9 -]");

var words = Regex.Split(textBox1.Text, @"(?=(?<=[^\s])\s+\w)");
for (int i = 0; i < words.Length; i++)
{
    words[i] = rgx.Replace(words[i], "");
}

当我使用Regex.Split()时,一些单词内部可能包含字符,例如: Daniel>Hello:\r\nNewhello--------------------------- 我需要仅获取不带任何标点符号的这些单词。
所以我尝试了以下循环,但在单词中发现了许多具有""的位置,以及一些只有------------------------的位置。
我无法在我的代码中将它们用作字符串。

你可以使用正则表达式来实现,例如 (\w+|\s+)。这将返回所有单词(不包括数字),并且它们之间有一个或多个空格。 - Automatico
2
@Cort3z,\w可以匹配字母、数字和下划线(或更多的Unicode字符)。如果您只想匹配字母,那么您可能需要使用[a-zA-Z] - Joe Enos
@JoeEnos 是的,完全忘记了。 - Automatico
1
@Cort3z 是的,我几天前也做了同样的事情。 - Joe Enos
3个回答

11

你不需要正则表达式来清除非字母字符。这将删除所有非Unicode字母。

public string RemoveNonUnicodeLetters(string input)
{
    StringBuilder sb = new StringBuilder();
    foreach(char c in input)
    {
        if(Char.IsLetter(c))
           sb.Append(c);
    }

    return sb.ToString();
}

或者,如果您只想允许拉丁字母,您可以使用此代码

public string RemoveNonLatinLetters(string input)
{
    StringBuilder sb = new StringBuilder();
    foreach(char c in input)
    {
        if(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
           sb.Append(c);
    }

    return sb.ToString();
}

基准测试 vs 正则表达式

public static string RemoveNonUnicodeLetters(string input)
{
       StringBuilder sb = new StringBuilder();
       foreach (char c in input)
       {
            if (Char.IsLetter(c))
                sb.Append(c);
       }

            return sb.ToString();
}



static readonly Regex nonUnicodeRx = new Regex("\\P{L}");

public static string RemoveNonUnicodeLetters2(string input)
{
     return nonUnicodeRx.Replace(input, "");
}


static void Main(string[] args)
{

    Stopwatch sw = new Stopwatch();

    StringBuilder sb = new StringBuilder();


    //generate guids as input
    for (int j = 0; j < 1000; j++)
    {
        sb.Append(Guid.NewGuid().ToString());
    }

    string input = sb.ToString();

    sw.Start();

    for (int i = 0; i < 1000; i++)
    {
        RemoveNonUnicodeLetters(input);
    }

    sw.Stop();
    Console.WriteLine("SM: " + sw.ElapsedMilliseconds);

    sw.Restart();
    for (int i = 0; i < 1000; i++)
    {
        RemoveNonUnicodeLetters2(input);
    }

    sw.Stop();
    Console.WriteLine("RX: " + sw.ElapsedMilliseconds);


}

输出(SM = 字符串操作,RX = 正则表达式)

SM: 581
RX: 9882

SM: 545
RX: 9557

SM: 664
RX: 10196

3
在描述方面,我敢打赌人们会更喜欢方法名称而不是正则表达式。在我看来,正则表达式适用于字符串解析过于复杂或冗长的情况。 - keyboardP
1
把正则表达式放在一个方法里。 - Konrad Rudolph
3
我陪伴你。正则表达式很棒,但清晰易懂的代码更好。 - Joe Enos
1
@KonradRudolph 对我来说,你不能只解决今天的问题。你还必须为明天做计划。假设明天它变成“字母、空格、正好两个单词,第一个单词是3-6个字符,第二个单词是4-10个字符,整个字符串在10-15个字符之间,正好有4个元音字母。”(荒谬的例子,但你知道我的意思)。当然,它可能仍然是一个使用RegEx的一行代码,但现在它是一个更长的表达式,你可能需要彻底测试它,并且可能不容易阅读。没有RegEx,它只是几行简单易读的代码。 - Joe Enos
1
我同意正则表达式有时会被不必要地避免,但我想补充说,在这种情况下,正则表达式也更昂贵,实际上会更慢。当然,如果是另一种情况,情况就会反过来,但对于像这样的基本清理,我认为字符串迭代应该非常快。 - keyboardP
显示剩余9条评论

2

keyboardP的解决方案还不错,可以考虑一下。但是正如我在评论中所说的那样,正则表达式实际上是这个工作的正确工具,你只是让它变得不必要地复杂了。实际上的解决方案只需要一行代码:

var result = Regex.Replace(input, "\\P{L}", "");

\P{...}指定了我们不想匹配的Unicode字符类(与\p{...}相反)。L是表示字母的Unicode字符类。

当然,将其封装成一个方法是有意义的,就像keyboardP所做的那样。为了避免重复编译正则表达式,您还应该考虑将正则表达式的创建从实际代码中提取出来(尽管这可能不会对性能产生很大影响):

static readonly Regex nonUnicodeRx = new Regex("\\P{L}");

public static string RemoveNonUnicodeLetters(string input) {
    return nonUnicodeRx.Replace(input, "");
}

3
我有约35位同事,或许只有一两个人能理解“\P{L}”这个正则表达式。我必须承认公司没有雇佣顶级人才,但你可能明白这对于可维护性意味着什么。在开发时,我经常使用正则表达式,但在重要代码中可以轻松避免它们时,我不会使用它们。 - Maarten Bodewes
@owlstead 所以在旁边加上注释。这不是使用正确工具的有效理由。相反,你应该学习这个工具-或者,在你的情况下,教育同事们。是的,对于未经训练的人来说,正则表达式很神秘,但条件运算符也是如此,然而有压倒性的共识认为你应该使用这样的习语。我甚至不确定这里是否适合使用注释-只要存在正则表达式的适当文档,代码就完全可以自我解释。 - Konrad Rudolph

2
为了帮助Konrad和keyboardP解决分歧,我运行了一个基准测试,使用他们的代码。结果发现,keyboardP的代码比Konrad的代码快10倍。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                string input = "asdf234!@#*advfk234098awfdasdfq9823fna943";
                DateTime start = DateTime.Now;
                for (int i = 0; i < 100000; i++)
                {
                    RemoveNonUnicodeLetters(input);
                }
                Console.WriteLine(DateTime.Now.Subtract(start).TotalSeconds);
                start = DateTime.Now;
                for (int i = 0; i < 100000; i++)
                {
                    RemoveNonUnicodeLetters2(input);
                }
                Console.WriteLine(DateTime.Now.Subtract(start).TotalSeconds);
            }
            public static string RemoveNonUnicodeLetters(string input)
            {
                StringBuilder sb = new StringBuilder();
                foreach (char c in input)
                {
                    if (Char.IsLetter(c))
                        sb.Append(c);
                }

                return sb.ToString();
            }
            public static string RemoveNonUnicodeLetters2(string input)
            {
                var result = Regex.Replace(input, "\\P{L}", "");
                return result;
            }
        }
    }

我得到了

0.12
1.2

作为输出
更新:
为了确定是正则表达式编译导致正则表达式方法变慢,我将正则表达式放入一个只会被构造一次的静态变量中。
            static Regex rex = new Regex("\\P{L}");
            public static string RemoveNonUnicodeLetters2(string input)
            {
                var result = rex.Replace(input,m => "");
                return result;
            }

但是这对运行时没有影响。


谢谢你的基准测试。我刚刚添加了我的测试,使用更少的循环和更长的字符串输入,结果是相关的。 - keyboardP
1
只是为了好玩,如果你对char[]进行基准测试而不是StringBuilder,你会得到更好的结果,大约提高了10%。(构建一个临时数组,大小与字符串相同,循环遍历字符串,将匹配项填充到临时数组中,然后将临时数组复制到正确大小的新数组中,并将其传递到string构造函数中)。 - Joe Enos
对于更复杂的正则表达式,提取创建实际上对运行时间有重大影响。我仍然对.NET的正则表达式实现感到非常失望。正则表达式应该非常快速。顺便说一句,您的基准测试代码并不是非常可靠,您应该使用StopWatch,您可能应该使用更多迭代(尽管在这种情况下可能只是好的),并且您应该交错测试调用以减轻可能会偏向结果的后台进程的周期性影响。理想情况下,您还应该绘制分布图以确保没有异常值使平均值产生偏差。 - Konrad Rudolph
只有在观察15毫秒以下的时间时,“StopWatch”才更准确。而“DateTime.Now”可以精确到15毫秒。我运行的时间比那长了100倍。100K次迭代已经足够了。我只是增加了它,直到我得到大约1秒的运行时间-这相当于数十亿个CPU周期。我运行了两次代码并得到了相同的结果,然后发布了结果。 - Mark Lakata
2
@Joe Enos,如果您使用字符串的大小预先分配StringBuilder(new StringBuilder(string.Length)),则由于StringBuilder正在运行时分配16字节、32字节、然后64字节,因此您可能会获得类似的10%速度提升。这对于StringBuilder来说是一个很好的加速,但我几乎从未在网上看到过它被使用。 - PRMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接