Charles uuu
Charles uuu

Reputation: 61

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

I created a list in f# named tickets that contains 10 records called Ticket.The records are all initialized with their specific seat number and empty customer name.

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

I want to write a function to book a specific seat in the list(add a customer name to the seat). How can i edit an item in the list and have other items still retain their value

Upvotes: 5

Views: 3515

Answers (3)

Dax Fohl
Dax Fohl

Reputation: 10781

I think the most idiomatic option here would be to simply use a string array. Given you know in advance the size and you want to be able to update it, this is the structure that fills both those needs most idiomatically. So,

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

...

tickets.[3] <- "New Customer"

keep it simple.

Granted this is not "purely-functional" (but any purely functional solutions here just kick the non-pure parts down the road anyway), but if the goal is just to get the job done, this will do it.

Upvotes: 0

Random Dev
Random Dev

Reputation: 52290

Here is a way to use a list of mutable (ref-cells) 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"

obvious you can make tickets itself mutable too, if you want to add seats instead of initializing them with all the obvious seats

a better approach(?)

Still I think that this is the wrong way to do it - I think you want a 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 }

note that these are all immutable values, so bookSeat will return a new Ticket-reservation-map

hyprid using Dictionary

or you can use a common .net Dictionary and mutate this:

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 }

Here you don't have to pass the Tickets around - they will be mutated in place (but still the Ticket-objects themselves are still immutable)

Note that this right now is not thread-safe so be careful.

Upvotes: 0

Tomas Petricek
Tomas Petricek

Reputation: 243096

The functional F# list type is immutable, which means that you cannot change it. The typical way of working with it would be to return a new, modified, copy.

To do that, you can write a function bookSeat that takes list<Ticket> together with number & new name and produces a new list<Ticket> with that one field updated:

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

Upvotes: 6

Related Questions