F#隐藏东西?

时间:2017-08-06 21:06:03

标签: f#

不好的标题可以找到正确的单词。

目前我正在尝试制作一些F#可以在任何情况下使用的基本数据结构,第一个是双链表。

我的问题不是如何实现它,而是如果有隐藏数据结构的丑陋的原因。简短形式,我有一些像节点一样的东西

type Node<'N> = 
  | (node<'N> ref, 'N, node<'N>)
  | Empty

并且当我们有超过三个列表的项目相当容易出错时分析这个。那么有没有一种方法可以使库的用户看到“看起来”,所以它看起来更像是.NET中的List。我要求的方法不依赖于已经建立的数据类型,并且不返回字符串外观(“...”)

2 个答案:

答案 0 :(得分:4)

您可以将F#类型包装在一个类中,并保持实际的F#表示形式。例如,如果你想要一个超级简单的可变列表,你可以这样做:

type private MyListNode<'T> = 
  | Empty 
  | Cons of 'T * MyListNode<'T>

type MyList<'T>() =
  let mutable nodes = Empty
  member x.Prepend(el) = nodes <- Cons(el, nodes)
  member x.ToArray() = 
    let rec loop el = seq {
      match el with 
      | Empty -> ()
      | Cons(x, xs) -> 
          yield x
          yield! loop xs }
    loop nodes |> Array.ofSeq

C#用户可以使用MyList,这是普通的PrependToArray方法。 MyListNode类型是私有的(隐藏在F#库中),C#用户永远不会看到它。

答案 1 :(得分:1)

这不是问题的答案,而是评论的答案,因为我要说的是需要图表,所以它不会在评论中发挥作用。

kam写道:

  

但我的双链表正在O(1)中运行,如果我们假设它只是数据而不是&#34;指针&#34;那些是不可变的然后你仍然可以在O(1)时间内复制整个列表,因为你在添加或删除时唯一要做的就是改变或者将指针(ref cell)改为旧列表然后我们仍然拥有一个副本没有复制每一个元素的旧列表。

如果您尝试这样做,您会发现使用双向链接列表, 实际上可以保留旧的列表指针。这就是原因。

使用单链表,您可以在O(1)时间前添加到列表中,同时保持对旧列表的任何指针保持不变。这是一个例子:

包含三个项目的旧列表:

singly-linked list

在添加新头之后的新列表:

singly-linked list with new item

请注意其他代码的引用是如何保持不变的。其他代码引用的旧列表为["Item 1"; "Item 2"; "Item 3"]。新列表为["New head item"; "Item 1"; "Item 2"; "Item 3"]。但是代码的不同部分所持有的引用仍然指向格式良好的列表。 &#34;格式良好&#34;部分很重要,正如您将要看到的那样。

使用双向链接列表,事情变得更加复杂 - 事实证明 来维持不变性 O(1)时间。首先,让我们看看包含三个项目的旧列表:

doubly-linked list before adding new item

这是一个结构良好的双向链表。它遵循所有格式良好的双向链表应遵守的以下属性:

  1. 所有节点都有一个Fwd和Back指针。
  2. 除头节点之外的所有节点都有一个有效(非空)的Back指针。只有头节点的后退指针为null
  3. 除尾节点外的所有节点都有一个有效(非空)的Fwd指针。只有尾节点的Fwd指针为null
  4. 从不是尾部的任何节点,前进然后返回应该返回您在开始的同一节点。
  5. 从任何不是头部的节点开始,返回然后前进应该会带您回到开始的同一节点。
  6. 现在,我们如何添加新的头部项目,同时仍然确保其他代码的引用继续指向格式良好的双向链接列表?

    这是第一次尝试。我们添加新的头部项目,调整其Fwd指针以指向&#34; old&#34;头节点,并重写该节点的Back指针指向新的头节点:

    doubly-linked list with new item, changing old head node

    这仍然是一个结构良好的列表,因为您可以轻松验证。所有五个属性仍适用于每个节点。但等待!代码的其他部分的引用已将其列表从其下面更改了!在它指向三个项目的列表之前,现在它指向四个项目列表中的第二项!如果其他代码只是向前迭代,则它不会注意到更改。但是在它试图向后迭代的那一刻,它会注意到之前没有新的头部项目! 我们打破不变性承诺。不可变性是对使用我们的数据结构的其他代码的保证,如果你引用了这个数据结构,你看到的数据永远不会从你的下面改变。&#34; 我们刚刚违背了这一承诺:用于查看列表["Item 1"; "Item 2"; "Item 3"]的旧代码,现在它看到了列表["New head item"; "Item 1"; "Item 2"; "Item 3"]

    好的,那么。有没有办法保持这个承诺,而不是改变其他代码看到的东西?好吧,我们可以尝试不重写那个旧的头节点;就这样,旧代码仍会看到三个项目的双重链接列表,每个人都很高兴,对吧?好吧,让我们看看如果我们这样做会是什么样子:

    doubly-linked list with new item but not changing old head node

    很好:其他代码仍然看到它过去看到的完全相同的双向链表,并且无法从旧列表到新的头节点。因此,尝试从列表头部向后移动的其他代码的任何部分都会发现头部仍然会像null那样转到{{1}}。但是等一下:格式良好的清单的五个属性怎么样?好吧,事实证明我们违反了属性#4:从头节点开始,然后返回结束于空指针,我们开始的节点。所以我们不再有一个结构良好的清单:糟糕。

    好的,这种方法不会起作用。我们还能尝试什么?嗯......嘿!我有个主意!让我们只是制作旧头节点的副本,并在我们离开旧头节点时调整副本!因为我们知道我们只复制一个节点,所以仍然是O(1)。然后其他代码确切地看到它曾经看到的内容,三个项目的列表,但新列表有四个项目。辉煌!正是我们想要的,对吗?好吧,让我们看一下:

    doubly-linked list with copy of old head node

    好的,这有用吗?好吧,其他代码引用了旧的,未更改的头节点,所以没关系:它不会意外地看到新数据,所以它仍然继续看到它以前的确切含义,三个项目的清单。好。从新的头部节点开始,我们可以前进和后退,最终到达我们开始的地方,这样做很好......但是等等......不,还有问题。从项目1的副本节点开始,然后返回将我们带到&#34;项目1&#34;节点,而不是项目1&#34;的副本;节点。所以我们仍然违反了格式良好的列表的属性,这个列表也不是很好。

    也是对那个问题的答案:复制包含第2项的节点。我已经厌倦了绘制图表并且这个答案变得越来越长,所以我会让你自己解决这个问题 - 但是你很快就会看到那个带有项目副本的节点2与以前有同样的问题:前进和后退将你带到&#34; old&#34;项目2.(或者你已经调整了&#34;旧的&#34;第2项节点,从而破坏了不变性承诺,因为其他代码现在可以通过一些代码查看&#34;新的&#34;数据Fwd和/或Back操作系列)。

    的解决方案也是如此:只需复制第3项。我也不会画出那张图,但你可以自己解决。并且您会发现,一旦您将第1项,第2项, 3复制到新列表中,您就设法满足两者不变性承诺,格式良好的列表的所有属性。其他代码仍然看到未触及的旧列表,新列表中有四个项目。唯一的问题是,您必须按照定义复制列表中的每个项目 - O(N)操作 - 以实现此结果。

    摘要:单链接列表有三个属性:

    1. 您可以在O(1)时间前添加项目。
    2. 在您的前置操作之后,其他引用旧列表的代码仍会看到相同的数据。
    3. 旧列表和新列表都格式正确。
    4. 但是,对于双向链接列表,您只能拥有这三个属性的两个。您可以进行O(1)前置操作并维护格式良好的列表,但随后任何其他代码都会看到列表数据发生变化。或者你可以有O(1)prepend并且仍然让其他代码看到它曾经使用的相同数据,但是你的列表将不再是格式良好的。或者,通过复制列表中的每个节点,您可以让其他代码仍然看到它所使用的相同数据,并且您的新列表将是格式良好的 - 但您必须执行O(N)操作才能实现这个结果。

      这就是为什么我说不可能有一个带有O(1)操作的不可变双向链表。