13

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

4

2 回答 2

18

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

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

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

!(o is T)string添加以防止char阵列变平

于 2012-11-16T02:11:26.383 回答
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"]
于 2012-11-16T02:52:08.833 回答