MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python range函数的高级用法

2022-06-095.2k 阅读

一、range 函数基础回顾

在深入探讨 Python range 函数的高级用法之前,让我们先简要回顾一下它的基本概念和常规用法。

range 函数是 Python 内置函数,主要用于生成一个整数序列。其最常见的形式有三种:

  1. range(stop):生成从 0 开始到 stop 结束(不包含 stop)的整数序列。例如:
for i in range(5):
    print(i)

上述代码会输出 0 1 2 3 4。这里 range(5) 生成了一个包含 0 到 4 的整数序列,循环会依次取出这些值并赋值给 i。 2. range(start, stop):生成从 start 开始(包含 start)到 stop 结束(不包含 stop)的整数序列。例如:

for i in range(2, 7):
    print(i)

这段代码会输出 2 3 4 5 6range(2, 7) 生成的序列从 2 开始,到 6 结束。 3. range(start, stop, step):生成从 start 开始,每次增加 step,直到达到或超过 stop(但不包含 stop)的整数序列。例如:

for i in range(1, 10, 2):
    print(i)

该代码会输出 1 3 5 7 9。这里 range(1, 10, 2) 从 1 开始,每次增加 2,直到不超过 9。

二、利用 range 生成特定模式的序列

(一)生成等差数列

range 函数天然就可以生成等差数列。等差数列是指从第二项起,每一项与它的前一项的差等于同一个常数的一种数列。这个常数就是 range 函数中的 step 参数。

比如我们要生成一个首项为 3,公差为 4,包含 5 项的等差数列:

a1 = 3  # 首项
d = 4   # 公差
n = 5   # 项数
last_term = a1 + (n - 1) * d
for i in range(a1, last_term + 1, d):
    print(i)

上述代码通过计算出末项 last_term,然后使用 range 函数生成了这个等差数列并输出。

(二)生成倒序序列

通过设置 step 为负数,可以使用 range 生成倒序的整数序列。例如,要生成从 10 到 1 的倒序序列:

for i in range(10, 0, -1):
    print(i)

这段代码会输出 10 9 8 7 6 5 4 3 2 1。这里 range(10, 0, -1) 从 10 开始,每次减少 1,直到不小于 1。

(三)生成间隔重复序列

有时候我们需要生成一些间隔重复的序列。例如,生成 [1, 1, 2, 2, 3, 3, 4, 4] 这样的序列。我们可以利用 range 结合一些简单的运算来实现:

result = []
for i in range(1, 5):
    result.extend([i] * 2)
print(result)

这里通过 range(1, 5) 生成数字 1 到 4,然后将每个数字重复两次添加到 result 列表中。

三、在循环中的高级应用

(一)循环索引与元素同时获取

在使用 range 结合列表或其他可迭代对象进行循环时,我们常常需要同时获取元素和其索引。虽然可以通过 enumerate 函数优雅地实现,但也可以利用 range 来达成。

假设有一个列表 my_list = ['a', 'b', 'c', 'd'],我们可以这样获取索引和元素:

my_list = ['a', 'b', 'c', 'd']
for i in range(len(my_list)):
    print(f"Index: {i}, Element: {my_list[i]}")

这种方式通过 range(len(my_list)) 生成与列表索引对应的整数序列,从而在循环中获取每个元素及其索引。

(二)嵌套循环中的步长控制

在嵌套循环中,合理控制 range 的步长可以实现一些特殊的矩阵或图案生成。

例如,我们要生成一个如下的等腰直角三角形图案:

*
**
***
****

可以使用如下代码:

for i in range(1, 5):
    for j in range(i):
        print('*', end='')
    print()

这里外层循环 range(1, 5) 控制行数,内层循环 range(i) 控制每行的 * 数量,随着外层循环的 i 增加,内层循环生成的 * 数量也相应增加。

再比如,我们要生成一个螺旋矩阵的部分代码,假设矩阵大小为 n x n,可以通过巧妙控制 range 的起始、结束和步长来实现螺旋填充:

n = 5
matrix = [[0] * n for _ in range(n)]
num = 1
for layer in range(n // 2):
    for i in range(layer, n - layer - 1):
        matrix[layer][i] = num
        num += 1
    for i in range(layer, n - layer - 1):
        matrix[i][n - layer - 1] = num
        num += 1
    for i in range(n - layer - 1, layer, -1):
        matrix[n - layer - 1][i] = num
        num += 1
    for i in range(n - layer - 1, layer, -1):
        matrix[i][layer] = num
        num += 1
if n % 2 == 1:
    matrix[n // 2][n // 2] = num
for row in matrix:
    print(row)

在这段代码中,通过多层 range 控制,在不同方向上进行螺旋式填充矩阵元素。

(三)循环中的跳跃式迭代

有时候我们需要在循环中跳过某些元素,这可以通过调整 rangestep 来实现。

比如,我们有一个列表 nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],要跳过偶数位置的元素(索引从 0 开始),只打印奇数位置的元素:

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in range(1, len(nums), 2):
    print(nums[i])

这里 range(1, len(nums), 2) 从索引 1 开始,每次跳跃 2 个位置,从而实现跳过偶数位置元素的效果。

四、与其他函数和数据结构的结合使用

(一)与列表推导式结合

列表推导式是 Python 中创建列表的一种简洁方式,range 函数经常与之配合使用。

例如,要创建一个包含 1 到 10 的平方的列表,可以这样写:

squares = [i ** 2 for i in range(1, 11)]
print(squares)

这里 range(1, 11) 生成 1 到 10 的整数序列,列表推导式对每个整数求平方并生成新的列表。

我们还可以在列表推导式中添加条件。比如,只生成 1 到 10 中偶数的平方:

even_squares = [i ** 2 for i in range(1, 11) if i % 2 == 0]
print(even_squares)

在这个例子中,if i % 2 == 0 条件筛选出 range 生成序列中的偶数,然后求其平方生成新列表。

(二)与 map 函数结合

map 函数会根据提供的函数对指定序列做映射。range 生成的序列可以作为 map 函数的输入。

假设我们有一个函数 square 用于计算平方:

def square(x):
    return x * x
result = list(map(square, range(1, 6)))
print(result)

这里 range(1, 6) 生成的序列作为 map 函数的第二个参数,map 函数将 square 函数应用到 range 生成的每个元素上,最后通过 list 转换为列表输出。

(三)与 zip 函数结合

zip 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象。

例如,我们有两个列表 list1 = [1, 2, 3]list2 = ['a', 'b', 'c'],要将它们按顺序配对,同时利用 range 生成索引:

list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
for i, (a, b) in zip(range(len(list1)), zip(list1, list2)):
    print(f"Index: {i}, Pair: ({a}, {b})")

这里外层的 zip 结合 range(len(list1)) 生成索引,内层的 ziplist1list2 配对,从而在循环中同时获取索引和配对元素。

五、在生成器中的应用

(一)构建自定义生成器

我们可以基于 range 构建自定义生成器。生成器是一种特殊的迭代器,它不会一次性生成所有数据,而是按需生成,这在处理大数据量时非常高效。

比如,我们要构建一个生成从某个数开始的等差数列的生成器:

def arithmetic_progression_generator(a1, d, n):
    last_term = a1 + (n - 1) * d
    for i in range(a1, last_term + 1, d):
        yield i


gen = arithmetic_progression_generator(2, 3, 5)
for num in gen:
    print(num)

在这个例子中,arithmetic_progression_generator 函数是一个生成器函数,它利用 range 生成等差数列,yield 关键字使函数成为生成器,每次调用 next(gen)(在 for 循环中隐式调用)时生成下一个数列元素。

(二)生成器表达式中的 range

生成器表达式与列表推导式类似,但它返回的是一个生成器对象,而不是列表。range 在生成器表达式中也经常使用。

例如,要生成 1 到 10 中奇数的平方的生成器对象:

square_odd_gen = (i ** 2 for i in range(1, 11) if i % 2 == 1)
for square in square_odd_gen:
    print(square)

这里 (i ** 2 for i in range(1, 11) if i % 2 == 1) 是一个生成器表达式,它使用 range 生成 1 到 10 的序列,筛选出奇数并求其平方,返回一个生成器对象,通过 for 循环逐个获取生成器中的元素。

六、在数学和算法中的应用

(一)数学序列求和

range 函数在计算一些数学序列的和时非常有用。比如计算 1 到 100 的整数和,可以使用如下代码:

total = sum(range(1, 101))
print(total)

这里 range(1, 101) 生成 1 到 100 的整数序列,sum 函数对这个序列进行求和。

再比如,计算 1 到 100 中所有偶数的和:

even_total = sum(i for i in range(1, 101) if i % 2 == 0)
print(even_total)

这段代码通过生成器表达式结合 range 筛选出偶数并求和。

(二)质数判断算法中的应用

在判断一个数是否为质数时,可以利用 range 进行优化。质数是指在大于 1 的自然数中,除了 1 和它自身外,不能被其他自然数整除的数。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True


num = 17
print(is_prime(num))

在这个 is_prime 函数中,range(2, int(n ** 0.5) + 1) 生成从 2 到 n 的平方根之间的整数序列,通过检查 n 是否能被这些数整除来判断 n 是否为质数。这样可以减少不必要的计算,提高算法效率。

(三)组合与排列算法中的应用

在组合和排列算法中,range 也扮演着重要角色。

例如,计算从 n 个不同元素中取出 k 个元素的组合数(不考虑顺序),可以使用如下代码:

def factorial(n):
    return 1 if n == 0 else n * factorial(n - 1)


def combination(n, k):
    return factorial(n) // (factorial(k) * factorial(n - k))


n = 5
k = 3
print(combination(n, k))

在计算阶乘 factorial 函数中,虽然没有直接使用 range,但在一些基于迭代的阶乘实现中会用到。而对于排列算法,例如生成 n 个元素的全排列,可以通过多层嵌套的 range 循环结合递归或其他方法来实现。这里假设有一个列表 elements = [1, 2, 3],要生成其全排列:

def permute(elements):
    if len(elements) == 0:
        return []
    if len(elements) == 1:
        return [elements]
    perms = []
    for i in range(len(elements)):
        m = elements[i]
        rem_list = elements[:i] + elements[i + 1:]
        for p in permute(rem_list):
            perms.append([m] + p)
    return perms


elements = [1, 2, 3]
print(permute(elements))

在这个 permute 函数中,range(len(elements)) 用于遍历每个元素,将其作为排列的起始元素,然后递归地生成剩余元素的排列,最终组合成所有的全排列。

七、性能优化与注意事项

(一)性能优化

  1. 减少不必要的计算:在使用 range 生成序列时,尽量避免在 range 的参数中进行复杂的计算。例如,如果 startstopstep 的值在循环过程中不会改变,提前计算好并赋值给变量,然后在 range 中使用这些变量。
# 不好的做法
for i in range(expensive_function1(), expensive_function2()):
    pass

# 好的做法
start = expensive_function1()
stop = expensive_function2()
for i in range(start, stop):
    pass
  1. 使用生成器代替列表:如果只是需要遍历一个序列,而不需要将其全部存储在内存中,使用基于 range 的生成器表达式或生成器函数会更高效。例如,(i for i in range(1000000))list(range(1000000)) 占用的内存少得多,因为生成器是按需生成数据,而列表会一次性生成并存储所有元素。

(二)注意事项

  1. 边界条件:使用 range 时要特别注意边界条件。例如,range(stop) 生成的序列不包含 stop 本身,range(start, stop) 同样不包含 stop。在编写循环或其他基于 range 的逻辑时,确保边界符合预期。
  2. 数据类型range 函数返回的是一个 range 对象,在 Python 3 中它是一个可迭代对象,但不是列表。如果需要列表形式,需要使用 list() 进行转换。例如,list(range(5)) 会返回 [0, 1, 2, 3, 4]。同时,range 的参数必须是整数(可以是 int 类型或能转换为 int 的类型),否则会抛出 TypeError
  3. 负步长与起始结束值关系:当使用负步长时,要注意 startstop 的值。range(start, stop, -step) 要求 start 大于 stop,否则生成的序列为空。例如,range(5, 10, -1) 生成的序列为空,而 range(10, 5, -1) 会生成 [10, 9, 8, 7, 6]

通过深入了解 range 函数的这些高级用法,我们可以在 Python 编程中更加灵活和高效地处理各种任务,无论是数据处理、算法实现还是日常的编程需求。