如何在列表中搜索元组的元素?

时间:2019-05-20 06:35:08

标签: list f# tuples return-value

对于我的任务,我必须列出婚礼上不能彼此相邻的人的元组列表。然后将其与桌上的人员列表进行比较。如果同一个元组中的任何两个人都在表列表中,则它应该为false。否则为真。这是我第一次用F#编码,因此语法在这里使我丧命。

let isValidTable (cantSit:(string*string) list) (people: string list) =

    let truth = true;
    let rec matchPeople cantSit person1 person2= 
        match cantSit with
        | [] -> None
        | head :: tail ->
            let (person1,person2) = head 
            if ((List.exists(fun names -> names = person1) people) && (List.exists(fun names2 -> names2 = person2) people)) then
                let result2 = false
            else 
                matchPeople tail fst snd;;
                let result = true;;
    matchPeople cantSit fst snd;;


let x = [("Eric", "Mark"); ("Anna", "Maya"); ("Beth", "Hope")];; 
let weddingList = ["Eric"; "Anna"; "Beth"]
let validOrNah = isValidTable x weddingList;;
printf("\n%O") validOrNah;;

对我来说,问题是我不断收到诸如“ matchPeople构造函数未定义”或“ let未完成”之类的错误。任何帮助将不胜感激,谢谢!

之前:我有

if(first person is in list and second person is in list)
then Some false
else recursive statement;

此代码编译无误,但仅输出null。我知道变量在F#中是不可变的,这使这变得很难。

1 个答案:

答案 0 :(得分:7)

您的代码中有许多问题,都是由您不熟悉F#引起的。我将逐行介绍它,并尝试解释您尚未理解的内容。

let isValidTable (cantSit:(string*string) list) (people: string list) =

这很好。

    let truth = true;

完全不需要此分配,因为您永远不会在代码的其他任何地方使用名称truth。而且,如果确实需要使用它,则只需将其替换为常量true,它会更好看。让我们完全删除此行。

顺便说一句,与C类语言不同,F#行末尾不需要分号。双分号仅在F#Interactive解释器中使用,以告诉解释器“我已经完成输入此表达式”。这使您可以将表达式拆分成多行,而无需解释器猜测完成的时间(因为F#中的许多局部表达式可以 look 完成,因此需要显式的表达式终止)。我不会在每次出现分号时都提到这一点,但是您可以在行尾删除所有分号(和双分号)。在F#中唯一需要分号的地方是列表中各项之间的位置,例如xweddingList中。

转到下一行。

    let rec matchPeople cantSit person1 person2=

这看起来不错,但是实际上,您根本不需要person1person2参数。我的猜测是您将它们包含在参数列表中,因为您认为在创建它们之前需要声明变量,但这根本不是F#的工作方式。当您稍后在函数中编写let (person1, person2) = head时,将在此处创建变量person1person2,并且不需要将它们作为函数参数。这样就可以删除它们,并且您的函数定义将变为let rec matchPeople cantSit =

        match cantSit with

这很好。

        | [] -> None

这是一个小错误。在其他地方,您似乎想返回一个布尔值,但是在这里,您返回一个选项。在F#中,match和/或if...else所有分支必须返回相同的类型。您的isValidTable函数显然旨在返回一个布尔值,matchPeople也是如此,因此也应该是一个布尔值。问题是,此行应返回false还是true?要回答该问题,请考虑一个空白的cantSit列表在您的问题域的语义中意味着什么。这意味着没有人不能彼此坐下,因此无论谁坐在桌子上,座位表都是有效的。或者,当然,这也可能意味着您已通过多个递归调用到达cantSit列表的末尾,在这种情况下,您在此处返回的值将是您从上一个递归调用最终返回的值。同样,返回true是您想要的,因为如果您早先发现了一个无效的坐对,那么您将立即返回false而不会进行另一个递归调用。因此,如果您到达cantSit列表为空的位置,则可以返回true

        | head :: tail ->

这很好。

            let (person1,person2) = head 

这不仅很好,还不错。

            if ((List.exists(fun names -> names = person1) people) && (List.exists(fun names2 -> names2 = person2) people)) then

这很好,但是可以简化。这里有一个List.contains函数可以满足您的需求。类型List.exists (fun item -> item = value) itemList)的任何调用都可以简化为List.contains item itemList。这样就变成了if (List.contains person1 people) && (List.contains person2 people) then,它更容易快速阅读和理解。

                let result2 = false

这是不正确的; F#中的let赋值没有值,并且由于它是if块中的最后一个表达式,因此意味着if块的条件为true时将不具有值。这就是为什么您遇到“未完成”错误的原因:在F#中,let赋值可能永远不是代码块的最后一个表达式。它必须始终跟随一个具有值的表达式。您实际上想在这里做的事情很清楚:如果两个人都在列表中,您想返回false。您只需在这一行中编写false即可;我会在这里解释更多。

在F#中,if...else是一个返回值的表达式,而不是大多数其他语言中的语句。因此,您可以编写如下内容:

let n = 5
let s = if n % 2 = 0 then "Even" else "Odd"
printfn "%s" s  // Prints "Odd"

在这里,您的if...elsematch表达式的一种情况的最后一行,因此其值将是match表达式的值。 match表达式是matchPeople函数的最后一个表达式,因此其值将是该函数的返回值。因此,如果您发现不能配对的匹配对(此if...else表达式的真正分支),则只需要一行写着false,这就是如果函数到达该分支,则返回该函数的值。

进入下一行。

            else 

这很好,很明显。

                matchPeople tail fst snd;;

一旦删除了fstsnd(因为我们更改了函数签名,使得matchPeople现在只接受一个参数),这很好,并且删除了前面提到的分号。

                let result = true;;

与前面的let result2 = false行相同的注释:let分配可能永远不会是F#中代码块的最后一行。在这里,您要做的是让递归matchPeople调用的结果成为您的“外部”递归级别的最终结果。您只需删除此let result = true行即可,从而使matchPeople调用成为else块的最后一行。这意味着它的结果将是else块的结果,并且由于if...else表达式是match这种情况的最后一个表达式,因此递归调用将是的最后一个表达式match语句。并且由于match语句是matchPeople函数的最后一个表达式,因此它的结果也将是整个函数的结果(如果代码到达else分支)。这意味着此递归调用位于 tail位置,这在以后很重要:如果调用的结果将是整个函数的结果,则调用位于尾部。尾部位置的呼叫通常简称为“尾部呼叫”。在这里,我不会深入探讨尾调用,只是要说编译器可以优化尾调用,这样无论您执行了多少次递归调用,它都不会导致堆栈溢出错误。现在,我们将尾部调用放在一边,然后继续查看您的其余代码:

    matchPeople cantSit fst snd;;

与其他调用一样,只需删除fstsnd参数(以及双分号),就可以了。

let x = [("Eric", "Mark"); ("Anna", "Maya"); ("Beth", "Hope")];; 
let weddingList = ["Eric"; "Anna"; "Beth"]
let validOrNah = isValidTable x weddingList;;
printf("\n%O") validOrNah;;

一旦删除了不必要的双分号,所有这些都很好。我可能会在最后一行写上printfn "%O" validOrNah,但这是个人喜好:我喜欢在输出的末尾打印换行符,而不是在开头(printfn在您询问的内容之后打印换行符进行打印,而在函数名中没有结尾printf的{​​{1}}不会打印结尾的换行符)。但是你在这里写的很好。

进行所有这些更改,这就是您的代码变成的内容:

n

由于您的逻辑正确(做得好!),因此我没有做任何更改,因此,一旦您完成了我建议的语法修复,就应该运行并打印出正确的结果。