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

4

我在f#中创建了一个名为tickets的列表,其中包含10个名为Ticket的记录。这些记录都已经初始化了它们特定的座位号和空客户姓名。

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

我想编写一个函数来预订列表中的特定座位(将客户名称添加到座位上)。 如何编辑列表中的项目并使其他项目仍保留其值?


你卡在哪个具体的部分了? - John Palmer
@JohnPalmer 我在某个地方读到,在f#中如果我想要更改列表中的一个项目,我必须创建一个新列表。 这就是我想出来的。但是新列表现在只有一个项目。 let bookTicket()= Console.WriteLine("输入座位号:") let seatNo = int(Console.ReadLine()) Console.WriteLine("输入客户姓名:") let customerName = string(Console.ReadLine()) tickets <- [{Ticket.seat = seatNo; Ticket.customer = customerName}] - Charles uuu
@CharlesUko 这是真的,在这种情况下(如果你真的想要改变事物),我会使用 System.Collection.Generic.List<'t>,就像你可能在 C# 中使用的一样 - 然后你可以用 let oldTicket = list.[itemNr] 来获取旧票据,使用 let newTicket = { oldTicket with customer = "newCustomerName" } 创建一个新记录,并使用 list.[itemNr] <- newTicket 将其放回列表中。 - Random Dev
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Random Dev
1
这与此问题非常相似:https://dev59.com/1mgu5IYBdhLWcg3wRk9h - John Palmer
2
@JohnPalmer 说得好 - 我认为(函数式)列表在这里是错误的数据表示。 - Random Dev
3个回答

5

F#中的函数式列表类型是不可变的,这意味着你不能改变它。通常使用它的方法是返回一个新的修改后的副本。

为此,你可以编写一个名为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

0

这里有一种使用可变列表(ref-cells)票据的方法:

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本身可变。

更好的方法(?)

尽管如此,我认为这样做是错误的方式-我认为您需要一个Map:

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将返回一个新的Ticket-reservation-map

使用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对象本身仍然是不可变的)

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


0

我认为在这里最惯用的选项是简单地使用一个字符串数组。如果您事先知道大小并且希望能够更新它,那么这就是最符合这两个需求的结构。因此,

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

...

tickets.[3] <- "New Customer"

保持简单。

虽然这不是“纯函数式”的(但任何纯函数式的解决方案都会把非纯部分推迟到后面),但如果目标只是完成工作,那么这样做就可以了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接