C#查找每个字符串中存在的未知子字符串

时间:2017-05-21 12:31:56

标签: c# arrays list duplicates substring

我们有List<string>。有没有办法找到(并在这种情况下删除)每个字符串中存在的未知子串?至少在情况1中,可选地在其他情况下。

            // Case 1:
            var l1 = new List<string>() {"FooOne", "FooTwo", "FooThree" };
            // Result should be:
            var r1 = new List<string>() { "One", "Two", "Three" };

            // Case 2:
            //var l2 = new List<string>() { "BarOneBar", "BarTwoBar", "BarThreeBar" };
            // Result should be:
            //var r2 = new List<string>() { "One", "Two", "Three" };

            // Case 3:
            //var l3 = new List<string>() { "OneFooTwoBar", "TwoFooThreeBar", "ThreeFooFourBar" };
            // Result should be:
            //var r3 = new List<string>() { "OneTwo", "TwoThree", "ThreeFour" };

更新 好的,案例2和3看起来无法解决。但无论如何有一种方法可以解决案例1.在这种情况下,每个字符串都以应该删除的未知字符集开头。

更新2: 我们应该尽可能多地替换重复的字符。案例1中为Foo,不是Fo,不是F

4 个答案:

答案 0 :(得分:2)

这不是一个直接的答案 - 它太大了,无法发表评论。

通过定义一些测试用例,您已经完成了重要的第一步 - 给定某些输入,您希望获得某些输出。

围绕这些测试用例创建一些单元测试并不是一个坏主意,如下所示:

使用不完整的类创建单元测试项目 - 您尚未确定它将如何执行您希望它执行的操作。你可以说我没有做太多的工作来命名它们。名称很容易改变,所以挂断它就会延误解决问题。

一个细节是我只关注问题的主要部分,找到子串。更换是另一个步骤,而且更容易。

public class UnknownSubstringFinder
{
    public IEnumerable<string>FindCommonSubstrings(IEnumerable string input)
    {

    }
}

然后写几个测试:

[TestClass]
public class UnknownSubstringFinderTests
{
    [TestMethod]
    public void FindsSubstringsCommonToEachInputString()
    {
        var subject = new UnknownSubstringFinder();
        var input = new string[]{"FooOne","FooTwo","FooThree"}
        var output = subject.FindCommonSubstrings(input).ToList();
        assert.IsTrue(output.Contains("Foo"));
    }
}

在考虑其他情况之前,您可以停止并编写类以解决该问题。但也许你已经意识到还有其他问题。

  • 是否要删除所有子字符串,或仅删除某个最小长度的子字符串? (你想删除多个字符串中出现的任何字母吗?)
  • 您希望搜索区分大小写还是不区分大小写?

基于此,在某种程度上修改类可能是有意义的。

public class UnknownSubstringFinder
{
    public IEnumerable<string>FindCommonSubstrings(IEnumerable string input, int minimumLength = 1)
    {

    }
}

然后你可以编写一些测试来确保找到所有常见的字符串。

[TestMethod]
public void FindsSubstringsCommonToEachInputString()
{
    var subject = new UnknownSubstringFinder();
    var input = new string[]{"HelloFromWorld","WorldFromHello","FromWorldHello"}
    var output = subject.FindCommonSubstrings(input, 5).ToList();
    assert.IsTrue(output.Contains("Hello"));
    assert.IsTrue(output.Contains("World"));
    assert.AreEqual(2, output.Count); // ensure no other matches
}

这种方法的有趣之处在于它可以帮助我们准确地发现我们想要实现的目标以及边缘情况。如果有我们没有想到的要求,这有助于我们看到它们。当我第一次阅读这个问题时,我并没有真正想过它。

例如,这表明需要分离查找字符串并替换它们。也许您提供输入并发现有两个匹配的子字符串,您必须决定要删除哪个。如果删除一个,则所有替换的字符串中可能不再存在另一个子字符串。

正如我所说,这不是你问题的真正答案。这只是一种帮助解决它的方法。单元测试特别有用的另一个原因是,当您解决每个场景时,它会为您提供一种简单的方法来验证您是否已经解决了所有问题,并且您解决的最后一个问题并未撤消第一个问题。在学习单元测试之前,我会通过输出到控制台并手动查看输出来查看是否得到了正确的结果。但这意味着我必须一遍又一遍地完成每个测试用例。这样您就可以运行所有测试以查看哪些案例正在运行。它更快,更可靠。

它提供了一种简单的方法来记录您期望行为的内容,而不仅仅是记住它。测试告诉你代码应该做什么。

答案 1 :(得分:2)

我不知道为什么没有人提供这个作为解决方案:

        var l1 = new List<string>() {"FooOne", "FooTwo", "FooThree" };
        var r1 = new List<string>();
        foreach (string s in l1)
        {
            r1.Add(s.Replace(UnknownString1, "").Replace(UnknownString2, ""));
        }
        // Result should be:
        var r1 = new List<string>() { "One", "Two", "Three" };
        
        // Case 1:
        var l1 = new List<string>() {"FooOne", "FooTwo", "FooThree" };
        // Case 2:
        //var l2 = new List<string>() { "BarOneBar", "BarTwoBar", "BarThreeBar" };
        // Case 3:
        //var l3 = new List<string>() { "OneFooTwoBar", "TwoFooThreeBar", "ThreeFooFourBar" };

适用于所有三种情况。无论将什么值放入 l1 列表,输出始终为 r1 = { "One", "Two", "Three" }
UnknownString1UnknownString2 变量可以更改为任何内容。
事实上,如果你把它变成一个方法,你可以做这样的事情:

        public static string RemoveString(this string str, string removalTarget)
        {
            return str.Replace(removalTarget, "");
        }
        
        public static string RemoveStrings(this string str, string[] removalTargets)
        {
            foreach (string s in removalTargets)
            {
                str = str.RemoveString(s);
            }
            return str;
        }

        public static string RemoveStringsFromList(this List<string> strs, string[] removalTargets)
        {
            List<string> result = new List<string>();
            foreach (string s in strs)
            {
                result.Add(s.RemoveStrings, removalTargets);
            }
            return result;
        }

然后你只需像这样在你的代码中实现它:

        var l3 = new List<string>() { "OneFooTwoBar", "TwoFooThreeBar", "ThreeFooFourBar" };
        var removeThis = new List<string>() { "Foo", "Bar" };

        var r3 = l3.RemoveStringsFromList(removeThis);

:-)

答案 2 :(得分:1)

您的问题是您没有所需行为的规范。 可以说“删除所有提供的字符串中存在的任何子字符串”,但最终会出现一些意外行为,例如

Input: "FooTwo", "FooThree", "FooTwelve"
Output: "wo", "hree", "welve"

您可以改为说“删除所有提供的字符串中存在的任何Pascal Case子字符串”。这适用于提供的示例,尽管提供的示例不像我的实际数据的代表性示例。

一旦你有明确定义的期望行为,你可能会发现编写实现是相当简单的。

答案 3 :(得分:1)

第一和第二案件相当简单。

基本上。你只需比较所有字符串的第一个字符,如果相同,则从所有字符串中删除第一个字符,重复此字符直到它们不相同。

然后对最后一个角色做同样的事情。

我很遗憾不会说C#。这是一些Python。无论如何,该算法在任何语言中都完全相同。我故意避免使用&#34; pythonisms&#34;在可能的情况;您需要知道的唯一特定于Python的事情是string[-1]是最后一个字符(与string[len(string)-1]相同),而字符串[:-1]是没有最后一个字符的字符串。

def remove_common_at_start_and_end(strings_to_check):

    # handle substring at the start of the lines
    finished_start = False
    while True:
        # any empty strings in the list would cause an exception so finish now
        if "" in strings_to_check:
            return strings_to_check
        # check if any first character might not be the same as the next one
        for i in range(len(strings_to_check)-1):
            if strings_to_check[i][0] != strings_to_check[i+1][0]:
                finished_start = True
        if finished_start:
            break
        # remove first character
        for i in range(len(strings_to_check)):
            strings_to_check[i]=strings_to_check[i][1:]

    # handle substring at the end of the lines
    finished_end = False
    while True:
        # any empty strings in the list would cause an exception so finish now
        if "" in strings_to_check:
            return strings_to_check
        # check if any last character might not be the same as the next one
        for i in range(len(strings_to_check)-1):
            if strings_to_check[i][-1] != strings_to_check[i+1][-1]:
                finished_end = True
        if finished_end:
            break
        # remove last character
        for i in range(len(strings_to_check)):
            strings_to_check[i]=strings_to_check[i][:-1]

    return strings_to_check

lines_to_check1=["FooOne", "FooTwo", "FooThree"]
print remove_common_at_start_and_end(lines_to_check1)
lines_to_check2=["BarOneBar", "BarTwoBar", "BarThreeBar"]
print remove_common_at_start_and_end(lines_to_check2)
lines_to_check2_2=["FooOneBar", "FooTwoBar", "FooThreeBar"]
print remove_common_at_start_and_end(lines_to_check2_2)

输出:

['One', 'Two', 'Three']
['One', 'Two', 'Three']
['One', 'Two', 'Three'] 

注意:此代码中的函数不保留作为参数提供给它的数组。可以在开始时添加副本以避免这种情况。

第三种情况是可以解决的,但我唯一的想法是循环遍历第一个字符串中的所有可能的子字符串并在其他字符串中检查它们。我现在没有时间对此进行编码。循环遍历所有可能的起始索引,然后遍历每个起始索引的所有可能的结束索引,这将获得子字符串。然后循环遍历所有其他字符串并检查它们是否包含此子字符串。然后取最长的子串并从每个字符串中删除它(如strings[i]=strings[i].replace(substring,"")中所示)。重复此过程,直到找不到常见的子字符串。

编辑:好的,我已编码了。

def remove_longest_substring(strings_to_check):
    # maximum common substring found so far
    # initialized with one character just so we don't loop through 1-char substrings
    max_substring = "1";

    # find all substring candidates
    for starting_index in range(0,len(strings_to_check[0])-1):
        # we need only the substrings longer than current max_substring
        for ending_index in range(starting_index+len(max_substring)+1,len(strings_to_check[0])+1):
            candidate_substring = strings_to_check[0][starting_index:ending_index]
            found_in_all = True
            for i in range(1,len(strings_to_check)):
                if strings_to_check[i].find(candidate_substring) == -1:
                    found_in_all = False
                    break
            if found_in_all:
                # found a new common substring longer than the previous one
                max_substring = candidate_substring
    if max_substring == "1":
        return False
    else:
        for i in range(len(strings_to_check)):
            strings_to_check[i] = strings_to_check[i].replace(max_substring,"")
        return True;

def remove_all_substrings(strings_to_check):
    while remove_longest_substring(strings_to_check):
        pass

lines_to_check1=["FooOne", "FooTwo", "FooThree"]
remove_all_substrings(lines_to_check1)
print lines_to_check1
lines_to_check2=["BarOneBar", "BarTwoBar", "BarThreeBar"]
remove_all_substrings(lines_to_check2)
print lines_to_check2
lines_to_check2_2=["FooOneBar", "FooTwoBar", "FooThreeBar"]
remove_all_substrings(lines_to_check2_2)
print lines_to_check2_2
lines_to_check3=["OneFooTwoBar", "TwoFooThreeBar", "ThreeFooFourBar"]
remove_all_substrings(lines_to_check3)
print lines_to_check3