C# LINQ:将一系列字符串折叠为一组;“范围”;
我有一个类似于此的字符串数组(显示在单独的行中以说明模式): …目标是将这些字符串折叠成逗号分隔的“范围”字符串: 我想折叠它们,这样我就可以构建一个URL。有数百个元素,但如果我以这种方式折叠它们,我仍然可以传递所有信息——将它们全部放入URL“longhand”(必须是GET,而不是POST)是不可行的C# LINQ:将一系列字符串折叠为一组;“范围”;,c#,string,linq,C#,String,Linq,我有一个类似于此的字符串数组(显示在单独的行中以说明模式): …目标是将这些字符串折叠成逗号分隔的“范围”字符串: 我想折叠它们,这样我就可以构建一个URL。有数百个元素,但如果我以这种方式折叠它们,我仍然可以传递所有信息——将它们全部放入URL“longhand”(必须是GET,而不是POST)是不可行的 我有个想法,用前两个字符作为键将它们分成几组,但是有人有什么聪明的想法来将这些序列(没有间隙)分解成不同的范围吗?我正在努力解决这个问题,我想到的所有东西看起来都像意大利面条。因此,您需要做
我有个想法,用前两个字符作为键将它们分成几组,但是有人有什么聪明的想法来将这些序列(没有间隙)分解成不同的范围吗?我正在努力解决这个问题,我想到的所有东西看起来都像意大利面条。因此,您需要做的第一件事就是解析字符串。将字母前缀和整数值分开是很重要的 接下来,您要对前缀上的项目进行分组 对于该组中的每个项目,您希望按编号对其进行排序,然后在上一个值的编号比当前项目的编号小一的情况下对项目进行分组。(或者,换句话说,前一项加上一项等于当前项。) 将所有这些项目分组后,要根据该范围的前缀以及第一个和最后一个数字将该组投影到一个值。不需要这些小组提供其他信息 然后,我们将每个组的字符串列表展平为一个常规的字符串列表,因为一旦我们都完成了,就不需要从不同的组中分离出范围。使用
SelectMany
完成此操作
当这一切都说了又做了,翻译成代码,是这样的:
public static IEnumerable<string> Foo(IEnumerable<string> data)
{
return data.Select(item => new
{
Prefix = item.Substring(0, 2),
Number = int.Parse(item.Substring(2))
})
.GroupBy(item => item.Prefix)
.SelectMany(group => group.OrderBy(item => item.Number)
.GroupWhile((prev, current) =>
prev.Number + 1 == current.Number)
.Select(range =>
RangeAsString(group.Key,
range.First().Number,
range.Last().Number)));
}
然后使用简单的帮助器方法将每个范围转换为字符串:
private static string RangeAsString(string prefix, int start, int end)
{
if (start == end)
return prefix + start;
else
return string.Format("{0}{1}-{0}{2}", prefix, start, end);
}
因此,您需要做的第一件事是解析字符串。将字母前缀和整数值分开是很重要的 接下来,您要对前缀上的项目进行分组 对于该组中的每个项目,您希望按编号对其进行排序,然后在上一个值的编号比当前项目的编号小一的情况下对项目进行分组。(或者,换句话说,前一项加上一项等于当前项。) 将所有这些项目分组后,要根据该范围的前缀以及第一个和最后一个数字将该组投影到一个值。不需要这些小组提供其他信息 然后,我们将每个组的字符串列表展平为一个常规的字符串列表,因为一旦我们都完成了,就不需要从不同的组中分离出范围。使用
SelectMany
完成此操作
当这一切都说了又做了,翻译成代码,是这样的:
public static IEnumerable<string> Foo(IEnumerable<string> data)
{
return data.Select(item => new
{
Prefix = item.Substring(0, 2),
Number = int.Parse(item.Substring(2))
})
.GroupBy(item => item.Prefix)
.SelectMany(group => group.OrderBy(item => item.Number)
.GroupWhile((prev, current) =>
prev.Number + 1 == current.Number)
.Select(range =>
RangeAsString(group.Key,
range.First().Number,
range.Last().Number)));
}
然后使用简单的帮助器方法将每个范围转换为字符串:
private static string RangeAsString(string prefix, int start, int end)
{
if (start == end)
return prefix + start;
else
return string.Format("{0}{1}-{0}{2}", prefix, start, end);
}
以下是不需要添加新扩展方法的LINQ版本:
var data2 = data.Skip(1).Zip(data, (d1, d0) => new
{
value = d1,
jump = d1.Substring(0, 2) == d0.Substring(0, 2)
? int.Parse(d1.Substring(2)) - int.Parse(d0.Substring(2))
: -1,
});
var agg = new { f = data.First(), t = data.First(), };
var query2 =
data2
.Aggregate(new [] { agg }.ToList(), (a, x) =>
{
var last = a.Last();
if (x.jump == 1)
{
a.RemoveAt(a.Count() - 1);
a.Add(new { f = last.f, t = x.value, });
}
else
{
a.Add(new { f = x.value, t = x.value, });
}
return a;
});
var query3 =
from q in query2
select (q.f) + (q.f == q.t ? "" : "-" + q.t);
我得到以下结果:
这是一个LINQ版本,无需添加新的扩展方法:
var data2 = data.Skip(1).Zip(data, (d1, d0) => new
{
value = d1,
jump = d1.Substring(0, 2) == d0.Substring(0, 2)
? int.Parse(d1.Substring(2)) - int.Parse(d0.Substring(2))
: -1,
});
var agg = new { f = data.First(), t = data.First(), };
var query2 =
data2
.Aggregate(new [] { agg }.ToList(), (a, x) =>
{
var last = a.Last();
if (x.jump == 1)
{
a.RemoveAt(a.Count() - 1);
a.Add(new { f = last.f, t = x.value, });
}
else
{
a.Add(new { f = x.value, t = x.value, });
}
return a;
});
var query3 =
from q in query2
select (q.f) + (q.f == q.t ? "" : "-" + q.t);
我得到以下结果:
即使可以在LINQ中使用,我也会在没有LINQ的情况下使用它,主要是为了让未来的开发人员能够对其进行维护和可读。@AdrianThompsonPhillips LINQ的目的是编写可读和可维护的查询……我发现LINQ通常更具可读性。从语法上看,意图相当清楚。话虽如此,我对任何解决方案都很满意,不仅仅是LINQ解决方案。@Servy确切地说,上面的问题可能会被一个无聊的学术头脑解决,但我打赌结果不会是可读的。我愿意被证明是错误的:-)即使在LINQ中有可能,我也会在没有LINQ的情况下这样做,主要是为了让它对未来的开发人员来说是可维护和可读的。@AdrianThompsonPhillips LINQ的目的是编写可读和可维护的查询……我发现LINQ通常更可读。从语法上看,意图相当清楚。话虽如此,我对任何解决方案都很满意,不仅仅是LINQ解决方案。@Servy确切地说,上面的问题可能会被一个无聊的学术头脑解决,但我打赌结果不会是可读的。我愿意被证明是错误的:-)非常酷的GroupWhile扩展!!我自己也在想一个循环。@AD.Net这不是你需要做的很多事情,但我认为如果你删除了开头的
GroupBy
,而改为按前缀排序,然后再按编号
,然后直接打电话给GroupWhile
(在那里添加“如果在同一前缀中”复选框),读起来会更自然。分组逻辑似乎应该放在一个地方,而输入列表似乎是按这种方式排序的。@Ocelot20存在的问题是,它不符合本机需求,也不符合我如何尝试“手动”有效地解决问题。是的,它取消了使用SelectMany
,但代价是对GroupWhile
使用更复杂的谓词。就我个人而言,我更喜欢这样,尽管这一改变并非完全不可能。@Servy:这个理由是有道理的。我想我更喜欢(IMO)看起来“干净”的东西,如果逻辑仍然易于遵循,而不是最初的“手工”解释(顺便说一句,这不是我对不同意的不满投票!)。非常酷的GroupWhile扩展!!我自己也在想一个循环。@AD.Net这不是你需要做的很多事情,但我认为如果你删除了开头的GroupBy
,而改为按前缀排序,然后再按编号
,然后直接打电话给GroupWhile
(在那里添加“如果在同一前缀中”复选框),读起来会更自然。分组逻辑似乎应该放在一个地方,而输入列表似乎是按这种方式排序的。@Ocelot20存在的问题是,它不符合本机需求,也不符合我如何尝试“手动”有效地解决问题。是的,它删除了