Python转Scheme转换问题

4 投票
4 回答
896 浏览
提问于 2025-04-11 20:31

我现在正在尝试写一个Python程序,使用Scheme的语义,这样我以后可以把它翻译成Scheme,而不需要依赖很多Python特有的东西。

我想用A*算法、深度优先搜索和广度优先搜索来解决滑块拼图问题(就是有9个格子,里面放着8个拼图块,拼图块可以滑动)。大约11年前我在某个人工智能课程上用Lisp做过这个,但当时我对Lisp一无所知,甚至非常讨厌它,回想起来我发现我其实是在用Lisp写“C”语言。教授在这方面也没有给我太多帮助。

我有一个Python函数,可以很容易地交换两个拼图块:

def swap(p, (r1, c1), (r2, c2)):  
    # Swaps *any* two locations and returns new configuration  
    # Does not concern itself with zero location, etc  
    # Not sure how to do this functionally  
    p_p = p[:]  
    temp = p_p[r1][c1]  
    p_p[r1][c1] = p_p[r2][c2]  
    p_p[r2][c2] = temp  
    return p_p  

我想把这个函数改成你在《计算机程序的构造和解释》中可能会看到的样子,避免副作用等等。

但这引出了一个问题。我在《计算机程序的构造和解释》中看到的所有循环都是通过递归实现的。我没有看到任何关于如何以恒定时间访问数组、向量或列表的内容。我能想象出一种循环或递归的方式来读取一个元素,但我觉得更难想象如何在不使用像set!这样的会产生副作用的东西,也不需要复杂的if/then/else语句来决定哪个元素应该被改变的情况下,创建一个新的列表,改变某个元素。当然,当考虑到二维数组时,这就更让人困惑了。在这种情况下,使用Python的解决方案显而易见,因为它原生支持多维数组。

在C/C++/Python/Matlab/Lua/其他任何语言中,通过[i]语法访问列表/数组是很简单的,这直接对应于底层硬件的指针查找。我不明白Scheme是怎么做到这一点的,考虑到《计算机程序的构造和解释》中定义的原子操作似乎都是围绕循环和搜索的。向量和列表的数组访问函数是如何实现恒定时间访问的?(我在这方面完全是新手,所以我甚至不确定我在说哪些函数)。是否有某个C或汇编库在背后被秘密调用?在Scheme中是否有任何固有的恒定时间语义可以用于列表/数组/向量的访问,这样我就可以在Python中无负担地使用这种习惯?

我该如何用Scheme的语义重写上面的Python函数?我该如何用Scheme重写上面的函数?

4 个回答

1

这里有一种方法可以实现这个目标。你可以用一个函数来重新创建列表,这个函数会应用适当的映射。

def swap(p, (r1,c1), (r2,c2)):
    def getitem(r,c):
        if (r,c) == (r1,c1): return p[r2][c2]
        elif (r,c) == (r2,c2): return p[r1][c1]
        return p[r][c]
    return [ [getitem(r,c) for c in range(len(p[0]))] for r in range(len(p)) ]

你甚至可以更进一步,让这个函数成为实际的接口,每次交换时只需返回一个函数,这个函数会在传递给下面的函数之前进行适当的转换。虽然这样做的效率不是特别高,但这是一个相对简单的函数式方法,可以避免使用复杂的可变数据结构。

def swap(f, (r1,c1), (r2,c2)):
    def getitem(r,c):
        if (r,c) == (r1,c1): return f(r2,c2)
        elif (r,c) == (r2,c2): return f(r1,c1)
        return f(r,c)
   return getitem

l=[ [1,2,3], [4,5,6], [7,8,0]]
f=lambda r,c: l[r][c]    # Initial accessor function
f=swap(f, (2,1), (2,2))  # 8 right
f=swap(f, (1,1), (2,1))  # 5 down
print [[f(x,y) for y in range(3)] for x in range(3)]
# Gives: [[1, 2, 3], [4, 0, 6], [7, 5, 8]]
2

我大约一年前用Lisp写了一个8数码游戏的解法程序。我用的是一个包含3个列表的列表,每个子列表有3个元素,代表数字。虽然这个方法不是常量时间的,但它很方便移植。

如果你真的想用函数式编程来实现这个功能(其实Scheme并不强制你这样做),最简单的方法就是创建一些辅助函数,这些函数可以根据行和列来获取特定的值,或者根据行和列来设置一个值。这样做的好处是,不会直接修改原来的数据结构,而是根据旧的状态构建新的状态。

接着,你可以基于这些获取和设置操作来写一个交换操作。下面是我大约一年前在Common Lisp中写的代码,不过很容易转换成Scheme:

; getval
;
; This function takes a position (r . c) where and returns the corresponding
; number in the 8-puzzle state. For example, if you wanted (1 . 2) from
; ((1 2 3) (4 5 6) (7 8 9)), the value would be 6. The r and c values begin
; at 0.
;
; parameters:  pos    The position to get
;              state  The 8-puzzle state
; returns:     The value at pos in state
(defun getval (pos state)
  (if (null state) 'no-value
      (if (= 0 (car pos))
      (if (= 0 (cdr pos))
          (caar state)
          (getval (cons (car pos) (- (cdr pos) 1)) (list (cdar state))))
      (getval (cons (- (car pos) 1) (cdr pos)) (cdr state)))))

; setval
;
; This function returns a state where the value at pos is replaced by val.
; Like getval, this function is zero-based. Accessing beyond the size of
; the state is undefined (and probably broken)
;
; parameters:  pos    Position to set
;              val    Value to set
;              state  State to modify
; returns:     New state where pos is val
(defun setval (pos val state)
  (if (null state) '()
      (if (= 0 (car pos))
      (if (= 0 (cdr pos))
          (cons (cons val (cdar state)) (cdr state))
          (let ((temp (setval (cons (car pos) (- (cdr pos) 1)) val
                  (cons (cdar state) (cdr state)))))
        (cons (cons (caar state) (car temp)) (cdr temp))))
      (cons (car state) (setval (cons (- (car pos) 1) (cdr pos)) val (cdr state))))))

; state-swap
;
; This function takes a state and two positions and returns a new state with
; the values in those two positions swapped.
;
; parameters:  state  State to swap within
;              a      Position to swap with b
;              b      Position to swap with a
; return:      State with a swapped with b
(defun state-swap (state a b)
  (let ((olda (getval a state)) (oldb (getval b state)))
    (setval a oldb (setval b olda state))))
4

你发现自己最开始的问题是试图用Lisp来写C语言的风格。那你是不是又在犯同样的错误,试图用Python来写Scheme的风格呢?我总是努力把学习的语言X当作一种思维方式来理解,同时尽量用最符合X语言特点的方式来编程。

如果这是一个你知道会迁移到其他平台的商业应用,那这样做可能是合理的,但如果不是的话,我建议你一开始就直接用Scheme来写。

撰写回答