笔记8-《程序员的数学》乘车费用问题与逻辑  

目录
  1. 第二章 逻辑–真与假的二元世界
  2. 为何逻辑如此重要
  3. 乘车费用问题–兼顾完整性和排他性
  4. 命题及其真假
  5. 使用 if 语句分解问题

第二章 逻辑–真与假的二元世界

  • 为何逻辑如此重要

    • 逻辑是消除歧义的工具
    • 致对逻辑持否定意见的读者
  • 乘车费用问题–兼顾完整性和排他性

    • 车费规则、命题及其真假
    • 有没有遗漏、有没有重复、注意边界值
  • 使用 if 语句分解问题

    • 逻辑的基本是两个分支

为何逻辑如此重要

  • 逻辑是消除歧义的工具

  • 自然语言极易产生歧义,连「或者」都不止一个正确意义

    • 课前对话:按下紧急按钮或者水位高于危险水位时报警,「或者」是排他的吗
    • 「他现在在大阪或者东京」,「或者」是排他的吗
  • 规格说明书一般用自然语言描述,程序员必须走出歧义的迷宫

  • 致对逻辑持否定意见的读者

    • 计算机不按感情运行,逻辑上有错的程序不会正确运行
    • 「逻辑冰冷且机械死板」正因如此才有用
    • 程序员站在人类和计算机的分界线上

乘车费用问题–兼顾完整性和排他性

  • 费用规则 A:6 岁以上的乘客 100 元,不到 6 岁的乘客 0 元

  • 13 岁的 Alice 车费 100 元,4 岁的 Bob 车费 0 元

  • 6 岁的 Charlie 车费 100 元,因为「6 岁以上」包含 6 岁

  • 有没有「遗漏」:不管针对哪个乘客,都能判定命题的真假

    • 规则 B「大于 6 岁 100 元 / 不到 6 岁 0 元」遗漏了正好 6 岁
  • 有没有「重复」:要确认某位乘客是不是有两种费用标准

    • 规则 C「6 岁以上 100 元 / 6 岁以下 0 元」,6 岁时两种费用都适用
    • 规则 D 多写一条「正好 6 岁 100 元」,重复但不矛盾
  • 画一根数轴辅助思考,命题为真的年龄范围用 ⊙ 标记,为假的用 ○ 标记

  • 注意边界值,0 岁和 6 岁是边界所在,错误往往出在边界值上

    规则0~5 岁6 岁7 岁以上问题
    A 6 岁以上 / 不到 6 岁0 元100 元100 元无
    B 大于 6 岁 / 不到 6 岁0 元无100 元遗漏 6 岁
    C 6 岁以上 / 6 岁以下0 元100 元 / 0 元100 元重复 6 岁
  • 兼顾完整性和排他性

    • 没有遗漏即完整性,没有重复即排他性
    • 大问题分解成小问题时,常用的方法就是检查完整性和排他性

命题及其真假

  • 能够判断对错的陈述句叫做命题,命题正确称为真,不正确称为假

    命题真假
    Alice(13 岁) 的年龄在 6 岁以上真命题
    Bob(4 岁) 的年龄在 6 岁以上假命题
    Charlie(6 岁) 的年龄在 6 岁以上真命题
  • 命题要么为 true 要么为 false,同时满足真和假的不能称为命题

#ruby
# 规则写成条件, 枚举年龄看有没有遗漏和重复
rules = {
"A" => [[->(a){ a >= 6 }, 100], [->(a){ a < 6 }, 0]],
"B" => [[->(a){ a > 6 }, 100], [->(a){ a < 6 }, 0]],
"C" => [[->(a){ a >= 6 }, 100], [->(a){ a <= 6 }, 0]],
"D" => [[->(a){ a >= 6 }, 100], [->(a){ a == 6 }, 100], [->(a){ a < 6 }, 0]]
}
(0..12).each do |age|
rules.each do |name, rs|
hit = rs.select { |c, _| c.call(age) }
next if hit.size == 1
fee = hit.map { |_, f| f }.uniq.sort
puts "规则#{name} 年龄#{age}: #{hit.empty? ? '遗漏' : "重复 费用#{fee}"}"
end
end
# 规则B 年龄6: 遗漏
# 规则C 年龄6: 重复 费用[0, 100]
# 规则D 年龄6: 重复 费用[100]

使用 if 语句分解问题

  • 把问题分解成命题为真和为假两部分

    • 显示年龄在 6 岁以上的乘客的费用:显示 100 元
    • 显示年龄不到 6 岁的乘客的费用:显示 0 元
  • 这种分解就是程序里的 if 语句

#ruby
# 查车费: 先判断 "乘客的年龄在 6 岁以上" 这个命题
def fare(age)
if age >= 6
100
else
0
end
end
[4, 6, 13].each do |age|
puts "年龄#{age}: 命题(>=6)=#{age >= 6} 车费#{fare(age)}元"
end
# 年龄4: 命题(>=6)=false 车费0元
# 年龄6: 命题(>=6)=true 车费100元
# 年龄13: 命题(>=6)=true 车费100元
  • 逻辑的基本是两个分支

    • 熟练的程序员不用特意想完整性和排他性也能写出 if 语句
    • 可要写几十条、几百条 if 语句,组合中稍出点错就产生 bug
    • 逻辑从根本上说是对完整性和排他性的组合表达
123.8k