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

Python用range创建数值列表的实践

2021-05-084.6k 阅读

Python中range函数基础

在Python编程世界里,range函数是一个极为有用的工具,它常被用于创建数值序列。range函数在Python 2和Python 3中有一些差异。在Python 2中,range返回的是一个列表,而在Python 3中,range返回的是一个可迭代的range对象,这一变化旨在提高内存使用效率,尤其是在处理大型数值序列时。

range函数的基本语法

range函数最常见的语法形式有三种:

  1. range(stop):生成从0开始到stop - 1的整数序列。例如:
nums = range(5)
for num in nums:
    print(num)

上述代码执行后,会依次输出0、1、2、3、4。这里range(5)生成了一个包含0到4这5个整数的序列。

  1. range(start, stop):生成从start开始到stop - 1的整数序列。例如:
nums = range(2, 7)
for num in nums:
    print(num)

此代码会输出2、3、4、5、6。序列从2开始,到6结束,因为stop值是7,实际生成的序列不包含7。

  1. range(start, stop, step):生成从start开始,每次增加step,直到stop - 1的整数序列。step表示步长,默认值为1。例如:
nums = range(1, 10, 2)
for num in nums:
    print(num)

这里输出1、3、5、7、9。序列从1开始,每次增加2,直到小于10。

深入理解range对象

在Python 3中,range返回的range对象是可迭代的,但它并不是一个真正的列表。这意味着它不会在内存中立即创建所有的数值,而是根据需要生成数值。例如,如果你想创建一个从0到1000000的序列,如果使用列表来存储,会占用大量内存:

# 不推荐,占用大量内存
large_list = list(range(1000000))

而使用range对象则不会有这样的问题:

large_range = range(1000000)

range对象实现了__len____getitem__方法,因此它可以像序列一样被使用。例如,可以通过索引获取range对象中的元素:

nums = range(5)
print(nums[2])

这会输出2,说明range对象可以像列表一样通过索引访问元素,尽管它不是真正的列表。

使用range创建数值列表的实践场景

循环计数

在Python中,range函数最常见的用途之一就是在for循环中进行计数。例如,当你需要执行某个操作特定次数时:

for i in range(10):
    print(f"这是第{i + 1}次循环")

上述代码中,range(10)生成了0到9的序列,循环会执行10次,每次循环i的值会从0递增到9,通过i + 1来表示实际的循环次数。

在更复杂的场景中,比如嵌套循环,range同样发挥着重要作用。假设你要打印一个乘法表:

for i in range(1, 10):
    for j in range(1, 10):
        print(f"{i} * {j} = {i * j}", end="\t")
    print()

这里外层循环range(1, 10)控制行数,内层循环range(1, 10)控制列数,从而生成完整的乘法表。

生成特定规律的列表

通过range函数结合列表推导式,可以轻松生成具有特定规律的列表。例如,生成一个包含1到10的平方的列表:

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

上述代码中,列表推导式[num ** 2 for num in range(1, 11)]遍历range(1, 11)生成的序列,对每个数求平方并组成新的列表,最终输出[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

再比如,生成一个包含1到20之间所有偶数的列表:

evens = [num for num in range(2, 21, 2)]
print(evens)

这里range(2, 21, 2)生成了从2开始,步长为2,到20结束(不包含21)的偶数序列,列表推导式将这些偶数组成列表,输出[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

与其他函数结合使用

range函数常常与其他函数配合使用,以实现更强大的功能。例如,enumerate函数可以同时获取序列元素和其索引,而range可以帮助生成索引序列。假设你有一个字符串列表,想要打印每个字符串及其索引:

words = ["apple", "banana", "cherry"]
for i in range(len(words)):
    print(f"索引 {i} 的单词是 {words[i]}")

也可以使用enumerate函数达到同样效果:

words = ["apple", "banana", "cherry"]
for i, word in enumerate(words):
    print(f"索引 {i} 的单词是 {word}")

但在某些情况下,使用range结合索引访问可能更符合特定的编程逻辑。

另外,zip函数用于将多个序列中的元素配对,range也能在其中发挥作用。比如,你有两个列表,想要将它们按索引配对:

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

同样可以使用zip函数:

list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
for pair in zip(list1, list2):
    print(f"{pair[0]} 和 {pair[1]} 配对")

通过range和其他函数的结合,能在不同场景下灵活处理数据。

用range创建数值列表的性能考量

内存占用

如前文所述,在Python 3中,range返回的是一个可迭代对象,而不是列表,这在处理大规模数值序列时能显著节省内存。例如,要创建一个包含1000万个整数的序列,如果使用列表:

import sys
big_list = list(range(10000000))
print(sys.getsizeof(big_list))

这会占用大量内存,打印出的内存大小会非常可观。而使用range对象:

import sys
big_range = range(10000000)
print(sys.getsizeof(big_range))

range对象占用的内存则相对极小,因为它不会一次性生成所有的数值,而是按需生成。

时间效率

虽然range对象在内存使用上有优势,但在某些情况下,将range对象转换为列表可能会影响时间效率。例如,当你需要多次遍历一个数值序列时,使用range对象每次遍历都按需生成数值,而将其转换为列表后,第一次遍历速度可能会快一些,但转换过程本身会消耗时间。

假设我们要对一个数值序列进行多次求和操作,使用range对象:

import time
start_time = time.time()
nums = range(1000000)
for _ in range(10):
    total = sum(nums)
end_time = time.time()
print(f"使用range对象的时间: {end_time - start_time} 秒")

而如果先将range对象转换为列表:

import time
start_time = time.time()
nums_list = list(range(1000000))
for _ in range(10):
    total = sum(nums_list)
end_time = time.time()
print(f"使用列表的时间: {end_time - start_time} 秒")

在这个简单测试中,将range转换为列表后,第一次求和可能会快一些,因为列表已经在内存中生成,但总体来看,转换过程消耗的时间可能使得使用列表的总时间更长,尤其是当数值序列非常大且遍历次数较多时。

用range创建数值列表的常见问题与解决方法

索引越界问题

在使用range结合索引访问列表等序列时,很容易出现索引越界问题。例如:

my_list = [10, 20, 30]
for i in range(4):
    print(my_list[i])

上述代码会引发IndexError错误,因为range(4)生成的索引序列包含0、1、2、3,而my_list只有3个元素,索引3已经超出了列表范围。解决方法是确保range生成的索引范围与序列长度匹配,比如:

my_list = [10, 20, 30]
for i in range(len(my_list)):
    print(my_list[i])

这样通过len(my_list)来动态获取列表长度,就不会出现索引越界问题。

步长为负数的情况

range函数的步长为负数时,需要注意startstop的取值关系。例如,要生成从10到1的递减序列:

nums = range(10, 0, -1)
for num in nums:
    print(num)

这里start为10,stop为0(实际生成的序列不包含0),步长为 -1,这样就能正确生成递减序列。如果start小于stop且步长为负数,会生成一个空的range对象:

nums = range(5, 10, -1)
for num in nums:
    print(num)

上述代码不会输出任何内容,因为start(5)小于stop(10)且步长为 -1,不符合递减序列的逻辑。在使用步长为负数的range时,要确保start大于stop

与其他可迭代对象的兼容性问题

在一些情况下,range对象可能与其他需要特定类型可迭代对象的函数或操作不兼容。例如,某些第三方库函数可能期望接收一个列表而不是range对象。这时可以将range对象转换为列表:

nums = range(5)
# 假设某个函数需要列表作为参数
def some_function(lst):
    return sum(lst)
result = some_function(list(nums))
print(result)

但要注意转换列表可能带来的内存和性能开销,尽量在必要时才进行转换。

高级应用:使用range生成复杂数值序列

生成等差数列

等差数列是指从第二项起,每一项与它的前一项的差等于同一个常数的一种数列。通过range函数可以很方便地生成等差数列。例如,要生成首项为3,公差为4,包含10项的等差数列:

start = 3
step = 4
num_terms = 10
end = start + step * (num_terms - 1)
arithmetic_sequence = range(start, end + 1, step)
for term in arithmetic_sequence:
    print(term)

这里通过计算出数列的末项end,然后使用range(start, end + 1, step)生成等差数列。

生成等比数列

等比数列是指从第二项起,每一项与它的前一项的比值等于同一个常数的数列。虽然range函数本身不能直接生成等比数列,但结合列表推导式可以实现。例如,要生成首项为2,公比为3,包含5项的等比数列:

start = 2
ratio = 3
num_terms = 5
geometric_sequence = [start * ratio ** i for i in range(num_terms)]
print(geometric_sequence)

这里通过列表推导式,利用range(num_terms)生成索引,然后根据等比数列的通项公式a * r ** n(其中a为首项,r为公比,n为项数)生成等比数列。

生成螺旋矩阵索引序列

在图形学、算法等领域,有时需要生成螺旋矩阵的索引序列。通过巧妙使用range函数可以实现这一目标。例如,生成一个5x5的螺旋矩阵索引序列:

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

上述代码中,通过多层for循环结合range函数来控制螺旋矩阵的填充过程,range函数在不同循环中控制索引的变化,从而生成螺旋矩阵的索引序列并填充矩阵。

通过对range函数的深入理解和灵活运用,在Python编程中能够高效地生成各种数值序列,满足不同场景下的需求,无论是简单的循环计数,还是复杂的算法实现。