LINQ表达式用于最短公共前缀

时间:2010-11-21 19:15:20

标签: c# linq

任何人都可以帮助我使用一个很好的LINQ表达式来转换另一个列表中的字符串列表,该列表只包含字符串的最短的不同公共前缀吗?前缀的分隔符为.

示例:["A", "A.B.D", "A", "A.B","E","F.E", "F","B.C"]

转到:["A", "E", "F", "B.C"]

移除:

  • “A.B.D”和“A.B”因为前缀“A”已经在列表中
  • “A”因为重复
  • “F.E”因为“F”已在列表中

谢谢!

12 个答案:

答案 0 :(得分:3)

你走了:

from set in
    (from item in list select item.Split('.')).GroupBy(x => x[0])
select
  set.First()
     .TakeWhile((part, index) => set.All(x => x.Length > index && x[index].Equals(part)))
     .Aggregate((x, y) => String.Format("{0}.{1}", x, y));

作为解释:

  1. 首先,我们将所有字符串拆分为'。'并按他们的第一个标记分组。
  2. 然后,我们查看每个分组的第一个元素,并在该组的每个元素继续匹配时从中获取部分(TakeWhile)。
  3. 然后,我们获取所有这些部分并使用Aggregate(String.Format)重新组合它们。

答案 1 :(得分:2)

编辑:感谢我在早期方法中指出错误的评论。

要克服这个缺点,这个查询应该有效:

var list = new List<string> { "A.B.D", "A", "A.B","E","F.E", "F","B.C", "B.C.D" };
var result = list.OrderBy(s => s)
                 .GroupBy(s => s[0])
                 .Select(g => g.First());

foreach (var s in result)
{
    Console.WriteLine(s);
}

方法不正确:

以下查询将按第一个字符对每个字符串进行分组。接下来,如果组计数有多个项目,则选择该项,否则选择单个项目。

var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = list.GroupBy(s => s[0])
                 .Select(g => g.Count() > 1 ? g.Key.ToString() : g.Single());

foreach (var s in result)
{
    Console.WriteLine(s);
}

答案 2 :(得分:2)

string[] source = {"A", "A.B", "A.B.D", "B.C", "B.C.D", "B.D", "E", "F", "F.E"};
var result = 
source.Distinct()
      .Select(str => str.Split('.'))
      .GroupBy(arr => arr[0])
      .Select(g =>
        {
          return string.Join(".", 
                 g.Aggregate((arr1, arr2) =>
                    {
                      return arr1.TakeWhile((str, index) => index < arr2.Length 
                                               && str.Equals(arr2[index]))
                                 .ToArray();
                    }));
        });

步骤:

(1)按Distinct()

删除重复的元素

(2)将每个元素拆分为一个数组,也准备好进行分组

(3)按照数组中的第一个字符串对这些数组进行分组

(4)对于每个组,通过聚合组中的所有阵列来创建一个公共前缀。聚合的逻辑是对于两个数组arr1和arr2,取arr1中的元素直到(1)超出边界(2)arr2中的对应元素是不同的

注意:我在代码中添加了两个return语句,以使其看起来更干净。如果删除return及其{}括号,则可以缩短。

答案 3 :(得分:2)

    var items = new[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
    var result = items
        .OrderBy(s => s.Length)
        .Distinct()
        .ToLookup(s => s.Substring(0, 1))
        .Select(g => g.First());

按项目长度订购项目,调用distinct以删除重复项,根据第一个字符转换为分组,然后选择每个组中的第一项。

收率:     “A”,“E”,“F”,“B.C”

修改:您可能甚至不需要Distinct作为选择每个组中的第一个项目,所以这实际上是多余的。

答案 4 :(得分:2)

Nailed it - 假设如果源列表包含“Q.X”&amp; “Q.Y”然后结果应包含“Q”。

var source = new []
{
    "A", "A.B.D", "A",
    "A.B", "E", "F.E",
    "F", "B.C",
    "Q.X", "Q.Y",
    "D.A.A", "D.A.B",
};

Func<string, int> startsWithCount =
    s => source.Where(x => x.StartsWith(s)).Count();

var results =
    (from x in source.Distinct()
    let xx = x.Split('.')
    let splits = Enumerable
        .Range(1, xx.Length)
        .Select(n => String.Join(".", xx.Take(n)))
    let first = startsWithCount(splits.First())
    select splits
        .Where(s => startsWithCount(s) == first)
        .Last()
    ).Distinct();


// results == ["A", "E", "F", "B.C", "Q", "D.A"]

答案 5 :(得分:2)

怎么样:

var possible = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var shortest = possible.Distinct().Where(x => possible.Distinct().Where(y => !y.Equals(x) && x.StartsWith(y)).Count() == 0).ToList();

它会自动检查列表,不包括相同的项目以及以任何其他项目开头的项目。我不确定效率如何:)

答案 6 :(得分:2)

我认为用一个漂亮的linq表达式来解决可能很难,所以我用linq编写了一个递归函数来解决这个问题:

class Program
{
    static void Main(string[] args)
    {
        var input = new string[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C", "B.C.D", "B.E" };

        var output = FilterFunc(input);
        foreach (var str in output)
            Console.WriteLine(str);

        Console.ReadLine();
    }

    static string[] FilterFunc(string[] input)
    {
        if (input.Length <= 1)
            return input;
        else
        {
            var firstElem = input[0];
            var indexNr = firstElem.Length;
            var maxFilteredElems = 0;
            for (int i = firstElem.Length; i > 0; i--)
            {
                var numberOfFilteredElems = input.Where(x => x.StartsWith(firstElem.Substring(0, i))).Count();
                if (numberOfFilteredElems > maxFilteredElems)
                {
                    maxFilteredElems = numberOfFilteredElems;
                    indexNr = i;
                }
            }
            var prefix = firstElem.Substring(0, indexNr);
            var recursiveResult = FilterFunc(input.Where(x => !x.StartsWith(prefix)).ToArray());
            var result = recursiveResult.ToList();
            prefix = prefix.EndsWith(".") ? prefix.Substring(0, prefix.Length - 1) : prefix;
            result.Insert(0, prefix);
            return result.ToArray();
        }
    }
}

代码可能更有效,更有条理,但现在没有时间。我认为到目前为止其他解决方案都是错误的,所以这就是为什么你得到我更长的解决方案。我认为你需要递归地解决它,以确保获得最短的列表。

答案 7 :(得分:2)

我的尝试,循环删除任何带有其他项目前缀的项目。



static void Run()
{
    var list = new string[] {"A", "A.B.D", "A",
                            "A.B", "E", "F.E",
                            "F", "B.C",
                            "Q.X", "Q.Y",
                            "D.A.A", "D.A.B"
                        };

    int size = 0;
    var prefixList = new string[list.Length];
    Array.Copy(list, prefixList, list.Length);

    for (int i = 0; i < list.Length; i++)
        prefixList 
        = prefixList
            .Where(c => !c.StartsWith(list[i]) || c == list[i])
            .Distinct()
                .ToArray();

    foreach (string s in prefixList)
        Console.WriteLine(s);
    Console.ReadLine();
}

答案 8 :(得分:2)

var list = new[] { "A.B.D", "A", "E", "A.B", "F", "F.E", "B.C.D", "B.C" };

var result = from s in list
             group s by s.Split('.').First() into g
             select LongestCommonPrefix(g);

foreach (var s in result)
{
    Console.WriteLine(s);
}

输出:

A
E
F
B.C

here找到最长公共前缀的方法(将/替换为.)。

答案 9 :(得分:1)

我对这个问题的理解是一个既包含“B.C”又包含“B.E”的列表,但没有“B”同时包含“B.C”和“B.E”。

string[] items = { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
char delimiter = '.';
var result = (from item in items.Distinct()
             where !items.Any(other => item.StartsWith(other + delimiter))
             select item).ToArray();

foreach (var item in result)
{
    Console.WriteLine(item);
}

输出

A
E
F
B.C

也适用于多字符前缀

string[] items = 
{
    "Alpha",
    "Alpha.Beta.Delta",
    "Alpha",
    "Alpha.Beta",
    "Echo",
    "Foxtrot.Echo",
    "Foxtrot",
    "Baker.Charlie"
 };

Alpha
Echo
Foxtrot
Baker.Charlie

答案 10 :(得分:-1)

var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };

var result = (list.Select(a => a.Split('.').First())).Distinct();

答案 11 :(得分:-1)

如果我严格遵守戴夫提供的定义,答案就比看起来容易:

  • 删除重复项=&gt;不同
  • 删除以列表中的任何其他项目开头的所有项目

所以我们得到:

from item in items.Distinct()
where !items.Any(other => other != item && item.StartsWith(other + '.'))
select item;

对于B.C和B.D问题,这按规定工作:两者都不包括另一个,因此dave提到的删除条件都没有被触发。

我承认可能会有更多令人兴奋的回答,但我担心这不是问题;)

更新:为where子句添加了分隔符,以便考虑多个字符。谢谢svick!