算法与数据结构

编程的本质:Logic 与 Control 分离!

编程的本质

两篇论文

1976 年,瑞士计算机科学家,Algol W,Modula,Oberon 和 Pascal 语言的设计师 Niklaus Emil Wirth写了一本非常经典的书《Algorithms + Data Structures = Programs》(链接为 1985 年版) ,即算法 + 数据结构 = 程序。

这本书主要写了算法和数据结构的关系,这本书对计算机科学的影响深远,尤其在计算机科学的教育中。1979 年,英国逻辑学家和计算机科学家 Robert Kowalski 发表论文 Algorithm = Logic + Control,并且主要开发“逻辑编程”相关的工作。

Robert Kowalski 是一位逻辑学家和计算机科学家,从 20 世纪 70 年代末到整个 80 年代致力于数据库的研究,并在用计算机证明数学定理等当年的重要应用上颇有建树,尤其是在逻辑、控制和算法等方面提出了革命性的理论,极大地影响了数据库、编程语言,直至今日的人工智能。

Robert Kowalski 在这篇论文里提到:

任何算法都会有两个部分, 一个是 Logic 部分,这是用来解决实际问题的。

另一个是 Control 部分,这是用来决定用什么策略来解决问题。

Logic 部分是真正意义上的解决问题的算法,而 Control 部分只是影响解决这个问题的效率。

程序运行的效率问题和程序的逻辑其实是没有关系的。

我们认为,如果将 Logic 和 Control 部分有效地分开,那么代码就会变得更容易改进和维护。

注意,最后一句话是重点——如果将 Logic 和 Control 部分有效地分开,那么代码就会变得更容易改进和维护。

编程的本质

两位老先生的两个表达式:

  • Programs = Algorithms + Data Structures

  • Algorithm = Logic + Control

第一个表达式倾向于数据结构和算法,它是想把这两个拆分,早期都在走这条路。

他们认为,如果数据结构设计得好,算法也会变得简单,而且一个好的通用的算法应该可以用在不同的数据结构上。

第二个表达式则想表达的是数据结构不复杂,复杂的是算法,也就是我们的业务逻辑是复杂的。

我们的算法由两个逻辑组成,一个是真正的业务逻辑,另外一种是控制逻辑。

程序中有两种代码,一种是真正的业务逻辑代码,另一种代码是控制我们程序的代码,叫控制代码,这根本不是业务逻辑,业务逻辑不关心这个事情。

算法的效率往往可以通过提高控制部分的效率来实现,而无须改变逻辑部分,也就无须改变算法的意义。

举个阶乘的例子, X(n)!= X(n) X(n-1) X(n-2) X(n-3) 3 2 * 1。

逻辑部分用来定义阶乘:

  • 1 是 0 的阶乘;

  • 如果 v 是 x 的阶乘,且 u=v*(x+1),那么 u 是 x+1 的阶乘

用这个定义,既可以从上往下地将 x+1 的阶乘缩小为先计算 x 的阶乘,再将结果乘以 1(recursive,递归),也可以由下而上逐个计算一系列阶乘的结果(iteration,遍历)。

控制部分用来描述如何使用逻辑。最粗略的看法可以认为“控制”是解决问题的策略,而不会改变算法的意义,因为算法的意义是由逻辑决定的。

对同一个逻辑,使用不同控制,所得到的算法,本质是等价的,因为它们解决同样的问题,并得到同样的结果。

因此,我们可以通过逻辑分析,来提高算法的效率,保持它的逻辑,而更好地使用这一逻辑。

比如,有时用自上而下的控制替代自下而上,能提高效率。

而将自上而下的顺序执行改为并行执行,也会提高效率。

总之,通过这两个表达式,我们可以得出:

Program = Logic + Control + Data Structure

基本上所有的语言或者编程范式都是在解决上面的问题。也就是下面的这几个事:

  • Control 是可以标准化的。比如:遍历数据、查找数据、多线程、并发、异步等,都是可以标准化的
  • 因为 Control 需要处理数据,所以标准化 Control,需要标准化 Data Structure,我们可以通过泛型编程来解决这个事
  • 而 Control 还要处理用户的业务逻辑,即 Logic。所以,我们可以通过标准化接口 / 协议来实现,我们的 Control 模式可以适配于任何的 Logic

上述三点,就是编程范式的本质。

  • 有效地分离 Logic、Control 和 Data 是写出好程序的关键所在!

  • 有效地分离 Logic、Control 和 Data 是写出好程序的关键所在!

  • 有效地分离 Logic、Control 和 Data 是写出好程序的关键所在!

我们在写代码当中,就会看到好多这种代码,会把控制逻辑和业务逻辑放在一块。

里面有些变量和流程是跟业务相关的,有些是不相关的。

业务逻辑决定了程序的复杂度,业务逻辑本身就复杂,你的代码就不可能写得简单。

Logic,它是程序复杂度的的下限,然后,我们为了控制程序,需要再搞出很多控制代码,于是 Logic+Control 的相互交织成为了最终的程序复杂度。

把逻辑和控制混淆的示例

以冒泡排序为例子。

func sortMaoPao(nums []int) []int {
    var temp int
    for i := 0; i < len(nums); i++ {
        for j := i + 1; j < len(nums); j++ {
            if nums[i] > nums[j] {
                //nums[i], nums[j] = nums[j], nums[i]
                temp = nums[i]
                nums[i] = nums[j]
                nums[j] = temp
            }
        }
    }
    return nums
}

排序的逻辑无非就是遍历,比较大小,交换。

那我们可以把 logic 抽象出来。便于其他的排序复用代码,也更利于理解程序!

如对很多其他的数据结构如结构体中的数据的排序,代码可复用,这就是接口方法的好处了。

即写三个接口方法:

  • Len() int 返回长度
  • Less(i, j int) bool 下边为 i 的数据是否小于 下标为 j 的数据
  • Swap(i, j int) 交换
package main

import "fmt"

type sorter interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

type data struct {
    nums []int
}

func main() {
    d := &data{[]int{1, 3, 2, 4, 6}}
    sortMaoPao(d)
    fmt.Println(*d) //[1, 2, 3, 4, 6]
}

func (d data) Len() int {
    return len(d.nums)
}

func (d data) Less(i, j int) bool {
    if d.nums[i] > d.nums[j] {
        return true
    }
    return false
}

func (d *data) Swap(i, j int) {
    var temp int
    temp = d.nums[i]
    d.nums[i] = d.nums[j]
    d.nums[j] = temp
}

func sortMaoPao(s sorter) {
    length := s.Len()
    for i := 0; i < length; i++ {
        for j := i + 1; j < length; j++ {
            if s.Less(i, j) {
                s.Swap(i, j)
            }
        }
    }
}

尽管代码变多了,但是我们完全将逻辑与控制分开了。

Len() 只求大小,Less()只比较大小,Swap()只用来交换。

另外写了接口用法用于代码复用。

我们要追求高质量代码,而不仅仅是写出来!

推荐

我总结了一些编程中常用的书籍以及大佬20年的编程经验。

对于学习非常的有帮助!

公众号回复:书籍 领取

3分钟构建一本属于自己的电子书(附我的研究过程)

上一篇

家庭版windows安装使用docker

下一篇

你也可能喜欢

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片

个人微信公众号

we-tuiguang

qq交流群

群号:1046260719

微信扫一扫

微信扫一扫