Khaine775
Khaine775

Reputation: 2765

F# Change element in list and return full new list

I have a list of type (string * (int * int)) list. I want to be able to search through the list, finding the right element by it's string identifier, do a calculation on one of the ints, and then return the full, modified list.

Example:

Given a list

let st = [("a1",(100,10)); ("a2",(50,20)); ("a3",(25,40))]

I'm trying to make a function which gets one of the elements and subtracts number from one of the ints in the tuple.

get ("a2",10) st 
//Expected result: st' = [("a1",(100,10)); ("a2",(40,20)); ("a3",(25,40))]

I feel I'm almost there, but am a little stuck with the following function:

let rec get (a,k) st =
    match st with
    | (a',(n',p'))::rest when a'=a && k<=n' -> (n'-k,p')::rest
    | (a',(n',p'))::rest                    -> (n',p')::get (a,k) rest
    | _                                     -> failwith "Illegal input"

This returns [("a2",(40,20)); ("a3",(25,40))] and is thus missing the first a1 element. Any hints?

Upvotes: 12

Views: 8941

Answers (2)

Sumeet Das
Sumeet Das

Reputation: 45

I was looking for a function which would update an element in a list based on the element's data. I couldn't find one in F#5, so wrote one using Tomas' solution:

let updateAt (elemFindFunc: 'a -> bool) (newElem: 'a) (source: 'a list) : 'a list =
source
|> List.map
    (fun elem ->
        let foundElem = elemFindFunc elem
        if foundElem then newElem else elem)

elemFindFunc is the function which consumes an element and returns true if this is the element we want to replace. If this function returns true for multiple elements, then those will be replaced by newElem. Also, if elemFindFunc evaluates to false for all elements, the list will be unaltered.

newElem is the new value you want to replace with. newElem could be replaced by a function like valueFunc: 'a -> 'a if you want to process the element before inserting it.

Upvotes: -1

Tomas Petricek
Tomas Petricek

Reputation: 243096

Lists are immutable, so if you want to "change one element" you are really creating a new list with one element transformed. The easiest way to do a transformation like this is to use List.map function. I would write something like:

let updateElement key f st = 
  st |> List.map (fun (k, v) -> if k = key then k, f v else k, v)

updateElement is a helper that takes a key, update function and an input. It returns list where the element with the given key has been transformed using the given function. For example, to increment the first number associated with a2, you can write:

let st = [("a1",(100,10)); ("a2",(50,20)); ("a3",(25,40))]

st |> updateElement "a2" (fun (a, b) -> a + 10, b)

Upvotes: 21

Related Questions