Functional programming 如何从功能上解决赎金券问题

Functional programming 如何从功能上解决赎金券问题,functional-programming,f#,Functional Programming,F#,编写一个函数,给出一个字母列表和一个单词,如果可以使用列表中的字母拼写单词,则返回true;如果不能,则返回false。例如,一个列表 ['a';'b';'d';'e';'a'] 这个词呢 bed 函数应该返回true。 如果单词是bbed,它也应该返回false,因为列表中只有一个b 通过在for循环中改变字典的状态,这是很容易做到的,但是如何在没有变化的情况下以更具功能性的方式做到这一点呢 这是我做的命令式版本: open System.Collections.Generic let

编写一个函数,给出一个字母列表和一个单词,如果可以使用列表中的字母拼写单词,则返回true;如果不能,则返回false。例如,一个列表

['a';'b';'d';'e';'a']
这个词呢

bed
函数应该返回true。 如果单词是
bbed
,它也应该返回false,因为列表中只有一个
b

通过在for循环中改变字典的状态,这是很容易做到的,但是如何在没有变化的情况下以更具功能性的方式做到这一点呢

这是我做的命令式版本:

open System.Collections.Generic

let letters = new Dictionary<char,int>()
[ ('a', 2); ('b', 1); ('c', 1); ('e', 1) ] |> Seq.iter letters.Add

let can_spell (word : string) =
    let mutable result = true
    for x in word do
        if letters.ContainsKey x && letters.[x] > 0 then
            let old = letters.[x]
            letters.[x] <- old - 1
        else
            result <- false
    done
    result

open System.Collections.Generic
let letters=新字典()
[('a',2);('b',1);('c',1);('e',1)]|>以下字母。添加
让我们拼写(单词:string)=
让可变结果=真
对于worddo中的x
如果letters.ContainsKey x&&letters.[x]>0,则
让旧=字母[x]
字母。[x]我想这样做:

let can_spell2 letters word =
    let uniqueLettersCount =    //group unique letters from words and count them
        word |> Seq.groupBy id
        |> Seq.map (fun (l,s) -> l,Seq.length s)
    
    uniqueLettersCount          //keep taking the sequence until you don't find a key or the key count is below the unique letter number
    |> Seq.takeWhile (fun (c,n) ->
        match Map.tryFind c letters with
        | Some n' -> if n' >= n then true else false
        | None -> false)
    |> fun s -> if Seq.length s = Seq.length uniqueLettersCount then true else false //if takeWhile didn't stop, the word is allowed
编辑:

用法示例:

let letters = ['a',2;'b',1;'c',1;'e',1] |> Map.ofList

can_spell2 letters "aab" //true
can_spell2 letters "aaba" //false
can_spell2 letters "bf" //false
can_spell2 letters "ecaba" //true

您可以使用两个词典按单词的字母和现有字母跟踪计数,然后检查字母计数是否大于单词的字母计数:

let contains (word:string)(letters:IDictionary<char,int>) = 
    let w = word
            |>Seq.countBy id
            |>dict

    w.Keys
    |>Seq.map(fun k-> letters.ContainsKey k && letters.[k] >= w.[k])
    |>Seq.reduce(&&)

这很容易。不是使用可变变量,而是在每次需要重新分配时调用函数。如果您不知道预先调用函数的次数,可以求助于递归。
let letters = 
    [ ('a', 2); ('b', 1); ('c', 1); ('e', 1); ('d', 1)] 
    |> dict

contains "bed" letters // True