【问题标题】:Sorting in functional programming languages函数式编程语言中的排序
【发布时间】:2020-10-13 19:45:03
【问题描述】:

我学习函数式编程已经有一段时间了,但我还没有在某处读过关于使用函数式编程语言进行排序的文章。

我知道基于价值交换的排序算法很难用函数式思想来实现,但我想知道在函数式编程中是否有任何排序算法?它们是什么?

谢谢。

【问题讨论】:

  • 我所知道的唯一问题是您无法使用不可变结构实现 in-place 算法。
  • @Goran Jovic:谢谢你的意见。我还在以程序的方式思考。 :-)
  • 不客气。顺便说一句,您也不能在命令式语言中就地使用不可变对象。只是命令式语言通常默认具有可变结构的库。
  • 可能对不可变数据结构进行就地排序,只要您在每一步都“复制”并且编译器可以证明从不使用旧值:即使用单子。然而,这并不简单:flyingfrogblog.blogspot.com/2010/08/…(免责声明:Jon Harrop 确实有一把斧头可以用 haskell 磨)。

标签: sorting functional-programming


【解决方案1】:

在函数式语言中,您编写一个函数,给定一个列表返回一个排序列表,而不是(当然)接触输入。

考虑例如合并排序...首先您编写一个函数,给定两个已排序的列表返回一个排序列表,其中包含两者的元素。例如:

def merge(a, b):
    if len(a) == 0:
        return b
    elif len(b) == 0:
        return a
    elif a[0] < b[0]:
        return [a[0]] + merge(a[1:], b)
    else:
        return [b[0]] + merge(a, b[1:])

然后您可以编写一个函数,通过合并列表的前半部分和后半部分的排序结果来对列表进行排序。

def mergesort(x):
    if len(x) < 2:
        return x
    else:
        h = len(x) // 2
        return merge(mergesort(x[:h]), mergesort(x[h:]))

关于 Python 语法:

  • L[0] 是列表 L 的第一个元素
  • L[1:] 是所有剩余元素的列表
  • 更一般地说,L[:n] 是最多第 n 个元素的列表,L[n:] 其余部分
  • A + B如果AB都是list就是拼接得到的list
  • [x] 是一个仅包含单个元素 x 的列表

PS:请注意,上面的 python 代码只是为了展示这个概念......在 Python 中,这不是一个合理的方法。我使用 Python 是因为我认为如果你知道任何其他常见的命令式语言,它是最容易阅读的。

【讨论】:

  • 这是一个比 larsman 更好的解决方案:不要使用快速排序,使用合并排序。
  • h = len(x) 应该是 m = len(x) / 2 但非常简洁的答案。谢谢。
  • @Yaniv.H:在 Python 中,// 是整数除法运算符,而不是注释。评论由#介绍。
  • 对它的运行时复杂性有什么想法吗?
  • @Abhishek:在 Python 中肯定很糟糕 :-) ...为了使这种方法合理,您需要链表进行合并 O(n1+n2) 并且拆分操作可能在奇数/偶数元素(需要一次通过)。在 Python 中,列表是作为数组实现的,并且没有尾递归优化,因此显示的合并实现非常糟糕。该代码仅用于展示功能性排序方法的想法。
【解决方案2】:

以下是一些在 Haskell 中实现的排序算法的链接:

归并排序通常是对链表进行排序的最佳选择。函数式语言通常在列表上运行,尽管我对大多数函数式语言如何实现列表知之甚少。在 Common Lisp 中,它们被实现为链表,我认为大多数函数式语言也是如此。

虽然可以为链表编写快速排序,但由于随机访问,它会受到糟糕的枢轴选择的影响。虽然这对完全随机输入无关紧要,但部分或完全排序的输入枢轴选择变得非常重要。其他排序算法也可能会受到链表缓慢的随机访问性能的影响。

另一方面,合并排序适用于链表,并且可以将算法实现为只需要链表的一些额外空间。

【讨论】:

  • 您应该指出,其中之一在功能世界中比所有其他人都更好:mergesort。
  • @Eamon,我之前没有听说过合并排序是用于函数式语言的算法。但是,我添加了一些来解释为什么会这样。如果您特别提到了我没有涉及的任何内容,那么我很好奇!
【解决方案3】:

这是 Haskell 中的经典 (pseudo-?) 快速排序:

sort []      =   []
sort (p:xs)  =   sort [x | x<- xs, x <= p]
              ++ [p]
              ++ sort [x | x <- xs, x > p]

参见,例如,c2.comLiteratePrograms.orgMerge sort 编写起来并不难,而且在实践中也更可靠。在 Scheme 中也可以这样做:

(define (sort xs)
  (if (null? xs)
    '()
    (let* ((p (car xs)) (xs (cdr xs)))
      (call-with-values (lambda () (partition (lambda (x) (<= x p)) xs))
                        (lambda (l r)
                          (append (sort l) (list p) (sort r)))))))

使用partition from SRFI-1(未经测试的代码)。另见chapter 4 of R6RS libraries

【讨论】:

  • 在我看来这会潜入额外的值,因为枢轴将包含在左侧子列表中。
  • 不是。请注意,输入列表通过模式匹配分为pxs。 (当然,xs 中可能有等于 p 的值。)
  • 关于这个版本的快速排序算法,Andrei Alexandrescu 在他的文章"On Iteration" 中有一些有趣的说法。基本上,它不是真正的快速排序,因为它不能就地工作;其次,它对枢轴元素的选择不是最优的。推荐阅读。
  • 这不是快速排序:informit.com/articles/printerfriendly.aspx?p=1407357 它不是原位的,并且会通常退化为二次复杂度:不要使用!
  • @Eamon:我并不是建议 OP 应该使用这个算法,它是如何在 FP 中实现排序的示例。 OP 应该在其语言的标准库中使用该算法。话虽如此,我参考 Alexandrescu 的有趣文章更新了我的答案,所以我希望你能收回 -1。
【解决方案4】:

您当然可以用函数式语言实现命令式的、具有副作用的排序算法。

我已经实现了一种排序算法,该算法在称为 ATS 的函数式编程语言中就地运行;所有突变都由线性类型处理。如果您对这类事情感兴趣,请给我留言。

【讨论】:

    【解决方案5】:

    我可能从坟墓中提出这个问题,但我认为全局比较方法可能对某些人有用(例如,我们没有对数字进行排序)。这是使用 ES6 的 TypeScript 版本:

    TL;DR

    type Comparator<T> = (itemA: T, itemB: T) => number;
    
    const mergeSort = <T>(list: T[], compare: Comparator<T>): T[] => {
      if (list.length <= 1) return list;
      const middleIndex = Math.floor(list.length / 2);
      const listA = mergeSort(list.slice(0, middleIndex), compare);
      const listB = mergeSort(list.slice(middleIndex), compare);
      return merge(listA, listB, compare);
    };
    
    const merge = <T>(listA: T[], listB: T[], compare: Comparator<T>): T[] => {
      if (listA.length === 0) return listB;
      if (listB.length === 0) return listA;
      return compare(listA[0], listB[0]) <= 0
        ? [listA[0], ...merge(listA.slice(1), listB, compare)]
        : [listB[0], ...merge(listA, listB.slice(1), compare)];
    };
    

    说明

    我们现在可以将一个额外的compare 函数传递给mergeSort 函数。这个compare 函数的定义与JavaScript's Array.prototype.sort() 方法的参数中的定义相同。

    例如,number 比较器将是:

    const compareNumbers: Comparator<number> = (numberA, numberB) =>
      numberA - numberB;
    

    ...User 对象比较器可能是:

    const compareUsersByAge: Comparator<User> = (userA, userB) =>
      userA.age - userB.age;
    

    ...如果需要,也可以进行更复杂的操作(例如字符串比较)。

    【讨论】:

      猜你喜欢
      • 2010-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-06
      • 2013-07-23
      • 2011-04-27
      • 2015-04-30
      • 2011-08-10
      相关资源
      最近更新 更多