轨道
/
Julia
Julia
/
练习
/
排序考验
排序考验

排序考验

学习练习

简介

和大多数编程语言一样,Julia 提供了丰富多样的集合类型。

有些集合天然就是有序的(包括 Vectors 和更高维的 Arrays),我们可以按位置访问其中的元素。另一些集合是无序的(Sets、Dicts),元素必须通过键或值来访问。

最简单的情况:假设有一个由 Ints 组成的 Vector,我们想按升序对它排序。

julia> v = [3, 2, 4, 1]
4-element Vector{Int64}:
 3
 2
 4
 1

julia> sort(v)
4-element Vector{Int64}:
 1
 2
 3
 4

# sort descending
julia> sort(v, rev=true)
4-element Vector{Int64}:
 4
 3
 2
 1

很简单! sort() 函数名字一目了然,看起来会返回排好序的 Vector。

开个玩笑! 学到教学大纲的这个阶段,你也许已经(正确地)猜到,其中还有一些细微之处需要我们讨论。

会修改输入的函数与不修改输入的函数

运行 sort(v) 之后,v 是什么?

julia> v
4-element Vector{Int64}:
 3
 2
 4
 1

julia> issorted(v)
false

原来的数组没有变。 sort() 是一个不修改输入的函数,它返回输入的一个_副本_。

在很多场景下,这是很好的设计:我们得到一个纯函数,它的行为很容易推断,即使在并行、分布式的代码中也是如此。

另一方面,假设有一个包含 10 亿个字符串的数组,我们想对它排序。

排序很可能很慢。 此外,我们还需要复制全部内容(很慢!),并分配足够的内存(好几 GB!)来存放这份副本。

比较一下这段非常相似的代码:

# v is as previously

julia> issorted(v)
false

julia> sort!(v)
4-element Vector{Int64}:
 1
 2
 3
 4

julia> v
4-element Vector{Int64}:
 1
 2
 3
 4

julia> issorted(v)
true

现在,输入的 Vector 会在原地排序,不需要复制,但原来的顺序也会永久丢失。

这样做可能很高效,也可能埋下 bug、带来意料之外的副作用,还可能让阅读你代码的人感到困惑。 请谨慎选择!

sort!() 是一个“修改输入的函数”。按照(非常强)的约定,任何会修改输入的函数都要在名字后面加上 !,用来提醒程序员。

排序索引数组

如果要排序的元素是很大的对象,那么用它们的位置下标来处理可能更方便。

julia> langs = ["Julia", "C++", "Python", "Elixir"]
4-element Vector{String}:
 "Julia"
 "C++"
 "Python"
 "Elixir"

julia> inxs = sortperm(langs)  # => Integers
4-element Vector{Int64}:
 2
 4
 1
 3

julia> langs[inxs]  # sort the entire vector
4-element Vector{String}:
 "C++"
 "Elixir"
 "Julia"
 "Python"

julia> langs[inxs[1:2]]  # slice the indices
2-element Vector{String}:
 "C++"
 "Elixir"

因为 langs 是一个 Vector,所以每个下标都是 1:length(langs) 范围内的一个整数。 sortperm() 的输出就是这些整数的一个排列,对应着对输入数组排序的结果。

说明

你受聘成为一家为乌龟生产被子的公司的助理生产工程师。

订单会在一天中陆续送来,内容包括客户姓名或编号以及订购数量。 这些订单会在早上、当天的生产开始之前处理完毕。

只要当天的订单全部完成,你就可以拿全额工资回家,所以你有动力尽可能缩短工作时间。 由于每个订单都由单台机器处理,先安排最大的订单可以把启动一项任务的额外开销转嫁到较小的订单上,从而缩短总的生产时间。 因此,你决定提供一些排序函数来帮助加快排程,这样你就能回家给你的乌龟织毛衣了。

1. 对数量排序!

如前所述,数量数组需要按从大到小排序。 由于这些数组可能很大,应当原地排序。 为了正确地跟踪所有内容,你还需要能够对客户数组排序,使其与数量一一对应,因此需要数量数组的排序置换。

编写一个函数 sortquantity!(qty),它接收一个由Integer数量组成的Vector。 该函数会原地按降序对输入的qty排序,并返回这一顺序的排序置换。

julia> qty = [8, 9, 5, 6]
4-element Vector{Int64}:
 8
 9
 5
 6

julia> sortquantity!(qty)
4-element Vector{Int64}:
 2
 1
 4
 3

julia> qty # original vector is sorted in-place
4-element Vector{Int64}:
 9
 8
 6
 5

2. 对客户排序

如上所述,你需要按照确定的顺序跟踪客户。

编写一个函数 sortcustomer(cust, srtperm),它接收一个客户Vector(cust)和一个排序置换Vector(srtperm)。 该函数返回一个新数组,其中的客户按照排序置换指定的顺序排列。

julia> cust = ["a", "b", "c"]
3-element Vector{String}:
 "a"
 "b"
 "c"

julia> sortcustomer(cust, [2, 3, 1])
3-element Vector{String}:
 "b"
 "c"
 "a"

julia> cust # original vector remains unsorted
3-element Vector{String}:
 "a"
 "b"
 "c"

3. 生产计划!

现在你可以把它们组合起来,帮助那些乌龟保持温暖了! 你现在可以正确地排序数量和客户了,但为了发货,你还需要能够恢复原来的顺序。 这可以通过逆排序置换来实现。

编写一个函数 production_schedule!(cust, qty),它接收一个客户Vector和一个数量Vector。 该函数会原地按降序对数量排序。 它返回一个新的客户Vector,其中的客户与数量顺序相同,以及一个逆排序置换,可用于把排序后的向量恢复为原来的顺序。

julia> cust, qty = ["a", "b", "c"], [2, 3, 1]
(["a", "b", "c"], [2, 3, 1])

julia> orderedcust, invperm = production_schedule!(cust, qty)
(["b", "a", "c"], [2, 1, 3])

julia> qty # has been sorted in-place
3-element Vector{Int64}:
 3
 2
 1

julia> orderedcust[invperm], qty[invperm] # inverse permutation gives original ordering
(["a", "b", "c"], [2, 3, 1])
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Julia Exercism

准备好开始 排序考验 了吗?

注册 Exercism,借助 35 个概念128 个练习 和真人导师指导,学习并掌握 Julia,全部免费。