是否可以实现递归“SelectMany”?

时间:2012-11-16 01:42:43

标签: c# .net recursion f#

众所周知,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语言欢迎中的示例。

2 个答案:

答案 0 :(得分:15)

据我了解你的想法,这是我的变种:

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

显然,它确实缺少一些类型有效性检查(InvalidCastExcpetion如果集合不包含T,可能还有一些其他缺点)......好吧,至少它是懒惰评估的,根据需要。

添加了

!(o is T)以防止string扁平化为char数组

答案 1 :(得分:6)

这在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"]