【问题标题】:Is it possible to implement a recursive "SelectMany"?是否可以实现递归“SelectMany”?
【发布时间】:2012-11-16 01:42:43
【问题描述】:

众所周知,Enumerable.SelectMany 将序列序列展平为单个序列。如果我们想要一种可以递归地展平序列序列的方法怎么办?

我很快想到了一个使用 ICollection<T> 的实现,即热切评估,但我仍然在摸索如何制作一个懒惰评估的实现,例如,使用 yield 关键字。

static List<T> Flatten<T>(IEnumerable list)  {
    var rv = new List<T>();
    InnerFlatten(list, rv);
    return rv;
}

static void InnerFlatten<T>(IEnumerable list, ICollection<T> acc) {
    foreach (var elem in list) {
        var collection = elem as IEnumerable;
        if (collection != null) {
            InnerFlatten(collection, acc);
        }
        else {
            acc.Add((T)elem);
        }
    }
}

有什么想法吗?欢迎使用任何 .NET 语言的示例。

【问题讨论】:

  • 也许使用 Y 组合器?这将找到固定点(即完全展平的列表)
  • Recursive List Flattening 的可能重复项
  • @Scorpi0:非常相似,但不完全相同。此问题要求 C# 或 F#(根据标签)或其他 .net 语言(来自问题)的答案。另一个问题是 C# 特有的。

标签: c# .net recursion f#


【解决方案1】:

据我了解您的想法,这是我的变体:

static IEnumerable<T> Flatten<T>(IEnumerable collection)
{
    foreach (var o in collection)
    {
        if (o is IEnumerable && !(o is T))
        {
            foreach (T t in Flatten<T>((IEnumerable)o))
                yield return t;
        }
        else
            yield return (T)o;
    }
}

检查一下

List<object> s = new List<object>
    {
        "1",
        new string[] {"2","3"},
        "4",
        new object[] {new string[] {"5","6"},new string[] {"7","8"},},
    };
var fs = Flatten<string>(s);
foreach (string str in fs)
    Console.WriteLine(str);
Console.ReadLine();

显然,它确实缺少一些类型有效性检查(如果集合不包含 T,则为 InvalidCastExcpetion,并且可能还有其他一些缺点)...好吧,至少它是惰性求值的,如所愿。

添加!(o is T) 是为了防止string 扁平化为char 数组

【讨论】:

  • !(o is T) 不错!
【解决方案2】:

这在具有递归序列表达式的 F# 中是微不足道的。

let rec flatten (items: IEnumerable) =
  seq {
    for x in items do
      match x with
      | :? 'T as v -> yield v
      | :? IEnumerable as e -> yield! flatten e
      | _ -> failwithf "Expected IEnumerable or %A" typeof<'T>
  }

测试:

// forces 'T list to obj list
let (!) (l: obj list) = l
let y = ![["1";"2"];"3";[!["4";["5"];["6"]];["7"]];"8"]
let z : string list = flatten y |> Seq.toList
// val z : string list = ["1"; "2"; "3"; "4"; "5"; "6"; "7"; "8"]

【讨论】:

  • 我必须说我不明白转换为 'T 诡计...在这里写 'T 和写 obj 有什么不同?
  • @Dr_Asik Daniel 的代码在其当前形状中是错误的。如果您尝试键入它,编译器会发出警告,第二条规则将永远不会匹配。
  • @Dr_Asik:'T 是类型 arg,match x with :? 'T 是类型测试(C# 中的 x is T)。由于类型推断,类型参数在 F# 中不需要显式。
  • @Joh:正如我的测试所示,该函数可以编译(没有警告)并且可以完美运行。你可以try it out on ideone
  • @Daniel 我知道语法是什么意思,但我不明白为什么 'T 不包含 IEnumerable 所以可以匹配第二个子句。
猜你喜欢
  • 2012-04-12
  • 1970-01-01
  • 1970-01-01
  • 2021-02-04
  • 2013-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多