1

我有一个平面可枚举字符串,其中包含基于某些标准(在本例中为整数)标识的主记录,后跟任意数量的详细记录。从细节到它的主记录没有硬性联系 - 唯一的联系是主记录的位置在其细节记录之前。

我创建了一个 Linq 查询,可以得到我想要的结果,但如果可能的话,我宁愿不做SkipWhile()andTakeWhile()调用。感觉非常低效,特别是因为我可以在 O(n) 处将这个查询作为一个简单的 foreach 循环来执行。

这是一个模拟示例,它返回我想要的结果,但没有我想要的效率:

var data = new[] {"1", "a", "b", "c", "2", "d", "e", "3", "1", "f", "g", "h", "i", "2", "j", "k", "l"};
int throwAway;

var indexedData = data.Select((item, index) => new {item, index} );

var results =
    from a in indexedData
    where Int32.TryParse(a.item, out throwAway) == true
    select new {
        HeaderIndex = a.index,
        HeaderValue = a.item,
        Details = 
            indexedData
            .SkipWhile((x) => x.index <= a.index)
            .TakeWhile(x => Int32.TryParse(x.item, out throwAway) == false)
            .Select(x => x.item)
    };

results.Dump();

所需的 LinqPad 结果

有没有一种方法可以在 Linq 中更有效地执行此查询,而无需求助于传统循环?

4

2 回答 2

1

首先创建一个辅助函数,该函数可以在满足条件时对项目进行分组:

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

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

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

然后,由于 LINQ 不能很好地处理out参数,我们可以制作一个非输出版本TryParse

public static bool IsInt(string value)
{
    int num;
    return int.TryParse(value, out num);
}

使用它,我们可以获得不是数字的项目组:

data.GroupWhile(str => !IsInt(str))
    .Select(group => new
    {
        Number = group.First(),
        Letters = group.Skip(1),
    });

重新添加索引并验证无效的输入数据留给读者作为练习。

于 2013-04-30T19:01:31.777 回答
1

您可以使用一些字符串操作将其转换为几行代码:

int idx = 0, loser;
var results = data
    // turn the list of strings into one string, where we separate the groups by a delimiter
    // and the elements in the group by a different delimiter
    .Aggregate((soFar, next) => int.TryParse(next, out loser) ? soFar + "|" + next : soFar + '=' + next)
    // split by that delimiter, to get the groups
    .Split('|')
    .Select(s =>
    {
        // get the elements in the group split up
        var groupValues = s.Split('=');
        // create the resulting object, using the index
        var r = new { HeaderIndex = idx, HeaderValue = int.Parse(groupValues[0]), Details = groupValues.Skip(1).ToList() };
        // update the index
        idx += s.Length;
        return r;
    });

这将返回请求的数据,但它们Details是 a List<string>,而不仅仅是Enumerable<string>

于 2013-04-30T19:32:55.517 回答