如何在 f# 中编辑可变列表中的项目并允许列表中的其他项目保留其值?

How to edit an Item in a mutable list in f# and allow the other items in the list retain their values?

我在 f# 中创建了一个名为 tickets 的列表,其中包含 10 条名为 Ticket.The 的记录,所有记录都使用其特定的座位号和空的客户名称进行了初始化。

type Ticket = {seat:int; customer:string}  
let mutable tickets = [for n in 1..10 -> {Ticket.seat = n; Ticket.customer = ""}]

我想写一个函数来预订列表中的特定座位(向座位添加客户名称)。 我怎样才能编辑列表中的项目并让其他项目仍然保留其价值

函数式 F# 列表类型是不可变的,这意味着您无法更改它。使用它的典型方法是 return 一个新的、修改过的副本。

为此,您可以编写一个函数 bookSeat,将 list<Ticket> 与数字和新名称一起生成一个新的 list<Ticket> 并更新该字段:

let bookSeat seatNo name tickets = 
  tickets |> List.map (fun ticket ->
    if ticket.seat = seatNo then { ticket with customer = name }
    else ticket )

bookSeat 3 "Tomas" tickets

这是使用可变(引用单元)票证列表的方法:

let maxSeats = 10
let tickets : (int * Ticket option) ref list =
    [1..maxSeats]
    |> List.map (fun s -> ref (s, None) )

let bookSeat seat name =
    match List.tryFind (fun r -> let (s,_) = !r in s = seat) tickets with
    | Some r -> 
        r :=
            match !r with
            | (s, Some ticket) -> (s, Some { ticket with customer = name })
            | (s, None)        -> (s, Some { seat = s; customer = name })
    | None ->
        failwith "seat not found"

显然你也可以让 tickets 本身可变,如果你想 add 席位而不是用​​所有明显的席位初始化它们

更好的方法(?)

我仍然认为这是错误的做法 - 我认为您需要地图:

type Ticket = {seat:int; customer:string}  
type Tickets = Map<int, Ticket>

let bookSeat seat name (tickets : Tickets) =
    match Map.tryFind seat tickets with
    | Some oldTicket ->
        tickets 
        |> Map.remove seat
        |> Map.add seat { oldTicket with customer = name }
    | None ->
        tickets
        |> Map.add seat { seat = seat; customer = name }

请注意,这些都是不可变的值,因此 bookSeat 将 return 一个新的 Ticket-reservation-map

hyprid 使用 Dictionary

或者您可以使用通用的 .net Dictionary 并对其进行修改:

type Ticket = {seat:int; customer:string}  
let tickets = System.Collections.Generic.Dictionary<int, Ticket>()

let bookSeat seat name =
    match tickets.TryGetValue seat with
    | (true, oldTicket) ->
        tickets.[seat] <- { oldTicket with customer = name }
    | (false, _) ->
        tickets.[seat] <- { seat = seat; customer = name }

在这里你不必传递 Tickets - 它们会在原地发生变化(但 Ticket-对象本身仍然是不可变的)

请注意,目前这不是线程安全的,所以要小心。

我认为这里最惯用的选项是简单地使用 string array。如果您事先知道大小并且希望能够对其进行更新,那么这是最符合这两种需求的结构。所以,

// create 10-element string array initialized with null
let (tickets : string array) = Array.zeroCreate 10 

...

tickets.[3] <- "New Customer"

保持简单。

当然这不是 "purely-functional"(但这里的任何纯功能性解决方案无论如何都会将非纯部分踢到一边),但如果目标只是完成工作,那么这就可以了.