【问题标题】:How to write a pattern match in Ocaml so it is easy to scale?如何在 Ocaml 中编写模式匹配以便于扩展?
【发布时间】:2012-12-29 21:14:01
【问题描述】:

我正在学习Jason Hickey's Introduction to Objective Caml

有一个这样的练习:

练习 4.3 假设我们有一个基于以下替换密码的密码系统,其中每个明文都根据下表进行加密。

Plain     | A B C D
--------------------
Encrypted | C A D B

例如,字符串BAD 将被加密为ACB

写一个函数check,给定一个明文字符串s1和一个密文字符串s2,当且仅当 s2s1 的密文时返回 true。如果 s1 不是纯文本字符串,您的函数应该引发异常。您可能希望参考第 8 页上的字符串操作。随着字母变大,您的代码如何扩展? [强调添加]


基本上,我用might-be-stupid-naive 的方式编写了两个函数用于这个练习。

我想先就我的解决方案征求意见。

那么我想询问练习中突出显示的缩放解决方案的提示。


使用 if else

let check_cipher_1 s1 s2 = 
    let len1 = String.length s1 in
        let len2 = String.length s2 in              
            if len1 = len2 then
                    let rec check pos =
                        if pos = -1 then
                            true
                        else
                            let sub1 = s1.[pos] in
                                let sub2 = s2.[pos] in
                                    match sub1 with
                                        | 'A' -> (match sub2 with
                                                    |'C' -> check (pos-1)
                                                    | _ -> false)
                                        | 'B' -> (match sub2 with
                                                    |'A' -> check (pos-1)
                                                    | _ -> false)
                                        | 'C' -> (match sub2 with
                                                    |'D' -> check (pos-1)
                                                    | _ -> false)
                                        | 'D' -> (match sub2 with
                                                    |'B' -> check (pos-1)
                                                    | _ -> false)
                                        | _ -> false;
                                            in
                                                check (len1-1)
            else
                false

到处使用纯匹配

let check_cipher_2 s1 s2 = 
    let len1 = String.length s1 in
        let len2 = String.length s2 in
            match () with
                | () when len1 = len2 -> 
                        let rec check pos =
                            match pos with
                                | -1 -> true
                                | _ -> 
                                    let sub1 = s1.[pos] in
                                        let sub2 = s2.[pos] in
                                            (*http://stackoverflow.com/questions/257605/ocaml-match-expression-inside-another-one*)
                                            match sub1 with
                                                | 'A' -> (match sub2 with
                                                            |'C' -> check (pos-1)
                                                            | _ -> false)
                                                | 'B' -> (match sub2 with
                                                            |'A' -> check (pos-1)
                                                            | _ -> false)
                                                | 'C' -> (match sub2 with
                                                            |'D' -> check (pos-1)
                                                            | _ -> false)
                                                | 'D' -> (match sub2 with
                                                            |'B' -> check (pos-1)
                                                            | _ -> false)
                                                | _ -> false
                                                    in
                                                        check (len1-1)
                | () -> false

好的。上述两种方案类似。

我制作了这两个,因为这里http://www.quora.com/OCaml/What-is-the-syntax-for-nested-IF-statements-in-OCaml,有人说if else不是首选。

这基本上是我一生中第一次编写not-that-simple 函数。所以我真的很想在这里寻求建议

例如,

  • 如何改进这些解决方案?
  • 我应该更喜欢match 而不是if else
  • 我是否正确设计了recuse the rec
  • 如果in check (len1-1) 正确?

缩放它

练习询问How does your code scale as the alphabet gets larger?。我现在真的没有头绪。在 Java 中,我会说我会有一个 map,然后对于 s1 中的每个字符,我正在寻找 s2 以获取相应的字符,并查看它是否是映射中的值。

对此有何建议?

【问题讨论】:

  • 具体来说,使您的代码看起来很奇怪的主要问题是您在每个let 之后缩进。你绝对不想那样做。有关格式化 OCaml 的一些建议可以在 Caml Programming Guidelines 中找到——尤其是“如何缩进 let ... in 结构”一节。
  • @JeffreyScofield 这也是需要的。

标签: functional-programming ocaml


【解决方案1】:
  • 如何改进这些解决方案?

您滥用缩进使程序更难阅读。消除不必要的选项卡并将check 移至外部范围以提高可读性:

let check_cipher_1 s1 s2 = 
    let rec check pos =
        if pos = -1 then
            true
        else
            let sub1 = s1.[pos] in
            let sub2 = s2.[pos] in
            match sub1 with
            | 'A' -> (match sub2 with
                      |'C' -> check (pos-1)
                      | _ -> false)
            | 'B' -> (match sub2 with
                      |'A' -> check (pos-1)
                      | _ -> false)
            | 'C' -> (match sub2 with
                      |'D' -> check (pos-1)
                      | _ -> false)
            | 'D' -> (match sub2 with
                      |'B' -> check (pos-1)
                      | _ -> false)
            | _ -> false in
    let len1 = String.length s1 in
    let len2 = String.length s2 in              
    if len1 = len2 then
            check (len1-1)
    else false
  • 我应该更喜欢 match 而不是 if else?

视情况而定。如果您在第二个函数 (match () with | () when len1 = len2) 中演示的模式匹配是肤浅的,那么与简单的 if/else 构造相比,它会带来 no 值。如果您对值进行模式匹配,则它比if/else 更好,并且在您使用高级构造时可能更短。例如,您可以通过匹配元组来缩短函数:

let check_cipher_1 s1 s2 =  
    let rec check pos =
        if pos = -1 then
           true
        else
            match s1.[pos], s2.[pos] with
            | 'A', 'C' | 'B', 'A' 
            | 'C', 'D' | 'D', 'B' -> check (pos-1)
            | _ -> false in
    let len1 = String.length s1 in
    let len2 = String.length s2 in 
    len1 = len2 && check (len1 - 1)

这里我们还使用 Or 模式对具有相同输出操作的模式进行分组,并将不必要的 if/else 块替换为 &&

  • 我是在设计记录还是正确使用记录?
  • 如果检查 (len1-1) 正确?

你的函数看起来不错。没有比在 OCaml 顶层测试几个输入更好的方法了。

缩放它

模式的数量随着字母的大小线性增长。这是相当不错的国际海事组织。

【讨论】:

  • 谢谢,你说的“很不错”是什么意思?你的意思是现有的解决方案随着字母的大小线性增长是好的?
  • 我的意思是当字母表增长时你不必添加太多代码。当然,一个通用的解决方案是使用您所描述的map
  • 关于indentation,您是否应该在2nd segment 代码中的match expression 之前使用indent
  • 是的,我应该。现在已经修好了。
【解决方案2】:

这是一个简单的解决方案:

让 tr = 函数 | 'A' -> 'C' | 'B' -> 'A' | 'C' -> 'D' | 'D' -> 'B' | _ -> failwith "不是明文" 让检查 ~tr s1 s2 = (String.map tr s1) = s2 检查〜tr“坏”“ACD”

您可以通过使用 tr 组合来添加更多字母。即

let comp c1 c2 x = try (c1 x) with _ -> (c2 x) 让 tr2 = comp tr (函数 | 'X' -> 'Y')

【讨论】:

  • ~tr 是一个带标签的参数。 hickey 在他的书中很早就解释了(第 3.3 节),所以如果你忘记了,你应该复习一下。在这个特定的例子中,它是每个字符的翻译函数。我们希望传递该函数而不是硬编码,以便我们可以构建不同的翻译函数。示例:check ~tr:(comp (function 'X' -> 'Y') (function 'A' -> 'B')) "XAA" "YBB"
  • 是的,我还记得~something 是一个带标签的参数。但是,我仍然不确定我是否理解您的观点。 ~tr这里不是标签吗?
  • 好的,我在 3.3 中修改了~label,正如你提到的。所以,如果我们之前定义了tr,为什么我们需要~tr' in let check ~tr s1 s2 = (String.map tr s1) = s2? can we just use let check s1 s2 = (String.map tr s1) = s2` ?
【解决方案3】:

最简单的解决方案似乎只是加密文本并比较结果:

let cipher_char = function
   | 'A' -> 'C'
   | 'B' -> 'A'
   | 'C' -> 'D'
   | 'D' -> 'B'
   | _ -> failwith "cipher_char"
let cipher = String.map cipher_char
let check_cipher s1 s2 = (cipher s1 = s2)

cipher_char 函数随字母表的大小线性缩放。为了使其更紧凑和通用,您可以使用某种形式的查找表,例如

(* Assume that only letters are needed *)
let cipher_mapping = "CADB"
let cipher_char c =
   try cipher_mapping.[Char.code c - Char.code 'A']
   with Invalid_argument _ -> failwith "cipher_char"

【讨论】:

  • 对于cipher s1 = s2,基本上我可以这样理解cipher s1返回一个列表,然后我们看看这个列表是否等于s2,对吧?
  • 您能否进一步解释一下您的第二段代码? try cipher_mapping.[Char.code c - Char.code 'A']的目的是什么?
  • 是的,除了 cipher s1 返回一个字符串而不是列表,即 s1 的密码。第二部分cipher_mapping.[...]cipher_mapping中查找c对应的字符,假设为一串密文字符,每个明文字符对应一个,第一个字符对应'A'。因此它可以通过字符代码来索引。它周围的try ... with 仅用于异常处理,以防字符串访问超出范围(即不支持原始字符)。
猜你喜欢
  • 2011-10-02
  • 1970-01-01
  • 1970-01-01
  • 2016-07-21
  • 1970-01-01
  • 1970-01-01
  • 2022-01-11
  • 2021-09-21
  • 2011-03-03
相关资源
最近更新 更多