轨道
/
Julia
Julia
/
练习
/
观鸟者
观鸟者

观鸟者

学习练习

简介

在Vectors概念中,我们说过“数组是 Julia 语言的核心”,而向量是一维的。

鉴于此,我们完全可以期待这门语言提供许多灵活而强大的方式,让我们对向量做各种事情,不管这具体意味着什么。

接受向量输入的函数

一些非常简单的函数接受向量输入,返回标量输出。

v = [2, 3, 4]
length(v)  # => 3
sum(v)  # => 9

算术运算

假设你有一个数值向量,想从每个值中减去 0.5。

julia> v = [1.2, 1.5, 1.7]
3-element Vector{Float64}:
 1.2
 1.5
 1.7

julia> v - 0.5
ERROR: MethodError: no method matching -(::Vector{Float64}, ::Float64)

这样会失败,那减去另一个向量呢?

julia> v - [0.5, 0.5, 0.5]
3-element Vector{Float64}:
 0.7
 1.0
 1.2

成功了,但随着向量越来越长,这种做法相当繁琐,也很占内存。

幸运的是,Julia 有一个“魔法”点号,可以非常简单地解决这个问题:只需要v .- 0.5就够了。

下一节会解释其中的原因。

广播

也就是说,v - 0.5会失败,而v .- 0.5能成功,我们需要弄明白这个点号在做什么。

它做了两件事,二者结合起来才得到想要的结果。

1. 逐元素应用

首先,在任何中缀运算符前加点号,意味着“把这个运算分别应用到每个元素上”。

同样,在函数名后面加点号,会把它“向量化”,即使这个函数原本是为标量输入而写的。

julia> sqrt.([1, 4, 9])
3-element Vector{Float64}:
 1.0
 2.0
 3.0

2. 单例扩展

在前面的例子中我们看到,可以相减两个长度相等的向量,不过请理解,.-比-更安全,因为它清楚地表明了逐元素的意图。

julia> v .- [0.5, 0.5, 0.5]
3-element Vector{Float64}:
 0.7
 1.0
 1.2

长度不相等的向量呢?

julia> v .- [0.5, 0.5]
ERROR: DimensionMismatch: arrays could not be broadcast to a common size

julia> v .- [0.5,]
3-element Vector{Float64}:
 0.7
 1.0
 1.2

一般来说,长度不相等就是一个错误,除非其中一个的长度为 1(严格来说,是“单例”维度)。

像[0.5,]或者直接写0.5这样的单例,会自动通过重复扩展到所需的长度。这正是broadcasting的核心。

下标

如何通过下标来选取向量元素,在之前的概念中已经讨论过。

a = collect('A':'Z')  # => 26-element Vector{Char}

# index with an integer
a[2]  # => 'B'

# index with a range
 a[12:2:18]  # => ['L', 'N', 'P, 'R']
 
 # index with another vector
 a[ [1, 3, 5] ]  # => ['A', 'C', 'E']

逻辑下标

也可以选取满足某个逻辑表达式(严格来说,是“谓词”)的元素。这通常需要用广播。

julia> a[a .< 'D']
3-element Vector{Char}:
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
 'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)

表达式越复杂,点号就越多(不过它们很小,也很好输入)。

julia> a[a .< 'D' .|| a .> 'W']
6-element Vector{Char}:
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
 'C': ASCII/Unicode U+0043 (category Lu: Letter, uppercase)
 'X': ASCII/Unicode U+0058 (category Lu: Letter, uppercase)
 'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
 'Z': ASCII/Unicode U+005A (category Lu: Letter, uppercase)

再提醒一下,“向量”实际上可以是任何合适的有序可迭代对象,比如一个范围:

julia> n = 3:10
3:10

julia> n[isodd.(n)]
4-element Vector{Int64}:
 3
 5
 7
 9

说明

你是一位热衷于观鸟的人,会记录过去 7 天里有多少只鸟来过你的花园。

你有 6 个任务,它们都与来过你花园的鸟的数量有关。

1. 查看今天有多少只鸟来访

实现 today() 函数,返回今天有多少只鸟来过你的花园。 鸟的数量按天排列,第一个元素是最早那天的数量,最后一个元素是今天的数量。

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> today(birds_per_day)
1

2. 增加今天的计数

实现 increment_todays_count() 函数,把今天的计数加一:

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> increment_todays_count(birds_per_day)
[2, 5, 0, 7, 4, 2]

3. 检查是否存在没有鸟来访的一天

实现 has_day_without_birds() 函数,如果存在某一天没有鸟来过花园,就返回 true,否则返回 false:

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> has_day_without_birds(birds_per_day)
true

4. 计算前若干天来访鸟的数量

实现 count_for_first_days() 函数,返回从这周开始以来来过你花园的鸟的数量,但只统计从这周开始算起的指定天数。

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> count_for_first_days(birds_per_day, 4)
14

5. 计算繁忙的天数

有些天比其他天更忙。 繁忙的一天指的是至少有 5 只鸟来过你的花园。 实现 busy_days() 函数,返回繁忙的天数:

julia> birds_per_day = [2, 5, 0, 7, 4, 1]
julia> busy_days(birds_per_day)
2

6. 按一周中的每天计算平均值

你决定扩展记录,保存多周的数据。 每种情况下,计数都按一周中的天排列,第一个条目是周一,最后一个是周日。

实现 average_per_day() 函数,返回 2 周的平均值。

julia> week1 = [7, 2, 9, 1, 3, 0, 10]
julia> week2 = [2, 6, 4, 1, 3, 8, 9]
julia> average_per_day(week1, week2)
[4.5, 4.0, 6.5, 1.0, 4.0, 3.0, 9.5]
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Julia Exercism

准备好开始 观鸟者 了吗?

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