在没有库的情况下访问f#中的元组列表中的特定元素

时间:2018-10-01 13:59:22

标签: list recursion f# tuples

我有一个包含三个不同元素的元组列表,如下所示:

[(a0:string, b0:string, c0:int); (a1, b1, c1); (and so on...)]

我需要制作一个函数,该函数接受此列表和一个a形式的“名称”,并给出所有b的列表,其中名称与元组中的a匹配。但是我不确定如何迭代和匹配所有内容。

input: function name tuplelist
output: [b0 if a0 = name; b1 if a1 = name; b2 if a2 = name]

我也无法使用库!

3 个答案:

答案 0 :(得分:1)

强大的F#模式匹配和递归以及类型推断功能可以轻松弥补删除库的局限性。

您需要构建一个map函数,将一个列表转换为另一个列表,这将通过递归来解决,应用于列表中每个元素的映射函数可能会使用模式匹配将元组分解为组件并执行转换。

将执行以下操作:

let filter name (tupleList:(string*string*int) list) =
    let checkElement name = function | (a,b,c) -> if a = name then Some b else None
    let rec mapList name inputList outputList =
        match inputList with
        | [] -> outputList
        | h::t -> let filter = checkElement name h
                  mapList name t (if filter = None then outputList else (outputList @ [filter.Value]))
    mapList name tupleList [] 

这里checkElement是一种映射函数,它接受name和一个元组(a,b,c)并返回一个选项值,如果a = name,则返回Some b;如果不是,则返回None不是。

每个步骤上的递归函数mapList与元组inputList的未处理部分一起工作,并且outputList仅在每个递归步骤中从匹配元素中累积部分。在每个递归步骤上,它都会检查inputList是否为空。如果是,那么我们就完成了,该返回累积结果了,否则我们将head元素从inputList中分离出来,并向其应用映射函数,从而更改累积列表(如果是这种情况)。然后,在inputList尾部和累加器上进行下一步递归操作。

答案 1 :(得分:1)

以函数式风格编写代码时,由于大多数列表/数组/序列函数都在后台使用了递归,因此经常会使用递归(如果不是显式的话则是隐式的)。

在F#中,您需要明确声明函数是递归的,因此您创建的函数将在其定义中使用let rec语法。根据您的要求,您的功能可能如下所示:

let rec myFilter (name: string) (input: (string * string * int) list) =
    ...

对于此类问题,您需要递归遍历列表,通常使用模式匹配来检查您是否在列表末尾,如果是,则返回一个空列表。

let rec myFilter (name: string) (input: (string * string * int) list) =
        match input with
        | [] -> []
        ...

现在,您需要编写模式匹配项,以根据提供的名称检查元组中的第一项。您可以在列表的开头使用模式匹配,并使用F#的when语法来解构列表的开头以进行比较

let rec myFilter (name: string) (input: (string * string * int) list) =
        match input with
        | [] -> []
        | ((a, b, _) :: rest) when a = name -> b :: myFilter name rest
        ...

a与查询名称匹配时,第二种情况匹配。当匹配时,它将返回一个新列表,其中b是列表的开头,然后它将获取其余的元组列表并递归调用myFilter。这是您递归遍历列表的方式。

我们还有一种情况需要检查:如果找不到匹配项,我们希望在不收集b的情况下继续浏览列表。这可以通过剥开头部并递归调用myFilter并仅发送其余的元组来表示。

let rec myFilter (name: string) (input: (string * string * int) list) =
        match input with
        | [] -> []
        | ((a, b, _) :: rest) when a = name -> b :: myFilter name rest
        | (_ :: rest) -> myFilter name rest

呼叫myFilter "a" [("a","x",3);("b","y",4);("a","z",5)]会产生["x"; "z"],如预期的那样。

答案 2 :(得分:0)

此任务需要您编写自己的递归列表处理功能。我能够通过使用两个子功能来实现它。

虽然@GeneBelitski和@ChadGilbert的答案非常适合学习,但它们不是尾递归的,所以我将添加自己的答案。

子功能aux将累加器和列表进行处理,并与列表的形状匹配。如果为空,则返回到目前为止累积的结果。如果它是一个带有尾巴的三元组,则将第一个元素与name进行比较,如果它们相等,则继续运行自身,将第二个元素放在累积的结果之前,并将其放在原始列表的尾部,否则只是到目前为止累积的结果,还有原始列表的末尾。

由于这种累积结果的方式会颠倒结果列表的顺序,因此我们需要在返回结果之前对其进行反转;这就是子功能rev的工作,如您所见,代码看起来几乎与aux相同,只是将元素添加到到目前为止累积的结果之前,但不进行任何比较或处理。

let filter_b name lst =
    let rec aux acc = function
        | [] -> acc
        | (a : string, b : string, c : int) :: tl ->
            if a = name
            then aux (b :: acc) tl
            else aux acc tl
    let rec rev acc = function
        | [] -> acc
        | hd :: tl -> rev (hd :: acc) tl
    lst
    |> aux []   // process list with aux function
    |> rev []   // reverse resulting list