【问题标题】:Remove All but First Occurrence of a Character in a List of Strings删除字符串列表中除第一次出现的字符之外的所有字符
【发布时间】:2018-06-07 03:44:04
【问题描述】:

我有一个名称列表,我需要输出一个字符串,该字符串按照名称出现的顺序显示名称中的字母,没有重复项(例如,如果列表是 ["John"; "James"; "Jack"],则输出字符串应该是 @987654322 @)。我有一个解决方案(将所有名称折叠成一个字符串然后解析),但我觉得我通过使我的字符串可变来有点讨厌它。

我还想声明,这不是学校作业,只是一个工作同事的练习,因为我从只知道 Java Web 知识进入 F#。

这是我的工作解决方案(出于洞察目的):

let lower = ['a' .. 'z']
let upper = ['A' .. 'Z']
let mutable concatedNames = ["John"; "James"; "Jack"] |> List.fold (+) ""

let greaterThanOne (length : int) = length > 1
let stripChars (str : string) letter =
    let parts = str.Split([| letter |])
    match greaterThanOne (Array.length parts) with
    | true -> seq {
                    yield Array.head parts
                    yield string letter
                    yield! Array.tail parts
                  }
                  |> String.concat ""
    | _ -> str

let killAllButFirstLower = lower |> List.iter (fun letter -> concatedNames <- (stripChars concatedNames letter))
let killAllButFirstUpper = upper |> List.iter ( fun letter -> concatedNames <- (stripChars concatedNames letter))
printfn "All names with duplicate letters removed: %s" concatedNames

我最初想单独使用函数明确地做到这一点,并且在上面有一个解决方案

let lower = ['a' .. 'z']
let upper = ['A' .. 'Z']
:
:
:
let lowerStripped = [""]
let killLowerDuplicates = lower |> List.iter (fun letter -> 
                                        match lowerStripped.Length with
                                        | 1 ->
                                                (stripChars concatedNames letter)::lowerStripped |> ignore

                                        | _ ->  (stripChars (List.head lowerStripped) letter)::lowerStripped |> ignore

                                 )

let upperStripped = [List.head lowerStripped]
let killUpperDuplicates = lower |> List.iter ( fun letter -> (stripChars (List.head upperStripped) letter)::upperStripped |> ignore )
let strippedAll = List.head upperStripped
printfn "%s" strippedAll

但我无法让这个工作,因为我意识到 consed 列表不会去任何地方(更不用说这可能效率低下)。这个想法是,通过这种方式,一旦我解析了所有内容,列表的第一个元素将是所需的字符串。

我知道问一个我已经有解决方案的问题可能很奇怪,但我觉得使用 mutable 只是我没有放弃我的命令式习惯(正如我所读的那样,它应该很少需要使用它),我想更多地加强纯功能。那么有没有更好的方法来做到这一点?如果我能以某种方式将结果传递到某个地方,第二种解决方案是否可行?

【问题讨论】:

    标签: f# functional-programming


    【解决方案1】:

    您可以使用Seq.distinct 去除重复并保留顺序,因此您只需要将字符串列表转换为单个字符串,可以使用String.concat "" 完成:

    let distinctChars s = s |> String.concat ""
                            |> Seq.distinct
                            |> Array.ofSeq
                            |> System.String
    

    如果你运行distinctChars ["John"; "James"; "Jack"],你会回来:

    "Johnamesck"
    

    【讨论】:

    • 很好,我不知道为什么我没有想到在我的回答中使用Seq.distinct。您甚至可以将整个事物定义为一个组合:let distinctChars: string seq -&gt; string = Seq.collect id &gt;&gt; Seq.distinct &gt;&gt; Array.ofSeq &gt;&gt; System.String
    • 老兄……太棒了。这个例子是我真正开始喜欢 F# 的原因之一,尽管仍在为范式转变而苦苦挣扎。内置了这么多小宝石。我的意思是你只是将我的近 20 行工作解决方案减少到 4 LOC。我现在将深入研究这些函数的文档,谢谢。
    【解决方案2】:

    这应该可以解决问题:

    let removeDuplicateCharacters strings =
        // Treat each string as a seq<char>, flattening them into one big seq<char> 
        let chars = strings |> Seq.collect id  // The id function (f(x) = x) is built in to F#
                                               // We use it here because we want to collect the characters themselves
        chars
        |> Seq.mapi (fun i c -> i,c) // Get the index of each character in the overall sequence
        |> Seq.choose (fun (i,c) ->  
            if i = (chars |> Seq.findIndex ((=) c))  // Is this character's index the same as the first occurence's index?
            then Some c                              // If so, return (Some c) so that `choose` will include it,
            else None)                               // Otherwise, return None so that `choose` will ignore it
        |> Seq.toArray // Convert the seq<char> into a char []
        |> System.String // Call the new String(char []) constructor with the choosen characters
    

    基本上,我们只是将字符串列表视为一个大字符序列,并选择整个序列中的索引与该字符第一次出现的索引相同的那些。

    运行 removeDuplicateCharacters ["John"; "James"; "Jack";] 会给出预期的输出:"Johnamesck"

    【讨论】:

    • 即使您的解决方案更加冗长,它仍然非常有用。我也非常感谢您花费额外的时间来添加 cmets。我正在努力尽可能快地学习和理解 F#。所以我真的可以通过每行仍在做的事情并学到很多东西,所以你的回答不是白费的:)
    • Chad Gilbert 的解决方案和我的实际上非常相似,我只是忘记了Seq.distinct 并使用Seq.mapiSeq.choose 重新发明了它,所以至少你必须了解这些功能是我失忆的结果!在我对乍得的回答的评论中,我基本上写了一个结合我的函数和他的函数的版本(使用Seq.distinct 和所有函数组合),你可以看到相似之处。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-08
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    • 2013-12-15
    • 2022-11-25
    • 1970-01-01
    相关资源
    最近更新 更多