簡體   English   中英

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

[英]C# Find unknown substring that exists in each string

我們有一個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:我們應該盡可能多地替換重復的字符。 Foo在情況 1 中,不是Fo ,不是F

這不是一個直接的答案 - 它太大而無法發表評論。

通過定義一些測試用例,您已經完成了重要的第一步 - 給定某些輸入,您期望某些輸出。

圍繞這些測試用例創建一些單元測試並不是一個壞主意,如下所示:

使用您未完成的類創建一個單元測試項目 - 您尚未確定它將如何執行您希望它執行的操作。 你可以說我沒有花太多精力來命名它們。 名稱很容易更改,因此掛在它上面只會延遲解決問題。

一個細節是我只關注問題的主要部分,找到子字符串。 更換是另一個步驟,而且要容易得多。

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
}

這種方法的有趣之處在於,它可以幫助我們准確地發現我們正在嘗試完成的任務以及邊緣情況可能是什么。 如果有我們沒有想到的需求,這有助於我們看到它們。 當我第一次閱讀這個問題時,我並沒有真正考慮過。

例如,這表明需要將查找字符串和替換它們分開。 也許您提供一個輸入並發現有兩個匹配的子字符串,您必須決定刪除哪一個。 如果刪除一個,另一個子字符串可能不再出現在所有替換的字符串中。

正如我所說,這並不是您問題的真正答案。 這只是幫助解決問題的一種方式。 單元測試特別有用的另一個原因是,當您解決每個場景時,它為您提供了一種簡單的方法來驗證您是否已經解決了所有問題,並且您解決的最后一個不會撤消第一個。 在學習單元測試之前,我會通過輸出到控制台並手動查看輸出來查看我是否得到正確的結果來完成此操作。 但這意味着我必須一遍又一遍地檢查每個測試用例。 通過這種方式,您可以運行所有測試以查看哪些案例有效。 它更快,更可靠。

它提供了一種簡單的方法來記錄您期望的行為,而不僅僅是記住它。 測試會告訴您代碼應該做什么。

我不知道為什么沒有人提供這個作為解決方案:

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

:-)

您的問題是您沒有所需行為的規范。 可以說“刪除所有提供的字符串中存在的任何子字符串”,但最終可能會出現一些意外行為,例如

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

您可以改為說“刪除所有提供的字符串中存在的任何 Pascal Case 子字符串”。 這適用於提供的示例,盡管在我看來,提供的示例不像您的真實數據的代表性示例。

一旦您有明確定義的所需行為,您可能會發現編寫實現是相當簡單的。

案例一和案例二相當簡單。

基本上。 您只需比較所有字符串的第一個字符,如果相同,則從所有字符串中刪除第一個字符,重復此操作直到它們不相同。

然后對最后一個字符做完全相同的事情。

遺憾的是我不會說 C#。 這是一些 Python。 無論如何,該算法在任何語言中都完全相同。 在可能的情況下,我故意避免使用“pythonisms”; 您需要知道的唯一特定於 Python 的事情是string[-1]是最后一個字符(與string[len(string)-1] )並且 string [:-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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM