ZetCode

F# 范围

最后修改日期:2025 年 5 月 17 日

在本文中,我们将探讨 F# 中的范围,以及它们如何简化有序序列的生成。范围提供了一种优雅的方式来创建值列表,而无需手动重复,这在循环和集合操作中特别有用。

F# 中的范围定义了在指定起始点和结束点之间的值序列。它们可用于整数、字符和其他可增量类型。这种语法允许编写简洁易读的代码,提高处理序列时的效率。F# 提供了包含和排除的范围运算符,以及可选的步长值来定义自定义增量。

包含范围在两个点之间生成值,包括最后一个值。例如,[1 .. 5] 生成 [1; 2; 3; 4; 5],而 ['a' .. 'e'] 生成 ['a'; 'b'; 'c'; 'd'; 'e']。这种方法确保了可预测的迭代,而无需显式赋值。要指定自定义步长值,可以引入第三个参数,例如 [2 .. 2 .. 10],它生成 [2; 4; 6; 8; 10]

排除范围会省略序列中的最后一个值。使用 seq { 1 .. 4 } 时,生成的序列包含 [1; 2; 3],不包含 4。这种行为在确保维护特定边界条件时很有用。

范围与循环自然集成,允许对预定义值进行结构化迭代。例如,使用 for 循环,for i in 1 .. 5 do printfn "Iteration %d" i 会打印从 15 的数字,减少了手动管理循环计数器的需求。

除了迭代,范围还可以通过转换和过滤来增强集合处理。列表推导式,例如 [ for i in 1 .. 5 -> i * i ],会对每个元素应用一个转换,生成 [1; 4; 9; 16; 25]。这种方法保持了代码的简洁性,同时利用了函数式编程原理。

由于 F# 范围在使用序列(seq 表达式)时是惰性生成的,因此它们可以高效地处理大型数据集。与直接具体化列表不同,惰性求值确保值仅在需要时计算,从而优化了性能。在处理大量序列时,Seq.initSeq.unfold 等替代方法可能提供进一步的效率提升。

F# 基本整数范围

最简单的范围形式在两个值之间生成整数。

basic.fsx
let numbers = [1..5]
printfn "Inclusive range: %A" numbers

let exclusive = [1..4..15]
printfn "With step 4: %A" exclusive

[1..5] |> List.iter (printfn "Number: %d")

我们创建了三个不同的整数范围并演示了它们的用法。

let numbers = [1..5]

创建一个从 1 到 5 的包含范围(1、2、3、4、5)。

let exclusive = [1..4..15]

创建一个从 1 到 15、步长为 4 的范围(1、5、9、13)。

λ dotnet fsi basic.fsx
Inclusive range: [1; 2; 3; 4; 5]
With step 4: [1; 5; 9; 13]
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

F# 降序范围

范围可以通过使用负步长来倒计数。

descending.fsx
let countdown = [10..-1..5]
printfn "Countdown: %A" countdown

let bigStepDown = [20..-3..5]
printfn "Big steps down: %A" bigStepDown

[5..-1..1] |> List.iter (printfn "%d")

该示例演示了不同步长值的降序范围。

let countdown = [10..-1..5]

创建一个从 10 到 5 的降序范围(10、9、8、7、6、5)。

λ dotnet fsi descending.fsx
Countdown: [10; 9; 8; 7; 6; 5]
Big steps down: [20; 17; 14; 11; 8; 5]
5
4
3
2
1

F# 字符范围

范围不仅适用于数字,也适用于字符。

characters.fsx
let alphabet = ['a'..'z']
printfn "Alphabet: %A" (alphabet |> List.take 5)

let vowels = ['a'..'e'..'z']
printfn "Every 5th letter: %A" vowels

['A'..'Z'] |> List.iter (printf "%c ")
printfn ""

展示了如何创建字符范围。

let alphabet = ['a'..'z']

生成从 'a' 到 'z' 的所有小写字母。

λ dotnet fsi characters.fsx
Alphabet: ['a'; 'b'; 'c'; 'd'; 'e']
Every 5th letter: ['a'; 'f'; 'k'; 'p'; 'u'; 'z']
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 

F# 范围与序列

范围可与 F# 序列一起使用以实现惰性求值。

sequences.fsx
let bigRange = seq {1..1000000}
printfn "First 5: %A" (bigRange |> Seq.take 5)

let infinite = seq {0..}
printfn "Infinite first 10: %A" (infinite |> Seq.take 10)

seq {10..-1..1} |> Seq.iter (printfn "Countdown: %d")

演示了如何使用范围和序列以实现内存效率。

let bigRange = seq {1..1000000}

创建一个不会立即分配所有元素的序列范围。

let infinite = seq {0..}

创建一个从 0 开始的无限序列(请小心使用!)。

λ dotnet fsi sequences.fsx
First 5: seq [1; 2; 3; 4; ...]
Infinite first 10: seq [0; 1; 2; 3; ...]
Countdown: 10
Countdown: 9
Countdown: 8
Countdown: 7
Countdown: 6
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1

F# for 循环中的范围

范围通常与 for 循环一起使用。

loops.fsx
for i in 1..5 do
    printfn "Loop iteration %d" i

printfn "---"

for i in 10..-2..0 do
    printfn "Countdown: %d" i

printfn "---"

for c in 'a'..'f' do
    printf "%c " c
printfn ""

该示例演示了在 for 循环中使用范围。

for i in 1..5 do

标准的 for 循环,使用从 1 到 5 的包含范围。

λ dotnet fsi loops.fsx
Loop iteration 1
Loop iteration 2
Loop iteration 3
Loop iteration 4
Loop iteration 5
---
Countdown: 10
Countdown: 8
Countdown: 6
Countdown: 4
Countdown: 2
Countdown: 0
---
a b c d e f 

F# 浮点数范围

浮点数范围需要显式指定步长值。

floating.fsx
let floatRange = [0.0..0.5..2.0]
printfn "Floating range: %A" floatRange

let descendingFloats = [5.0.. -1.0 ..0.0]
printfn "Descending floats: %A" descendingFloats

[0.0..0.1..1.0] |> List.iter (printfn "%.1f")

展示了如何创建浮点数范围。

let floatRange = [0.0..0.5..2.0]

浮点数范围必须指定步长值。

λ dotnet fsi floating.fsx
Floating range: [0.0; 0.5; 1.0; 1.5; 2.0]
Descending floats: [5.0; 4.0; 3.0; 2.0; 1.0; 0.0]
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0

F# DateTime 自定义范围

范围不是为 DateTime 内置的,但你可以使用序列表达式和步长值创建一个自定义范围。这对于生成一系列日期非常有用。

date_time.fsx
open System

let dateRange (start: DateTime) (finish: DateTime) (step: TimeSpan) =
    seq {
        let mutable current = start
        while current <= finish do
            yield current
            current <- current + step
    }

let startDate = DateTime(2024, 5, 1)
let endDate = DateTime(2024, 5, 5)
let step = TimeSpan(1, 0, 0, 0) // 1 day

for d in dateRange startDate endDate step do
    printfn "%A" d

此示例定义了一个 dateRange 函数,该函数生成从开始日期到结束日期的日期,并按指定的间隔步进。在此,它打印了 2024 年 5 月 1 日到 2024 年 5 月 5 日的所有日期。

在本文中,我们探讨了 F# 中范围的多功能性。它们为生成值序列提供了一种简洁的语法,在与循环和集合操作结合使用时尤其有用。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我自 2007 年起就开始撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。