任何人都可以帮助我使用一个很好的LINQ表达式来转换另一个列表中的字符串列表,该列表只包含字符串的最短的不同公共前缀吗?前缀的分隔符为.
。
示例:["A", "A.B.D", "A", "A.B","E","F.E", "F","B.C"]
转到:["A", "E", "F", "B.C"]
移除:
谢谢!
答案 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)
编辑:感谢我在早期方法中指出错误的评论。
要克服这个缺点,这个查询应该有效:
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)
如果我严格遵守戴夫提供的定义,答案就比看起来容易:
所以我们得到:
from item in items.Distinct()
where !items.Any(other => other != item && item.StartsWith(other + '.'))
select item;
对于B.C和B.D问题,这按规定工作:两者都不包括另一个,因此dave提到的删除条件都没有被触发。
我承认可能会有更多令人兴奋的回答,但我担心这不是问题;)
更新:为where子句添加了分隔符,以便考虑多个字符。谢谢svick!