对于我的任务,我必须列出婚礼上不能彼此相邻的人的元组列表。然后将其与桌上的人员列表进行比较。如果同一个元组中的任何两个人都在表列表中,则它应该为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;;
Run Code Online (Sandbox Code Playgroud)
对我来说,问题是我不断收到诸如“ matchPeople构造函数未定义”或“ let未完成”之类的错误。任何帮助将不胜感激,谢谢!
以前:我有
if(first person is in list and second person is in list)
then Some false
else recursive statement;
Run Code Online (Sandbox Code Playgroud)
此代码编译无错误,但仅打印为空。我知道变量在F#中是不可变的,这使这变得很难。
您的代码中有许多问题,都是由您对F#的不熟悉引起的。我将逐行介绍它,并尝试解释您尚未理解的内容。
let isValidTable (cantSit:(string*string) list) (people: string list) =
Run Code Online (Sandbox Code Playgroud)
这可以。
let truth = true;
Run Code Online (Sandbox Code Playgroud)
完全不需要分配,因为您永远不会truth在代码中的其他任何地方使用该名称。而且,如果确实需要使用它,则只需将其替换为常量true,它就会更好看。让我们完全删除此行。
顺便说一句,与C类语言不同,F#行末尾不需要分号。双分号仅在F#Interactive解释器中使用,以告诉解释器“我已经完成输入此表达式”。这使您可以将表达式拆分成多行,而无需解释器猜测完成的时间(因为F#中的许多局部表达式看起来很完整,因此需要显式的表达式终止)。我不会在每次出现分号时都提到这一点,但是您可以在行尾删除所有分号(和双分号)。F#中唯一需要分号的地方是列表中的项目之间,例如in x或in中weddingList。
转到下一行。
let rec matchPeople cantSit person1 person2=
Run Code Online (Sandbox Code Playgroud)
看起来不错,但实际上,您根本不需要person1和person2参数。我的猜测是您将它们包含在参数列表中,因为您认为在创建它们之前需要声明变量,但这根本不是F#的工作方式。当您let (person1, person2) = head稍后在函数中编写代码时,就在此处创建变量person1和person2,而无需将它们作为函数参数。因此,您可以删除它们,您的函数定义将变为let rec matchPeople cantSit =
match cantSit with
Run Code Online (Sandbox Code Playgroud)
这可以。
| [] -> None
Run Code Online (Sandbox Code Playgroud)
这是一个小错误。在其他地方,您似乎想返回一个布尔值,但是在这里,您返回一个选项。在F#中,和/或的所有分支都必须返回相同的类型。您的函数显然打算返回一个布尔值,所以也是如此,因此它也应该是一个布尔值。问题是,这条线应该返回还是?要回答该问题,请考虑空列表在问题域的语义中意味着什么。这意味着没有人不能彼此坐下,因此无论谁坐在桌子上,座位表都是有效的。或者,当然,这也可能意味着您已经结束了matchif...elseisValidTablematchPeoplefalsetruecantSitcantSit按多个递归调用列出,在这种情况下,您在此处返回的值将是您从上一个递归调用最终返回的值。再说一次,返回true就是您想要的,因为如果您早先发现了一个无效的坐对,那么您将false立即返回而不会再进行另一个递归调用。因此,如果您到达cantSit列表为空的位置,那么就可以返回了true。
| head :: tail ->
Run Code Online (Sandbox Code Playgroud)
这可以。
let (person1,person2) = head
Run Code Online (Sandbox Code Playgroud)
这不仅很好,还不错。
if ((List.exists(fun names -> names = person1) people) && (List.exists(fun names2 -> names2 = person2) people)) then
Run Code Online (Sandbox Code Playgroud)
很好,但是可以简化。这里有一个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
Run Code Online (Sandbox Code Playgroud)
这是不正确的。letF#中的赋值没有值,并且由于它是if块中的最后一个表达式,因此,if如果条件成立,则该块将没有值。这就是为什么出现“未完成”错误的原因:在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"
Run Code Online (Sandbox Code Playgroud)
在这里,your if...else是match表达式的一种情况的最后一行,因此其值将是match表达式的值。和match表达是最后表达matchPeople功能,所以它的价值将是函数的返回值。因此,如果您发现一个不能配对的匹配对(此if...else表达式的真实分支),则只需要用一行写成false,这将是函数到达该分支的返回值。
移至下一行。
else
Run Code Online (Sandbox Code Playgroud)
显然,这很好。
matchPeople tail fst snd;;
Run Code Online (Sandbox Code Playgroud)
一旦删除了fstand snd(因为我们更改了函数签名,以便matchPeople现在只使用一个参数),并且如上所述删除了分号,就可以了。
let result = true;;
Run Code Online (Sandbox Code Playgroud)
与前let result2 = false一行相同的注释:let赋值可能永远不会是F#中代码块的最后一行。在这里,您要做的就是让递归matchPeople调用的结果成为您的“外部”递归级别的最终结果。您可以通过简单地删除这一let result = true行来做到这一点,从而使matchPeople调用成为该else块的最后一行。这意味着它的结果将是该else块的结果,并且由于if...else表达式是这种情况的最后一个表达式match,因此递归调用将是该match语句的最后一个表达式。并且由于该match语句是matchPeople函数的最后一个表达式,因此其结果也将是整个函数的结果(如果代码到达了else科)。这意味着此递归调用位于尾位置,这在以后是一个重要概念:如果调用的结果将是整个函数的结果,则该调用位于尾位置。尾部位置的呼叫通常简称为“尾部呼叫”。在这里,我不会深入探讨尾调用,只是说编译器可以优化尾调用,这样无论您执行了多少次递归调用,它都不会导致堆栈溢出错误。现在,我们将尾调用放在一边,然后继续看一下代码的其余部分:
matchPeople cantSit fst snd;;
Run Code Online (Sandbox Code Playgroud)
与其他调用一样,只需删除fst和snd参数(和双分号),就可以了。
let x = [("Eric", "Mark"); ("Anna", "Maya"); ("Beth", "Hope")];;
let weddingList = ["Eric"; "Anna"; "Beth"]
let validOrNah = isValidTable x weddingList;;
printf("\n%O") validOrNah;;
Run Code Online (Sandbox Code Playgroud)
一旦删除了不必要的双分号,所有这些都很好。我可能会写printfn "%O" validOrNah在最后一行,但是这是个人喜好:我喜欢在我的输出端打印一个换行符,而不是开始(printfn打印什么之后,你问它换行打印,而printf无需尾随n在函数名称不显示尾随换行符)。但是你在这里写的很好。
进行所有这些更改,这就是代码变成的内容:
let isValidTable (cantSit:(string*string) list) (people: string list) =
let rec matchPeople cantSit =
match cantSit with
| [] -> true
| head :: tail ->
let (person1,person2) = head
if (List.contains person1 people) && (List.contains person2 people) then
false
else
matchPeople tail
matchPeople cantSit
let x = [("Eric", "Mark"); ("Anna", "Maya"); ("Beth", "Hope")]
let weddingList = ["Eric"; "Anna"; "Beth"]
let validOrNah = isValidTable x weddingList
printfn "%O" validOrNah
Run Code Online (Sandbox Code Playgroud)
因为它是正确的,所以我没有对您的逻辑进行任何更改(做得好!),因此,一旦您完成了我建议的语法修复,就应该运行并打印出正确的结果。