查找字符串的公共前缀

use*_*334 26 c# string pattern-matching

我有4个字符串:

"h:/a/b/c"
"h:/a/b/d"
"h:/a/b/e"
"h:/a/c"
Run Code Online (Sandbox Code Playgroud)

我想找到这些字符串的公共前缀,即"h:/a".怎么找到?

通常我会用分隔符拆分字符串'/'并将其放在另一个列表中,依此类推.
有没有更好的方法呢?

dtb*_*dtb 35

string[] xs = new[] { "h:/a/b/c", "h:/a/b/d", "h:/a/b/e", "h:/a/c" };

string x = string.Join("/", xs.Select(s => s.Split('/').AsEnumerable())
                              .Transpose()
                              .TakeWhile(s => s.All(d => d == s.First()))
                              .Select(s => s.First()));
Run Code Online (Sandbox Code Playgroud)

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source)
{
    var enumerators = source.Select(e => e.GetEnumerator()).ToArray();
    try
    {
        while (enumerators.All(e => e.MoveNext()))
        {
            yield return enumerators.Select(e => e.Current).ToArray();
        }
    }
    finally
    {
        Array.ForEach(enumerators, e => e.Dispose());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 一个聪明的功能方法!感谢您的贡献,dtb - 阅读您的内容总是很有趣. (2认同)

Yeg*_*gor 18

我的一个简短的LINQy解决方案.

var samples = new[] { "h:/a/b/c", "h:/a/b/d", "h:/a/b/e", "h:/a/e" };

var commonPrefix = new string(
    samples.First().Substring(0, samples.Min(s => s.Length))
    .TakeWhile((c, i) => samples.All(s => s[i] == c)).ToArray());
Run Code Online (Sandbox Code Playgroud)

  • 在我看来,这里的想法是获取列表中最短的字符串。`.Min()` 不一定能得到最短的字符串。`{"aa", "b"}.Min()` 返回“aa”,因为“a”排序低于/早于“b”。 (2认同)

Sam*_*der 15

只需循环最短字符串的字符,并将每个字符与其他字符串中相同位置的字符进行比较.虽然他们都匹配继续前进.只要一个不匹配,那么直到当前位置-1的字符串就是答案.

像(伪代码)的东西

int count=0;
foreach(char c in shortestString)
{
    foreach(string s in otherStrings)
    {
        if (s[count]!=c)
        {
             return shortestString.SubString(0,count-1); //need to check count is not 0 
        }
    }
    count+=1;
 }
 return shortestString;
Run Code Online (Sandbox Code Playgroud)

  • 如果你只有20个字符串我不认为你需要担心效率,但我不确定你能做什么,因为你需要检查每个字符串以确保它们是相同的.如果你知道这些字符串是否可能是常见的,你或许可以做得更好. (3认同)

Sim*_*n D 6

基于Sam Holder解决方案的工作代码(注意它给出h:/ a/not h:/ a作为问题中最长的公共初始子字符串):

using System;

namespace CommonPrefix
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(CommonPrefix(new[] { "h:/a/b/c", "h:/a/b/d", "h:/a/b/e", "h:/a/c" })); // "h:/a/"
            Console.WriteLine(CommonPrefix(new[] { "abc", "abc" })); // "abc"
            Console.WriteLine(CommonPrefix(new[] { "abc" })); // "abc"
            Console.WriteLine(CommonPrefix(new string[] { })); // ""
            Console.WriteLine(CommonPrefix(new[] { "a", "abc" })); // "a"
            Console.WriteLine(CommonPrefix(new[] { "abc", "a" })); // "a"

            Console.ReadKey();
        }

        private static string CommonPrefix(string[] ss)
        {
            if (ss.Length == 0)
            {
                return "";
            }

            if (ss.Length == 1)
            {
                return ss[0];
            }

            int prefixLength = 0;

            foreach (char c in ss[0])
            {
                foreach (string s in ss)
                {
                    if (s.Length <= prefixLength || s[prefixLength] != c)
                    {
                        return ss[0].Substring(0, prefixLength);
                    }
                }
                prefixLength++;
            }

            return ss[0]; // all strings identical up to length of ss[0]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 一个小问题 - 您对代码底部的 return 语句的评论指出“所有字符串相同”。那不是真的。该函数执行的测试仅测试数组 ss 中其他字符串的根是否与 ss[0] 匹配,直到 ss[0] 的长度。数组 ss 中的其他字符串可能比 ss[0] 长。尽管如此,在我看来,代码正确地执行了所需的任务。 (2认同)

Joh*_*lla 5

这是最常见的子字符串问题(虽然它只是一个稍微专业的情况,因为您似乎只关心前缀)..NET平台中没有可以直接调用的算法库实现,但是这里链接的文章充满了你自己如何做的步骤.

  • @MartinStettner我认为你不应该拒绝答案来提出你认为正确的答案.你应该对你认为错误的答案进行投票,然后提出你认为正确的答案.如果有足够的人同意你的话,答案会自然而然地升到顶峰.如果标记为已接受,则正确答案将位于顶部. (3认同)
  • 鉴于只搜索了一个公共前缀,一般最长的公共子串算法将是一个可怕的过度杀伤(O(n ^ 2)对O(n)只有两个字符串......).问题不是"稍微专业化的案例",而是更容易解决的问题:-).通常情况下,我会给-1(为了给出正确的答案),但考虑到这个问题被改写了,我会留下它:-) ... (2认同)