【问题标题】:regular expression for finding parts of a string within another用于在另一个字符串中查找部分字符串的正则表达式
【发布时间】:2008-12-19 05:32:38
【问题描述】:

我有两个字符串:第一个的值为“catdog”,第二个的值为“got”。

我正在尝试找到一个正则表达式来告诉我“got”的字母是否在“catdog”中。我特别希望避免出现重复字母的情况。例如,我知道“got”是一个匹配项,但是“gott”不是一个匹配项,因为“catdog”中没有两个“t”。

编辑:

根据 Adam 在下面的回复,这是我在解决方案中必须使用的 C# 代码。感谢所有回复的人。

注意:我必须将 char 转换为 int 并减去 97 以获得数组的适当索引。在我的情况下,这些字母总是小写。

    private bool CompareParts(string a, string b)
    {

        int[] count1 = new int[26];
        int[] count2 = new int[26];

        foreach (var item in a.ToCharArray())
            count1[(int)item - 97]++;

        foreach (var item in b.ToCharArray())
            count2[(int)item - 97]++;

        for (int i = 0; i < count1.Length; i++)
            if(count2[i] > count1[i])
                return false;

        return true;
    }

【问题讨论】:

  • 使用 ToList() 上的 ForEach 扩展有一种更简洁的方法...我在下面演示了

标签: c# regex


【解决方案1】:

您为这项工作使用了错误的工具。这不是正则表达式能够轻松处理的事情。幸运的是,没有正则表达式相对容易做到这一点。您只需计算两个字符串中每个字母的出现次数,然后比较两个字符串之间的计数 - 如果对于字母表中的每个字母,第一个字符串中的计数至少与第二个字符串中的计数一样大,那么你的条件就满足了。由于您没有指定语言,以下是伪代码的答案,应该可以轻松翻译成您的语言:

bool containsParts(string1, string2)
{
    count1 = array of 26 0's
    count2 = array of 26 0's

    // Note: be sure to check for an ignore non-alphabetic characters,
    // and do case conversion if you want to do it case-insensitively
    for each character c in string1:
        count1[c]++
    for each character c in string2:
        count2[c]++

    for each character c in 'a'...'z':
        if count1[c] < count2[c]:
            return false

    return true
}

【讨论】:

  • 很好的答案 - 但我认为你的 return true 和 return false 颠倒了,不是吗?你不能早早打破并返回成功,对吗?
  • 哎呀,抓住了。现已修复。
  • 很好的答案——下面有一个更简洁的方法
【解决方案2】:

以前的建议已经提出,也许正则表达式不是最好的方法,我同意,但是,考虑到你想要实现的目标,你接受的答案有点冗长,那就是测试看看是否一组字母是另一组字母的子集。

考虑以下代码,它在一行代码中实现了这一点:

MatchString.ToList().ForEach(Item => Input.Remove(Item));

可以如下使用:

public bool IsSubSetOf(string InputString, string MatchString) 
{
  var InputChars = InputString.ToList(); 
  MatchString.ToList().ForEach(Item => InputChars.Remove(Item)); 
  return InputChars.Count == 0;
}

然后你可以调用这个方法来验证它是否是一个子集。

这里有趣的是“got”将返回一个没有项目的列表,因为匹配字符串中的每个项目只出现一次,但“gott”将返回一个包含单个项目的列表,因为只有一个调用从列表中删除“t”。因此,您将在列表中留下一个项目。也就是说,“gott”不是“catdog”的子集,但“got”是。

您可以更进一步,将方法放入静态类中:

using System;
using System.Linq;
using System.Runtime.CompilerServices;

static class extensions
{
    public static bool IsSubSetOf(this string InputString, string MatchString)
    {
        var InputChars = InputString.ToList();
        MatchString.ToList().ForEach(Item => InputChars.Remove(Item));
        return InputChars.Count == 0;
    }
}

这使您的方法成为字符串对象的扩展,从长远来看实际上使细化变得更加容易,因为您现在可以像这样进行调用:

Console.WriteLine("gott".IsSubSetOf("catdog"));

【讨论】:

  • 您的代码看起来很优雅,我只好尝试一下。出于某种原因,您的方法对我不起作用。我将它按原样直接放入我的项目中,并替换了我调用的方法。此外,运行时间几乎是我上面发布的方法的两倍。
  • @Sailing Judo: 真的吗?我直接从我的项目中剪切并粘贴到这里。事实上,从这里剪切并将其粘贴到项目中也可以。虽然 LINQ 可能不是最快的解决方案,但它的性能确实足够好,我不会为了更快的东西而放弃它......
  • @Sailing Judo:同意,我的表现不太好,但差异不会太大,不能忽视维护的便利性。运行 1,000,000,000 次迭代的测试比较,您的代码平均每次迭代产生 10 个滴答声,我的是 16...
  • @Sailing Judo: ...所以你的更快,但我的更容易维护
  • 我同意,我更喜欢你的代码。但由于某种原因,它没有给我我需要的结果。我没有探索原因,因为我已经有了一个可行的解决方案。 (其实现在想来,我可能知道原因了。我可能已经逆向测试了……)
【解决方案3】:

我不认为有一种理智的方法可以用正则表达式来做到这一点。疯狂的方法是写出所有的排列:

/^(c?a?t?d?o?g?|c?a?t?d?g?o?| ... )$/

现在,通过一些小技巧,您可以使用一些正则表达式来做到这一点(Perl 中的示例,未经测试):

$foo = 'got';
$foo =~ s/c//;
$foo =~ s/a//;
...
$foo =~ s/d//;
# if $foo is now empty, it passes the test.

理智的人当然会使用循环:

$foo = 'got'
foreach $l (split(//, 'catdog') {
    $foo =~ s/$l//;
}
# if $foo is now empty, it passes the test.

当然,有更好的方法来实现这一点,但它们不使用正则表达式。毫无疑问,如果您可以使用 Perl 的扩展正则表达式功能(如嵌入式代码),则可以使用这些方法。

【讨论】:

    【解决方案4】:

    您想要一个与这些字母完全匹配的字符串,仅匹配一次。这取决于你用什么写正则表达式,但它会像

    ^[^got]*(g|o|t)[^got]$
    

    如果你有一个“精确匹配”的运算符会有所帮助。

    【讨论】:

      【解决方案5】:

      查理·马丁几乎是正确的,但你必须对每封信做一个完整的传递。您可以使用一个正则表达式来做到这一点,方法是对除最后一遍之外的所有内容使用前瞻:

      /^
       (?=[^got]*g[^got]*$)
       (?=[^got]*o[^got]*$)
       [^got]*t[^got]*
      $/x
      

      这对磨练你的正则表达式技能是一个很好的练习,但如果我必须在现实生活中这样做,我不会这样做。非正则表达式方法将需要更多的输入,但任何最不称职的程序员都能够理解和维护它。如果您使用正则表达式,那么假设的维护者也必须在正则表达式方面的能力超过最低限度。

      【讨论】:

        【解决方案6】:

        @Adam Rosenfield 在 Python 中的解决方案:

        from collections import defaultdict
        
        def count(iterable):
            c = defaultdict(int)
            for hashable in iterable:
                c[hashable] += 1
            return c
        
        def can_spell(word, astring):
            """Whether `word` can be spelled using `astring`'s characters."""
        
            count_string = count(astring)
            count_word   = count(word)
        
            return all(count_string[c] >= count_word[c] for c in word)
        

        【讨论】:

          【解决方案7】:

          使用正则表达式的最佳方法是 IMO:

          A.对大字符串中的字符进行排序(搜索空间) 因此:把“catdog”变成“acdgot”

          B.

          1. 对搜索字符的字符串执行相同操作:“gott”变成,嗯,“gott”...

          2. 在每个字符之间插入“.*

          3. 使用后者作为正则表达式在前者中搜索。

          例如,一些 Perl 代码(如果你不介意的话):

          $main = "catdog"; $search = "gott";
          # break into individual characters, sort, and reconcatenate
          $main = join '', sort split //, $main;
          $regexp = join ".*", sort split //, $search;
          print "Debug info: search in '$main' for /$regexp/ \n";
          if($main =~ /$regexp/) {
              print "Found a match!\n";
          } else {
              print "Sorry, no match...\n";
          }
          

          打印出来:

          Debug info: search in 'acdgot' for /g.*o.*t.*t/
          Sorry, no match...
          

          放下一个“t”,你会得到一个匹配。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-12-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多