F#: Recursive Functions: Concatenating the common

2019-07-13 20:06发布

问题:

let rec common a b =
    match isolate(a) b with
    | (x::xs,isolate(b)) ->
        if memberof(b x) then [x,common(xs b)]
        else common(xs b)

Here is what I have right now, but I've been trying many different things. The memberof() takes a float list and a float and returns true if the float is a member of the float list. The memberof() function works. I can't figure out how to return the new list.

I also have an isolate() function which will take a list and remove any duplicate floats within the list. This seem like it may be helpful.

edit: I'm trying to figure out how to return my new list when xs = []. In this thread the answer is right until common which does not return the common members in a list but rather concatenated list containing all elements.

Example I would like common to produce the result:

[1.0;2.0;3.0;4.0;5.0] [4.0;3.0;9.0] -> [3.0;4.0]

Rather than 'common' producing:

[1.0;2.0;3.0;4.0;5.0] [4.0;3.0;9.0] -> [1.0;2.0;3.0;4.0;5.0;9.0]

How can I solve this problem using recursion?

回答1:

Building upon the examples I gave as an answer to your earlier question.

Note: This is basically the same as the answer by TheInnerLight, I just used the format mentioned in the earlier questions and gave examples of all of the needed functions.

Since you appear to not be using this format I will walk through the steps I used to make the function common.

Start with

let funXYZ list =
    let rec funXYZInner list acc =
        match list with
        | head :: tail ->
            let acc = (somefunc head) :: acc
            funXYZInner tail acc
        | [] -> acc
    funXYZInner list []

and name the function common

let common list =
    let rec commonInner list acc =
        match list with
        | head :: tail ->
            let acc = (somefunc head) :: acc
            commonInner tail acc
        | [] -> acc
    commonInner list []

Common needs two input list and one output list.
First we will set the signature then then pass both list to commonInner. At this point I have not decided how to handle the two list I know that I need them both to do the transformation function.
Since the output will come from the accumulator and be a list, we initialize the accumulator with an empty list in commonInner a b []

let common (a : 'a list) (b : 'a list) : 'a list =
    let rec commonInner a b acc =
        match list with
        | head :: tail ->
            let acc = (somefunc head) :: acc
            commonInner tail acc
        | [] -> acc
    commonInner a b []

Now we can decide how to transform the two lists into one list with items common to both. Since the final list will not have duplicates, lets transform each list individually into list of unique items using isolate.

let common (a : 'a list) (b : 'a list) : 'a list =
    let aUnique = isolate a
    let bUnique = isolate b
    let rec commonInner a b acc =
        match list with
        | head :: tail ->
            let acc = (somefunc head) :: acc
            commonInner tail acc
        | [] -> acc
    commonInner aUnique bUnique  []

At this point when we reach the match statement we have the two list. We only need to compare each item in one list against the other list and when they are the same do something with the item.

With functional programming one has to be aware that list are immutable and use that to our benefit. An immutable is one that cannot be changed, you can not add to it, delete from it, or even change its value. Now you are probably asking, why can I add to a list, this is done all the time in the code. The answer is that the original list is left on the stack and a new list is constructed using the original list and the item to be added. Yes this is more work than necessary, but that's how it works. From my perspective with regards to provability of code, its worth the price. So to get the output list it is better to create the list starting from an empty list than to modify an existing list. That is why in the examples I gave you never saw a delete function.

So we know how to look at each item in a list individually with recursion. If this doe not make sense then just play with this on some list of different types and only print the values. Learning recursion for most people is one of those things were you struggle for a while then all at once you get it.

let rec processlist list =
    match list with
    | head :: tail ->
        do something with head, e.g. print head
        processlist tail
    | [] 

and we have to make a decision when we have a head and the other list b

if (memberof b hd) the

which gets us to this

let common (a : 'a list) (b : 'a list) : 'a list =
    let aUnique = isolate a
    let bUnique = isolate b
    let rec commonInner a b acc =
        match list with
        | head :: tail ->
            if (memberof b head) then
                do something
                commInner tail acc
            else
                do something different
                commonInner tail acc
        | [] -> acc
    commonInner aUnique bUnique  []

When the head from a is in list b then we want to add it to the accumulator, acc which will become the output list. When the head from a is NOT in list b then we do NOT want to add it to the accumulator, acc which will become the output list.

let common (a : 'a list) (b : 'a list) : 'a list =
    let aUnique = isolate a
    let bUnique = isolate b
    let rec commonInner a b acc =
        match list with
        | head :: tail ->
            if (memberof b head) then
                let acc = head :: acc
                commInner tail acc
            else
                commonInner tail acc
        | [] -> acc
    commonInner aUnique bUnique  []

and lastly since the accumulator has the output list in a backward order because of the use of ::, we just use reverse to reverse the list before returning the result.

let common (a : 'a list) (b : 'a list) : 'a list =
    let aUnique = isolate a
    let bUnique = isolate b
    let rec commonInner a b acc =
        match list with
        | head :: tail ->
            if (memberof b head) then
                let acc = head :: acc
                commInner tail acc
            else
                commonInner tail acc
        | [] -> reverse acc
    commonInner aUnique bUnique  []

here is the working code

// Reverse the order of the items in a list.

// val reverse : l:'a list -> 'a list
let reverse l =
    let rec reverseInner l acc =
        match l with
        | x::xs -> 
            let acc = x :: acc
            reverseInner xs acc
        | [] -> acc
    reverseInner l []

// Predicate that returns true if item is a member of the list.

// val memberof : l:'a list -> item:'a -> bool when 'a : equality
let memberof l item =
    let rec memberInner l item =
        match l with
        | x::xs -> 
            if x = item then
                true
            else 
                memberInner xs item
        | [] -> false
    memberInner l item


// Return a list of unique items.

// val isolate : list:'a list -> 'a list when 'a : equality
let isolate list =
    let rec isolateInner searchList commonlist =
        match searchList with
        | x::xs ->
            if (memberof commonlist x) then
                isolateInner xs commonlist
            else
                let commonlist = (x :: commonlist)
                isolateInner xs commonlist
        | [] -> reverse commonlist
    isolateInner list []

// val common : a:'a list -> b:'a list -> 'a list when 'a : equality
let common a b =
    let aUnique = isolate a
    let bUnique = isolate b
    let rec commonInner a b acc =
        match a with
        | x::xs ->
            if memberof b x then
                let acc = x :: acc
                commonInner xs b acc
            else
                commonInner xs b acc
        | [] -> reverse acc
    commonInner aUnique bUnique []

common [1.0;2.0;3.0;4.0;5.0] [4.0;3.0;9.0]      // val it : float list = [3.0; 4.0]


回答2:

As far as I understand the requirements of this function, it ought to return the intersection of two lists. Since this is a set-based operation, it's already built-in to F#:

> set [1.0;2.0;3.0;4.0;5.0] |> Set.intersect (set [4.0;3.0;9.0]);;
val it : Set<float> = set [3.0; 4.0]


回答3:

So, to do this recursively with lists, you need something like this:

let common a b =
    let rec commonRec a b =
        match a with
        | [] -> [] // a is empty : no elements can be common so return empty list
        | x::xs ->
            if memberof x b then x :: commonRec xs b // if x is a member of b, keep it
            else commonRec xs b // otherwise continue with remaining elements
    commonRec (isolate a) (isolate b) // remove duplicates here

Notice that I have removed isolate from the recursive function, it only needs to be performed once at the start.

Since you didn't post code for the isolate or memberof functions, I don't want to make assumptions about what they might be from other questions or this answer won't be useful to anyone else. Of course, you can freely use a different implementation of the same functions.

I just made the following simple definitions:

let isolate lst =
    lst |> Set.ofList |> Set.toList

let memberof a list =
    List.contains a list

Result:

[1.0; 2.0; 3.0; 4.0; 5.0] [4.0; 3.0; 9.0] ----> [3.0; 4.0]