【问题标题】:Implement a queue type in F# contains elements在 F# 中实现一个队列类型包含元素
【发布时间】:2021-11-12 04:31:10
【问题描述】:

我在 F# 中实现了一个队列,并且插入元素效果很好。

// Defines the Queue type as an algebraic data type.
type 'a Queue = QueueList of 'a list * 'a list

let empty = QueueList([], [])

let add elem = function
  | QueueList(top, rest) -> QueueList(top, elem :: rest) 

我一直在尝试验证队列列表是否具有该元素,则返回 true。如果不返回 false。

// Returns true if the queue contains the given element.
val contains: 'a -> 'a Queue -> bool when 'a : equality

// Returns true if the queue contains the given element.
// let rec contains elem = function
       | QueueList([], []) -> false
       | QueueList(hd::tl, tl2) -> if hd = elem 
                                       then true 
                                         else contains elem (QueueList (tl, tl2))

感谢您的帮助。

【问题讨论】:

  • 我这里有几个问题,为什么你的队列实现是两个列表?你打算使用两个堆栈方法吗? top 对您的队列意味着什么?如果您只想查看rest 列表,您可以使用List.find 或使用递归来导航和查找列表中的元素。但是你的问题不是很清楚
  • 我有两个列表,因为使用两个列表的元组:一个列表用于删除项目,另一个列表用于添加项目。

标签: types f# queue


【解决方案1】:

您可以使用列表的概念来实现不可变队列。所以你有一个顶部元素和队列的其余部分。

type QueueA<'a> =
    | Empty
    | Top   of 'a * QueueA<'a>

这是可能的;但插入性能很差。无论如何,这是您如何实现这种队列的方法。

module QueueA =
    let empty = Empty

    let rec add x q =
        match q with
        | Empty        -> Top (x,Empty)
        | Top(y,rest ) -> Top (y, add x rest)

    let head q =
        match q with
        | Empty       -> None
        | Top(x,rest) -> Some x

    let tail q =
        match q with
        | Empty       -> None
        | Top(x,rest) -> Some rest

    let rec iter f q =
        match head q with
        | None   -> ()
        | Some x ->
            f x
            Option.iter (iter f) (tail q)

add 不是尾递归的。如果您希望第一个元素保持在顶部并在末尾添加进一步添加的元素,那么您必须重建整个队列。 Add 基本上是一个List.append,您可以在其中将一个元素添加到队列的末尾以重建整个队列。所以你的插入速度很慢,具有二次 O(x^2) 的性能。

但是tail 是一个 O(1) 的快速运算。

您可以将两个列表用于不可变队列,而不是这样做。这个想法是:添加的元素构建一个列表,然后你离开它们 只要您不获取元素,就以相反的顺序进行。只有当你想获取一个元素,并且没有反向列表可用时,你才将列表反向一次,并保存反向列表。所以添加 一个元素是 O(1) 性能。

如果您已经有一个反向列表,则在另一端获取一个元素可能是 O(1),否则可能是 O(N)。通常你可以说它是 Amortized O(1)。

一个实现看起来像

type QueueB<'a> = Queue of queue:list<'a> * added:list<'a>

module QueueB =
    let empty     = Queue ([], [])
    let queue q a = Queue (q,a)

    let add x (Queue (q,r)) =
        queue q (x::r)

    let head q =
        match q with
        | Queue([],[]) -> None
        | Queue([],r)  -> Some (List.head (List.rev r))
        | Queue(q,_)   -> Some (List.head q)

    let tail q =
        match q with
        | Queue([],[]) -> None
        | Queue([],r)  ->
            let q = (List.tail (List.rev r))
            Some (queue q [])
        | Queue(h::t,r) ->
            Some (queue t r)

    let rec iter f q =
        match head q with
        | None   -> ()
        | Some x ->
            f x
            Option.iter (iter f) (tail q)

例如当你将1,2,3添加到QueueA时,它会构建结构

Top(1, Top(2, Top(3, Empty)))

当你将1,2,3添加到QueueB时,它会构建结构

Queue([], [3;2;1])

当您为此致电tail 时,您会得到

Queue([2;3], [])

添加 4,5,6 收益率

Queue([2;3], [6;5;4])

例子

(* Top(1, Top(2, Top(3, Empty))) *)
let qs =
    QueueA.empty
    |> QueueA.add 1
    |> QueueA.add 2
    |> QueueA.add 3

(* Queue([], [3;2;1]) *)
let qs =
    QueueB.empty
    |> QueueB.add 1
    |> QueueB.add 2
    |> QueueB.add 3

(* Queue([2;3], [6;5;4]) *)
let qs2 =
    QueueB.tail qs
    |> Option.defaultValue QueueB.empty
    |> QueueB.add 4
    |> QueueB.add 5
    |> QueueB.add 6

(* Prints numbers from 2 to 6 *)
QueueB.iter (printfn "%d") qs2

也许在实际实现中,我会更改 head 以同时返回剩余的队列(尾部)。因为大多数时候,如果您使用head,您可能还会调用tail。在这种情况下,列表的反转发生了两次。

同时返回 head 只执行一次。如果你不需要尾巴,那么你可以把尾巴扔掉。


现在,如果你想要额外的功能,你应该先添加一个fold 和foldBack 功能。例如,在 QueueB 上,fold 将是

let rec fold f (state:'State) q =
    let rec loop state q =
        match head q with
        | None      -> state
        | Some head ->
            match tail q with
            | None      -> state
            | Some rest -> loop (f state head) (rest)
    loop state q

然后你就可以用它实现contains了。

let contains x q =
    let folder state e =
        if e = x then true else state
    fold folder false q

还有上面的qs2

QueueB.contains 1 qs2 (* false *)
QueueB.contains 6 qs2 (* true  *)

【讨论】:

  • 谢谢,好代码。感谢您的帮助。
  • @Diego 谢谢,如果它回答了你的问题,那么请将我的回答标记为正确。
  • 标记为正确!
猜你喜欢
  • 2020-03-11
  • 2011-07-11
  • 2018-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-17
相关资源
最近更新 更多