当前位置:

首页 > 编程开发 > Python实现动态数组:学习数组的复杂方式

Python实现动态数组:学习数组的复杂方式

Part1聊聊Python序列类型的本质在本博客中,我们来聊聊探讨Python的各种“序列”类,内置的三大常用数据结构——列表类(list)、元组类(tuple)和字符串类(str)的本质。不知道你发现没有,这些类都有一个很明显的共性,都可以用来保存多个数据元素,最主要的功能是:每个类都支持下标(索引)访问该序列的元素,比如使用语法Seq[i]​。其实上面每个类都是使用数组这种简单的数据结构表示。但是熟悉Python的读者可能知道这3种数据结构又有一些不同:比如元组和字符串是不能修改的,列表可以修改

本文目录

    Part1聊聊Python序列类型的本质

    在本博客中,我们来聊聊探讨Python的各种“序列”类,内置的三大常用数据结构——列表类(list)、元组类(tuple)和字符串类(str)的本质。

    不知道你发现没有,这些类都有一个很明显的共性,都可以用来保存多个数据元素,最主要的功能是:每个类都支持下标(索引)访问该序列的元素,比如使用语法 Seq[i]​。其实上面每个类都是使用 数组 这种简单的数据结构表示。

    但是熟悉Python的读者可能知道这3种数据结构又有一些不同:比如元组和字符串是不能修改的,列表可以修改。

    1、计算机内存中的数组结构

    计算机体系结构中,我们知道计算机主存由位信息组成,这些位通常被归类成更大的单元,这些单元则取决于精准的系统架构。一个典型的单元就是一个字节,相当于8位。

    计算机系统拥有庞大数量的存储字节,那么如何才能找到我们的信息存在哪个字节呢?答案就是大家平时熟知的 存储地址 。基于存储地址,主存中的任何字节都能被有效的访问。实际上,每个存储字节都和一个作为其地址的唯一二进制数字相关联。如下图中,每个字节均被指定了存储地址:

    Python实现动态数组:学习数组的复杂方式

    一般来说,编程语言记录标识符和其关联值所存储的地址之间的关系。比如,当我们声明标识符 x 就有可能和存储器中的某一值相关联,而标识符 y就可能和其他的值相关联。一组相关的变量能够一个接一个地存储在计算机存储器的一块连续区域内。我们将这种方式称为 数组。

    我们来看Python中的例子,一个文本字符串 HELLO 是以一列有序字符的形式存储的,假定该字符串的每个Unicode字符需要两个字节的存储空间。最下面的数字就是该字符串的索引值。

    Python实现动态数组:学习数组的复杂方式

    我们可以看到,数组可以存储多个值而无需构造具有特定索引的多个变量来指定其中的每个项目,并且几乎在所有编程语言(例如C、Java、C#、C++)中使用,但是Python更具有优势。Python在构建列表时,熟悉的读者可能知道,不需要预先定义数组或列表的大小,相反,在Python中,列表具有动态性质,我们可以不断的往列表中添加我们想要的数据元素。接下来,让我们看看Python列表的知识(已经熟悉的读者可以快速浏览或者跳过)。

    2、Python列表

    Python列表的操作

    • 创建列表的语法格式:

    [ele1, ele2, ele3, ele4, ...]

    • 创建元组的语法格式:

    (ele1, ele2, ele3, ele4, ...)

    元组比列表的内存空间利用率更高,因为元组是固定不变的,所以没有必要创建拥有剩余空间的动态数组。

    我们先在Python的IDE中创建一个列表,然后大致了解一下列表部分内置操作,我们先创建了一个名为test_list的列表,然后修改(插入或删除)元素,反转或清空列表,具体如下:

    >>> test_list = []# 创建名为test_list的空列表
    >>> test_list.append("Hello")
    >>> test_list.append("World")
    >>> test_list
    ['Hello', 'World']
    >>> test_list = ["Hello", "Array", 2019, "easy learning", "DataStructure"]# 重新给test_list赋值
    >>> len(test_list)# 求列表的长度
    5
    >>> test_list[2] = 1024# 修改列表元素
    >>> test_list
    ['Hello', 'Array', 1024, 'easy learning', 'DataStructure']
    >>>
    >>> test_list.insert(1, "I love")# 向列表中指定位置中插入一个元素
    >>> test_list
    ['Hello', 'I love', 'Array', 1024, 'easy learning', 'DataStructure']
    >>> test_list.append(2020)# 向列表末尾增加一个元素
    >>> test_list
    ['Hello', 'I love', 'Array', 1024, 'easy learning', 'DataStructure', 2020]
    >>>
    >>> test_list.pop(1)# 删除指定位置的元素
    'I love'
    >>> test_list.remove(2020)# 删除指定元素
    >>> 
    >>> test_list.index('Hello')# 查找某个元素的索引值
    0
    >>> test_list.index('hello')# 如果查找某个元素不在列表中,返回ValueError错误
    Traceback (most recent call last):
    File "", line 1, in 
    test_list.index('hello')
    ValueError: 'hello' is not in list
    >>> 
    >>> test_list.reverse()# 反转整个列表
    >>> test_list
    ['DataStructure', 'easy learning', 2019, 'Array', 'Hello']
    >>> test_list.clear()# 清空列表
    >>> test_list
    []

    我们看上面的代码,可以看到list的相关操作——增删改查,已经很强大了,还有一些内置方法这里并没有做展示,留给读者自己去发现并体验。

    Python列表的内存分配背后的基础知识

    因此,让我们通过编码实践以及内存中保存的数组的实际大小与给定大小之间的关系来查看这种额外的空间演示。

    前往Jupyter notebook进行练习。或者使用自己选择的任何编辑器或开发环境。复制下面编写的代码。

    # 导入sys模块能方便我们使用gestsizeof函数
    import sys
    
    # set n
    n = 20
    # set empty list
    list = []
    for i in range(n):
    a = len(list)
    # 调用getsizeof函数用于给出Python中存储对象的真实字节数
    b = sys.getsizeof(list)
    print('Length:{0:3d}; Size of bytes:{1:4d}'.format(a, b))
    # Increase length by one
    list.append(n)

    运行代码,可以看到如下输出:

    Python实现动态数组:学习数组的复杂方式

    现在,随着我们增加列表的长度,字节也增加了。我们分析一下,Length:1

    位置的元素填入列表时,字节数从64跳到96,增加了32个字节。因为本实验是在64位机器上运行的,这表明每个内存地址是64位(即8个字节)。增加的32个字节即为分配的用于存储4个对象引用的数组大小。当增加第2个、第3个或者第4个元素时,内存占用没有任何改变。字节数96能够提供4个对象的引用。

    96 = 64 + 8 times 4

    当Length:10

    时,字节数从一开始的64跳到192,能存下16个对象的引用,

    192 = 64 + 8 times 16

    一直到Length: 17

    后又开始跳转,所以理论上264个字节数应该可以存下25个对象

    264 = 64 + 8 times 25

    但因为我们在代码中设置n=20,然后程序就终止了。

    我们可以看到Python内置的list类足够智能,知道当需要额外的空间来分配数据时,它会为它们提供额外的大小,那么这究竟是如何被实现的呢?

    好吧,答案是动态数组。说到这里,不知道大家学Python列表的时候是不是这样想的——列表很简单嘛,就是list()类、用中括号[]括起来,然后指导书籍或文档上的各类方法append、insert、pop...在各种IDE一顿操作过后,是的我觉得我学会了。

    但其实背后的原理真的很不简单,比如我举个例子:A[-1]这个操作怎么实现?列表切片功能怎么实现?如何自己写pop()默认删除列表最右边的元素(popleft删除最左边简单)?...这些功能用起来爽,但真的自己实现太难了(我也还在学习中,大佬们请轻喷!)如果我们能学习并理解,肯定可以加强我们对数组这一结构的理解。

    3、动态数组

    什么是动态数组

    动态数组是内存的连续区域,其大小随着插入新数据而动态增长。在静态数组中,我们需要在分配时指定大小。在定义数组的时候,其实计算机已经帮我们分配好了内存来存储,实际上我们不能扩展数组,因为它的大小是固定的。比如:我们分配一个大小为10的数组,则不能插入超过10个项目。

    但是动态数组会在需要的时候自动调整其大小。这一点有点像我们使用的Python列表,可以存储任意数量的项目,而无需在分配时指定大小。

    所以实现一个动态数组的实现的关键是——如何扩展数组?当列表list1的大小已满时,而此时有新的元素要添加进列表,我们会执行一下步骤来克服其大小限制的缺点:

    1. 分配具有更大容量的新数组list2
    2. 设置list2[i] = list1[i] (i=0,1,2,...,n-1),其中n是该项目的当前编号
    3. 设置list1 = list2,也就是说,list2正在作为新的数组来引用我们的新列表。
    4. 然后,只要将新的元素插入(添加)到我们的列表list1即可。

    Python实现动态数组:学习数组的复杂方式

    接下来要思考的问题是,新数组应该多大?通常我们得做法是:新数组的大小是已满的旧数组的2倍。我们将在Python中编程实现动态数组的概念,并创建一个简单的代码,很多功能不及Python强大。

    实现动态数组的Python代码

    在Python中,我们利用ctypes的内置库来创建自己的动态数组类,因为ctypes模块提供对原始数组的支持,为了更快的对数组进行学习,所以对ctypes的知识可以查看官方文档进行学习。关于Python的公有方法与私有方法,我们在方法名称前使用双下划线**__**使其保持隐藏状态,代码如下:

    # -*- coding: utf-8 -*-
    # @Time: 2019-11-01 17:10
    # @Author: yuzhou_1su
    # @ContactMe : https://blog.csdn.net/yuzhou_1shu
    # @File: DynamicArray.py
    # @Software: PyCharm
    
    import ctypes
    
    
    class DynamicArray:
    """A dynamic array class akin to a simplified Python list."""
    
    def __init__(self):
    """Create an empty array."""
    self.n = 0 # count actual elements
    self.capacity = 1# default array capacity
    self.A = self._make_array(self.capacity)# low-level array
    
    def is_empty(self):
    """ Return True if array is empty"""
    return self.n == 0
    
    def __len__(self):
    """Return numbers of elements stored in the array."""
    return self.n
    
    def __getitem__(self, i):
    """Return element at index i."""
    if not 0 <= i < self.n:
    # Check it i index is in bounds of array
    raise ValueError('invalid index')
    return self.A[i]
    
    def append(self, obj):
    """Add object to end of the array."""
    if self.n == self.capacity:
    # Double capacity if not enough room
    self._resize(2 * self.capacity)
    self.A[self.n] = obj# Set self.n index to obj
    self.n += 1
    
    def _resize(self, c):
    """Resize internal array to capacity c."""
    B = self._make_array(c) # New bigger array
    for k in range(self.n):# Reference all existing values
    B[k] = self.A[k]
    self.A = B# Call A the new bigger array
    self.capacity = c # Reset the capacity
    
    @staticmethod
    def _make_array(c):
    """Return new array with capacity c."""
    return (c * ctypes.py_object)()
    
    def insert(self, k, value):
    """Insert value at position k."""
    if self.n == self.capacity:
    self._resize(2 * self.capacity)
    for j in range(self.n, k, -1):
    self.A[j] = self.A[j-1]
    self.A[k] = value
    self.n += 1
    
    def pop(self, index=0):
    """Remove item at index (default first)."""
    if index >= self.n or index < 0:
    raise ValueError('invalid index')
    for i in range(index, self.n-1):
    self.A[i] = self.A[i+1]
    self.A[self.n - 1] = None
    self.n -= 1
    
    def remove(self, value):
    """Remove the first occurrence of a value in the array."""
    for k in range(self.n):
    if self.A[k] == value:
    for j in range(k, self.n - 1):
    self.A[j] = self.A[j+1]
    self.A[self.n - 1] = None
    self.n -= 1
    return
    raise ValueError('value not found')
    
    def _print(self):
    """Print the array."""
    for i in range(self.n):
    print(self.A[i], end=' ')
    print()

    测试动态数组Python代码

    上面我们已经实现了一个动态数组的类,相信都很激动,接下来让我们来测试一下,看能不能成功呢?在同一个文件下,写的测试代码如下:

    def main():
    # Instantiate
    mylist = DynamicArray()
    
    # Append new element
    mylist.append(10)
    mylist.append(9)
    mylist.append(8)
    # Insert new element in given position
    mylist.insert(1, 1024)
    mylist.insert(2, 2019)
    # Check length
    print('The array length is: ', mylist.__len__())
    # Print the array
    print('Print the array:')
    mylist._print()
    # Index
    print('The element at index 1 is :', mylist[1])
    # Remove element
    print('Remove 2019 in array:')
    mylist.remove(2019)
    mylist._print()
    # Pop element in given position
    print('Pop pos 2 in array:')
    # mylist.pop()
    mylist.pop(2)
    mylist._print()
    
    
    if __name__ == '__main__':
    main()

    测试结果

    激动人心的时刻揭晓,测试结果如下。请结合测试代码和数组的结构进行理解,如果由疏漏,欢迎大家指出。

    The array length is:5
    Print the array:
    10 1024 2019 9 8 
    The element at index 1 is : 1024
    Remove 2019 in array:
    10 1024 9 8 
    Pop pos 2 in array:
    10 1024 8 

    Part2总结

    通过以上的介绍,我们知道了数组存在静态和动态类型。而在本博客中,我们着重介绍了什么是动态数组,并通过Python代码进行实现。希望你能从此以复杂的方式学会数组。总结发言,其实越是简单的操作,背后实现原理可能很复杂。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Python
    相关文章 更多
    python删除一个列表里的重复元素的方法与代码示例
    python删除一个列表里的重复元素的方法与代码示例

    学习如何使用Python高效删除列表中的重复元素。本文提供5种常见方法的代码示例,涵盖保持顺序与不保持顺序的场景,适合初学者和进阶开发者参考。

    Python写接口的步骤及完整代码示例
    Python写接口的步骤及完整代码示例

    详细讲解使用Python编写Web接口的核心步骤与完整代码示例,涵盖环境准备、路由定义、数据校验、服务启动及调试测试方法。

    codex怎么读取word文档及docx文件操作教程
    codex怎么读取word文档及docx文件操作教程

    介绍如何利用编程工具读取和解析Word文档(docx格式),涵盖基础文本提取代码示例、结构化数据处理逻辑,以及处理复杂排版时的技术局限与替代方案。

    怎么在Python中调用C语言代码提升性能
    怎么在Python中调用C语言代码提升性能

    详细讲解在Python中调用C语言代码提升性能的完整实践指南,包含原理分析、编译步骤、ctypes与CFFI具体实现、性能对比及常见陷阱。

    Python与C语言的性能差异及底层原理
    Python与C语言的性能差异及底层原理

    深入解析Python与C语言在执行效率上的数量级差异,从编译机制、变量内存布局、动态类型推断到GIL锁,系统阐明底层核心原理。

    Python和C语言哪个更适合编程入门
    Python和C语言哪个更适合编程入门

    客观对比Python与C语言在心智负担、底层机制与学习反馈上的核心差异,提供清晰有据的选择标准与切实的入门学习路径。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    langgraph python guide中文入门教程详解
    langgraph python guide中文入门教程详解

    本篇LangGraph Python中文入门教程详解,系统梳理StateGraph状态机定义、节点编写、边与条件路由、检查点持久化(MemorySaver)等核心机制,提供从环境安装到完整循环智能体构建的代码示例与实操指导。

    Windows 安装 Python:从官方入口到虚拟环境的可复现实践
    Windows 安装 Python:从官方入口到虚拟环境的可复现实践

    面向后端开发者,基于 Python 官方文档说明如何在 Windows 上安装 Python、创建 venv 虚拟环境及使用 pip 管理依赖。包含可复制命令、验证方法及工具选择边界,避免常见配置误区。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。