函数式编程/球拍翻译命令式嵌套循环

时间:2015-12-19 22:12:35

标签: functional-programming racket nested-loops

如果你有一个算法需要像(在C中)那样的东西:

int pts[length];

for(int i = 0; i < length; ++i){
   for(int j = 0; j < length; ++j){
      if(pts[i] == pts[j]){

         //modify both pts[i] and pts[j] somehow

      }
   }
}

您如何将其转化为功能风格?这意味着它返回一个数组或带有修改的点列表而不更改原始数据。可以使用嵌套递归,贴图/过滤器等,循环或其他类型的Racket样式来演示答案。虽然我在Racket中尝试这个,但是我可以接受其他语言的答案。

3 个答案:

答案 0 :(得分:1)

这里首先要说的是 算法在命令式风格中更有意义,大多数函数式语言为您提供了执行此操作所需的机制。您使用Racket作为示例,将此代码转换为执行变异的嵌套循环(例如使用racket的for)是完全合理的。

然而,还有一些算法在功能风格中具有完美的意义; 对此的回答很大程度上取决于modify both pts[i] and pts[j]某种方式。

让我们做点什么;假设我在派对上有一系列男孩,每个人都穿着一件衣服,如果其中两个穿着同样的衣服,他们的快乐就会减少。这是我头脑中的第一件事,但老实说它很好地概括了。

执行此操作的一种方法是使用嵌套循环,如您所述。为了在功能上做到这一点,我想我可能会这样写:

#lang racket

;; a boy is a structure: (make-boy symbol number)
(define-struct boy [dress-color happiness]
  #:transparent)

;; given a list of boys,
;; decrease each boy's happiness
;; by 3 for each other boy wearing the same color
;; dress
;; list-of-boys -> list-of-boys
(define (oh-noes boys)
  (oh-noes-helper boys (boy-colors boys)))

;; given a list of boys and a list of all the colors
;; decrease each boy's happiness
;; by 3 for each other boy wearing the same color
;; dress
;; list-of-boys list-of-colors -> list-of-boys
(define (oh-noes-helper boys all-colors)
  (cond [(empty? boys) empty]
        [else (cons (adjust-boy (first boys) all-colors)
                    (oh-noes-helper (rest boys) all-colors))]))

;; given a boy and a list of all the dress colors,
;; decrease the boy's happiness by three for every
;; *other* boy wearing the same color dress
;; boy list-of-colors -> boy
(define (adjust-boy boy all-colors)
  (make-boy (boy-dress-color boy)
            (- (boy-happiness boy)
               (* 3 (sub1 (num-occurrences (boy-dress-color boy)
                                           all-colors))))))

;; given a list of boys, return a list of colors
;; let's just use map...
(define (boy-colors boys)
  (map boy-dress-color boys))

;; given a list of colors, return the number of occurrences of that
;; color in the list
;; too lazy, just using foldl...
(define (num-occurrences element list)
  (length (filter (λ (c) (equal? element c)) list)))


(require rackunit)
(check-equal? (boy-colors (list (make-boy 'blue 13)
                             (make-boy 'green 15)
                             (make-boy 'orange 9)
                             (make-boy 'green 2)
                             (make-boy 'orange 2)
                             (make-boy 'green 1)))
              (list 'blue 'green 'orange 'green 'orange 'green))

(check-equal? (num-occurrences 'green
                               (list 'blue 'green 'orange
                                     'green 'orange 'green))
              3)

(check-equal? (oh-noes (list (make-boy 'blue 13)
                             (make-boy 'green 15)
                             (make-boy 'orange 9)
                             (make-boy 'green 2)
                             (make-boy 'orange 2)
                             (make-boy 'green 1)))
              (list (make-boy 'blue 13)
                    (make-boy 'green 9)
                    (make-boy 'orange 6)
                    (make-boy 'green -4)
                    (make-boy 'orange -1)
                    (make-boy 'green -5)))

请注意,我用非常HtDP的风格写了这个;如果你让自己自由使用Racket,你可以在几行内做到这一点。

这与原始实现相比如何?好吧,他们都有n ^ 2 运行时间。如果你想改进这个,你可能想要创建一个小的 哈希表将颜色映射到命令和命令中的出现次数 功能性解决方案。

编辑:

以下是5 loc的全部内容:

;; once again more rackety-small
(define (oh-noes2 boys)
  (define colors (map boy-dress-color boys))
  (for/list ([b (in-list boys)])
    (match-define (struct boy (c h)) b)
    (boy c (- h (* 3 (sub1 (length (filter (λ(x)(eq? x c)) colors))))))))

答案 1 :(得分:1)

你真的不像Algol那样做。您使用哈希表  摆脱O(n²)。

;; finds and replaces duplicates in O(n) time
(define (replace-duplicates replacement-proc lst)
  ;; this goes through the list once
  ;; mapping every value into a hash
  ;; with their frequency
  (define h
    (foldl (lambda (e h)
             (hash-update h e add1 0))
           (hash)
           lst))

  ;; goes through the list once more
  ;; and replacing the elements that has
  ;; a frequency over 1 with the result of 
  ;; (replacement-proc e)
  (map (lambda (e)
         (if (> (hash-ref h e) 1)
             (replacement-proc e)
             e))
       lst))

(replace-duplicates (lambda (x) 'duplicate) 
                    '(1 2 3 4 1)) 
; ==> (duplicate 2 3 4 duplicate)

答案 2 :(得分:1)

如果你想用list做事情,那么:

(define (my-list-update lst idx val) ; older version of Racket doesn't have list-update
  (if (= idx 0)
      (cons val (rest lst))
      (cons (first lst) (my-list-update (rest lst) (- idx 1) val))))

(define LENGTH 10)
(define initial-lst (build-list LENGTH (lambda (_) 0))) ; create list consisting of 0 for LENGTH entries
(define arr
  (foldl
    (lambda (i lst)
      (foldl
        (lambda (j lst) ; shadowing, change if you don't like it
          (my-list-update lst i (+ (list-ref lst i) (+ i j))))
        lst (range LENGTH))) ; for j = 0 -> LENGTH - 1
    initial-lst (range LENGTH))) ; for i = 0 -> LENGTH - 1

上面的代码几乎等同于以下伪代码:

int arr[N] = initial_lst;
for(int i = 0; i < N; ++i){
    for(int j = 0; j < N; ++j){
        arr[i] += i + j; // not exactly, in Racket, we didn't mutate anything
    }
}

后讨论

  

您如何将其转化为功能风格?

嗯,“功能风格”的定义是什么?特别是,你真的想要一个数组作为输出吗?因为数组是一个可变数据结构,所以它不是纯粹的功能。这对你来说是否可以接受?

  

返回带有修改的数组或点列表,而不更改原始数据。

如果要在不修改原始数组的情况下输出数组。这很简单:只需将原始数组中的所有条目复制到新数组即可。然后,您可以改变新数组,做任何你想做的事情,并返回它。然而,这成了势在必行的风格。

如果您真的希望程序纯粹是功能性的,那么阵列就不适合您。使用上面示例的列表并不理想,因为更新和获取需要O(n),而数组可以在O(1)中执行。有一种更好的方法:使用Okasaki's random access list。此数据结构纯粹是功能性的,同时允许您更新O(log n)中的条目。它的实现非常简单(与AVL Tree等数据结构相比),您可以轻松地使用它代替列表。