LINQ:将一系列字符串折叠成一组“范围”

时间:2014-03-14 20:42:39

标签: c# string linq

我有一个类似于此的字符串数组(在单独的行上显示以说明模式):

{ "aa002","aa003","aa004","aa005","aa006","aa007", // note that aa008 is missing
  "aa009"
  "ba023","ba024","ba025"
  "bb025",
  "ca002","ca003",
  "cb004",
   ...}

...目标是将这些字符串折叠为以逗号分隔的“范围”字符串:

"aa002-aa007,aa009,ba023-ba025,bb025,ca002-ca003,cb004, ... "

我想折叠它们,以便我可以构建一个URL。有数百个元素,但如果我以这种方式将它们折叠,我仍然可以传达所有信息 - 将它们全部放入URL“纵向”(它必须是GET,而不是POST)是不可行的。

我有想法使用前两个字符作为键将它们分成组 - 但是有没有人有任何聪明的想法将这些序列(无间隙)折叠到范围内?我正在努力解决这个问题,而我所提出的一切看起来都像意大利面。

2 个答案:

答案 0 :(得分:3)

所以你需要做的第一件事是解析字符串。分别使用字母前缀和整数值很重要。

接下来,您要对前缀中的项目进行分组。

对于该组中的每个项目,您希望按编号对它们进行排序,然后将项目分组,而前一个值的数字比当前项目的数字少1。 (或者,换句话说,前一项加上一项等于当前项目。)

将所有要投影的项目分组到基于该范围的前缀以及第一个和最后一个数字的值之后。不需要来自这些群体的其他信息。

然后我们将每个组的字符串列表展平为一个常规的字符串列表,因为一旦完成,就不需要从不同的组中分离出范围。这是使用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)));
}

GroupWhile方法可以这样实现:

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
    this IEnumerable<T> source, Func<T, T, bool> predicate)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;

        List<T> list = new List<T>() { iterator.Current };

        T previous = iterator.Current;

        while (iterator.MoveNext())
        {
            if (!predicate(previous, iterator.Current))
            {
                yield return list;
                list = new List<T>();
            }

            list.Add(iterator.Current);
            previous = iterator.Current;
        }
        yield return list;
    }
}

然后是将每个范围转换为字符串的简单帮助方法:

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

答案 1 :(得分:1)

这是一个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);

我得到了这些结果:

results