【问题标题】:Yield return brainfreeze收益回报大脑冻结
【发布时间】:2013-02-28 21:52:25
【问题描述】:

假设我有这样的方法:

IEnumerable<record> GetSomeRecords()
{
  while(...)
  {
    yield return aRecord
  }
}

现在,假设我有一个调用者,它也返回一个相同类型的枚举,像这样

IEnumerable<record> ParentGetSomeRecords()
{
  // I want to do this, but for some reason, my brain locks right here
  foreach(item in someItems)
    yield return GetSomeRecords();
}

该代码出现语法错误错误,因为 yield return 需要一个类型记录,而我正在返回一个 IEnumerable 记录

我想要一个“扁平”的 IEnumerable 来扁平化嵌套的枚举循环。这让我发疯,因为我知道我以前做过,但我似乎不记得那是什么了。有什么提示吗?

【问题讨论】:

  • 在这种情况下,我不确定您所说的“一个平坦的 IEnumerable”是什么意思 - 您能否展示一个预期返回类型/数据的示例?
  • 使用yield return吗?
  • 如果您只想将数据作为物化列表返回(不进行任何数据转换),您可以使用 GetSomeRecords().ToList()。这就是你想要的吗?
  • @JMarsch:GetSomeRecords()ParentGetSomeRecords()之间的关系不清楚;您应该描述它们之间的关系。 ParentGetSomeRecords() 是否返回某种树? Record 是自引用类型吗?

标签: c# linq


【解决方案1】:

这就是你所追求的吗?

IEnumerable<record> ParentGetSomeRecords()
{
    foreach(var item in someItems)
        foreach(var record in GetSomeRecords())
            yield return record;
}

如上所述,这仅适用于单个级别的子级,但与您的示例代码最等效。

更新

有些人似乎认为您想要扁平化层次结构的能力。这是一个执行广度优先展平的扩展方法(在孩子之前获取兄弟姐妹):

来自单个项目:

[Pure]
public static IEnumerable<T> BreadthFirstFlatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
    Contract.Requires(!ReferenceEquals(source, null));
    Contract.Requires(selector != null);
    Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

    var pendingChildren = new List<T> {source};

    while (pendingChildren.Any())
    {
        var localPending = pendingChildren.ToList();
        pendingChildren.Clear();
        foreach (var child in localPending)
        {
            yield return child;
            var results = selector(child);
            if (results != null)
                pendingChildren.AddRange(results);
        }
    }
}

可以这样使用:

record rec = ...;
IEnumerable<record> flattened = rec.BreadthFirstFlatten(r => r.ChildRecords);

这将导致IEnumerable&lt;record&gt; 包含rec、所有recs 孩子、所有孩子的孩子等等。

如果您来自records 的集合,请使用以下代码:

[Pure]
private static IEnumerable<T> BreadthFirstFlatten<T, TResult>(IEnumerable<T> source, Func<T, TResult> selector, Action<ICollection<T>, TResult> addMethod)
{
    Contract.Requires(source != null);
    Contract.Requires(selector != null);
    Contract.Requires(addMethod != null);
    Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

    var pendingChildren = new List<T>(source);

    while (pendingChildren.Any())
    {
        var localPending = pendingChildren.ToList();
        pendingChildren.Clear();
        foreach (var child in localPending)
        {
            yield return child;
            var results = selector(child);
            if (!ReferenceEquals(results, null))
                addMethod(pendingChildren, results);
        }
    }
}

[Pure]
public static IEnumerable<T> BreadthFirstFlatten<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
    Contract.Requires(source != null);
    Contract.Requires(selector != null);
    Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

    return BreadthFirstFlatten(source, selector, (collection, arg2) => collection.AddRange(arg2));
}

[Pure]
public static IEnumerable<T> BreadthFirstFlatten<T>(this IEnumerable<T> source, Func<T, T> selector)
{
    Contract.Requires(source != null);
    Contract.Requires(selector != null);
    Contract.Ensures(Contract.Result<IEnumerable<T>>() != null);

    return BreadthFirstFlatten(source, selector, (collection, arg2) => collection.Add(arg2));
}

这两种扩展方法可以这样使用:

IEnumerable<records> records = ...;
IEnumerable<record> flattened = records.BreadthFirstFlatten(r => r.ChildRecords);

或者从相反的方向:

IEnumerable<record> records = ...;
IEnumerable<record> flattened = records.BreadthFirstFlatten(r => r.ParentRecords);

所有这些扩展方法都是迭代的,因此不受堆栈大小的限制。

我有一大堆这些类型的方法,包括前序和后序深度优先遍历,如果你想看到它们,我会做一个 repo 并上传到某个地方:)

【讨论】:

  • 可能就是他要找的东西。 +1
  • 我认为他的record 类有一个IEnumerable&lt;record&gt;,所以它可能是n 级别的深度。这仅适用于第一级
  • @mattytommo 我会更新我的答案,以防他想要这样做。
  • 您可以使用这种方法的扩展来“展开”递归序列。
  • @MatthewWatson 几乎我发布的内容,除了我更新的代码是迭代的,我没有 stackoverflows ^^
【解决方案2】:

怎么样:

IEnumerable<record> ParentGetSomeRecords()
{
    var nestedEnumerable = <whatever the heck gets your nested set>;
    // SelectMany with an identity flattens 
    // IEnumerable<IEnumerable<T>> to just IEnumerable<T>
    return nestedEnumerable.SelectMany(rec => rec);
}

【讨论】:

  • 我认为他有一个永无止境的子项目链。这仅适用于第一级子项目。
  • @mattytommo 是的,不得不重新阅读几次...编辑了解释性评论
【解决方案3】:

效率低,但你可以使用这个:

List<Record> rcrdList = new List<Record>();
foreach (var item in someItems)
{
    rcrdList.AddRange(item.GetSomeRecords());
}
return rcrdList;

【讨论】:

    猜你喜欢
    • 2011-05-27
    • 2012-01-14
    • 1970-01-01
    • 1970-01-01
    • 2018-01-23
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-03
    相关资源
    最近更新 更多