F#迭代嵌套词典

时间:2014-06-16 12:35:39

标签: recursion dictionary f#

我有一个丑陋的调试功能,用于打印3级地图,实际上是他们的词典。我一直试图转换它,以便我可以打印出一张n级地图。这是我到目前为止的尝试:

open System.Text
open System.Collections.Generic

let rec PrintMap x = 
    let PrintVal (v: obj) = match v with
                            | :? seq<KeyValuePair<_,_>> as vm ->  "{ " + PrintMap vm + " }"
                            | _ -> sprintf "Value: %s" (v.ToString())
    let sb = StringBuilder()
    for KeyValue(key,value) in x do 
        sb.AppendLine(sprintf "%s => %s" (key.ToString()) (PrintVal value)) |> ignore
    sb.ToString()

type dict3 = Dictionary<string,obj>
type dict2 = Dictionary<string,dict3>
type dict1 = Dictionary<string,dict2>

let k1 = "k1"
let k2 = "k2"
let k3 = "k3"
let v  = "foo" :> obj

let dict = dict1()
dict.[k1] <- dict2()
dict.[k1].[k2] <- dict3()
dict.[k1].[k2].[k3] <- v

printf "%s" (PrintMap dict) 

我宁愿不把嵌套字典切换到他们的F#等价物(至少对于输入),因为真实的东西处理它们。

你可以推断出我几乎可以肯定的是对于活跃模式的天真尝试,甚至没有提到StringBuilder,我对F#来说是一个新手。似乎我的困难在于说出字典和其他所有字母之间的区别。在查看该值时,通知类型推断系统,该值与&#39; x&#39;中的字典的类型不同。参数。

任何提示?

三江源!

修改

一个小小的阐述---有时我会得到一个dict2dict3类型而不是完整的dict1,这就是我的原因之一试图使这更通用。

解决方案

感谢Daniel:

let rec PrintMap x = 
    let PrintVal (v: obj) = match v with
                            | :? IDictionary as vd -> "{ " + PrintMap vd + " }"
                            | _ -> sprintf "%s" (v.ToString())
    let sb = StringBuilder()
    for key in x.Keys do 
        sb.AppendLine(sprintf "%s => %s" (key.ToString()) (PrintVal x.[key])) |> ignore
    sb.ToString()

输出非常难看,但它确实有效。使用上面的输入字典:

k1 => { k2 => { k3 => foo
 }
 }

3 个答案:

答案 0 :(得分:2)

PrintVal中,第二种情况从不匹配,因为seq<KeyValuePair<_,_>>并不意味着任何键值对序列,而不管类型args ;它意味着将它留给编译器来推断类型args 。换句话说,下划线只是用于编译时类型推断的通配符,而不是模式匹配。在这种情况下,类型args可能推断为<obj, obj>,它与测试中的任何字典都不匹配。

要取消此功能,您可能必须匹配非通用类型,例如System.Collections.IDictionary

答案 1 :(得分:0)

我认为你想做的事情是行不通的,而且理解F#并不是理解泛型。您必须记住,类型推断和泛型都是编译时规则,因此使用在运行时确定类型的对象调用此函数将永远不会起作用,因为编译器无法确定要应用的类型参数。换句话说,每次调用PrintMap v时,编译器都必须知道v的类型。

与C#中的类似问题一样,要解决此问题,您必须走dynamically invoking .NET methods using reflection的路线。

答案 2 :(得分:0)

虽然Daniel建议的解决方案适用于完整的词典,但它不能处理IDictionary<_,_>的实现,就像F#dict函数返回的那样。这可以通过测试接口并使用非泛型IEnumerable来迭代其内容来实现:

let getProp name x = 
    x.GetType().InvokeMember(
        name, BindingFlags.Public ||| BindingFlags.InvokeMethod |||
            BindingFlags.Instance ||| BindingFlags.GetProperty, null, x, null )

let rec PrintMap x = 
    match x.GetType().GetInterface "IDictionary`2" with
    | null -> sprintf "%A" x
    | _ ->
        let sb = StringBuilder()
        sb.Append "{ " |> ignore
        for o in unbox<System.Collections.IEnumerable> x do
            let (k, v) = getProp "Key" o, getProp "Value" o
            sb.AppendLine(sprintf "%s => %s" (string k) (PrintMap v)) |> ignore
        sb.Append " }" |> ignore
        string sb

dict["k1", dict["k2", dict["k3", "foo"]]]
|> PrintMap

输出(略有不同):

{ k1 => { k2 => { k3 => "foo"
 }
 }
 }