栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

第2章 序列构成的数组

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

第2章 序列构成的数组

第2章 序列构成的数组 2.1 内置序列类型概览

容器序列存放的是它们所包含的任意类型的对象的引用, 包括list, tuple, collections.deque等.

扁平序列存放的是值而不是引用, 是一段连续的内存空间, 包括str, bytes, bytearray, memoryview, array.array等.

可变序列: list, bytearray, array.array, collections.deque, memoryview

不可变序列: tuple, str, bytes

2.2 列表推导和生成器表达式 2.2.1 列表推导和可读性

列表推导是构建列表的快捷方式.

symbols = '$¢£¥€¤'
codes = []
for symbol in symbols:
    codes.append(ord(symbol))
codes
>>> [36, 162, 163, 165, 8364, 164]
# use list comp
symbols = '$¢£¥€¤'
codes = [ord(symbol) for symbol in symbols]
codes
>>> [36, 162, 163, 165, 8364, 164]

通常的原则是, 只用列表推导来创建新的列表, 并且尽量保持简短.

2.2.2 列表推导同filter和map比较
# compare list comp and filter/map
symbols = '$¢£¥€¤'
beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
beyond_ascii
>>> [162, 163, 165, 8364, 164]
symbols = '$¢£¥€¤'
beyond_ascii = list(filter(lambda c: c>127, map(ord, symbols)))
beyond_ascii
>>> [162, 163, 165, 8364, 164]

map/filter组合不一定比列表推导要快.

2.2.3 笛卡尔积

由列表推导可以生成两个或以上的可迭代类型的笛卡尔积.

# Cartesian product
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors for size in sizes]
tshirts
>>> [('black', 'S'),
 ('black', 'M'),
 ('black', 'L'),
 ('white', 'S'),
 ('white', 'M'),
 ('white', 'L')]
tshirts = [(color, size) for size in sizes for color in colors]
tshirts
>>> [('black', 'S'),
 ('white', 'S'),
 ('black', 'M'),
 ('white', 'M'),
 ('black', 'L'),
 ('white', 'L')]

如果想要生成其它类型的序列, 可以使用生成器表达式.

2.2.4 生成器表达式

生成器表达式背后遵守了迭代器协议, 可以逐个地产出元素, 而不是先建立一个完整的列表, 然后再把这个列表传递到某个构造函数里.

生成器表达式的语法和列表推导差不多, 只不过把方括号换成了圆括号.

# generator
symbols = '$¢£¥€¤'
tuple(ord(symbol) for symbol in symbols)
>>> (36, 162, 163, 165, 8364, 164)

这里生成器表达式是函数调用过程中的唯一参数, 因此不需要额外的圆括号.

import array
array.array('I', (ord(symbol) for symbol in symbols))
>>> array('I', [36, 162, 163, 165, 8364, 164])

这个array的构造方法需要两个参数, 第一个参数指定了数组中数字的存储方式, 因此需要添加圆括号.

使用生成器表达式计算笛卡尔积

colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirt in ('%s %s' % (c,s) for c in colors for s in sizes):
    print(tshirt)
>>> black S
black M
black L
white S
white M
white L
2.3 元组不仅仅是不可变的列表 2.3.1 元组和记录

元组其实是对数据的记录, 元组的每个元素都存放了记录中一个字段的数据, 外加这个字段的位置.

lax_coordinates = (33.9425, -118.408056)
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for passport in sorted(traveler_ids):
    print('%s/%s' % passport)

BRA/CE342567
ESP/XDA205856
USA/31195855

for country, _ in traveler_ids:
    print(country)

USA
BRA
ESP

2.3.2 元组拆包

最好辨认的元组拆包形式是平行赋值, 如

latitude, longitude = lax_coordinates
latitude

33.9425

longitude

-118.408056

*运算符把一个可迭代对象拆开作为函数的参数

t = (20, 8)
divmod(*t)

(2, 4)

quotient, remainder = divmod(*t)
quotient, remainder

(2, 4)

可以用_占位符处理不感兴趣的数据

import os

_, filename = os.path.split('home/luciano/.ssh/idrsa.pub')
filename

‘idrsa.pub’

用*来处理剩下的数据, 可以放在任意位置

# use of *
a, b, * rest = range(5)
a, b, rest

(0, 1, [2, 3, 4])

a, *body, c, d = range(5)
a, body, c, d

(0, [1, 2], 3, 4)

*head, b, c, d = range(5)
head, b, c, d

([0, 1], 2, 3, 4)

2.3.3 嵌套元素拆包

接受表达式的元组可以是嵌套式的, 只要这个接受元组的嵌套结构符合表达式本身的嵌套结构, Python就能做出正确的对应.

例: 输出元组中在西半球的城市

metro_areas = [
    ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
    ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
    ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
    ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
    ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]

print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas:
    if longitude <= 0:
        print(fmt.format(name, latitude, longitude))
                |   lat.    |   long.  
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358
2.3.4 具名元组

collections.namedtuple 可以用来构建一个带字段名的元组和一个有名字的类.

例:定义和使用具名元组

from collections import namedtuple
City = namedtuple('City', 'name country population coordinates')
tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
tokyo

City(name=‘Tokyo’, country=‘JP’, population=36.933, coordinates=(35.689722, 139.691667))

创建一个具名元组需要两个参数, 一个是类名, 另一个是类的各个字段的名字. 后者可以是由数个字符串构成的可迭代对象, 或以空格分割开的字段名组成的字符串.

可以通过字段名或者位置来获取一个字段的信息.

tokyo.population

36.933

tokyo.coordinates

(35.689722, 139.691667)

tokyo[1]

‘JP’

具名元组还有些自己专有的属性, 比如__fields__, _make(iterable),_asdict()

City._fields

(‘name’, ‘country’, ‘population’, ‘coordinates’)

返回这个类所有字段名称的元组

LatLong = namedtuple('LatLong', 'lat long')
delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
delhi = City._make(delhi_data)
delhi._asdict()

{‘name’: ‘Delhi NCR’,
‘country’: ‘IN’,
‘population’: 21.935,
‘coordinates’: LatLong(lat=28.613889, long=77.208889)}

for key, value in delhi._asdict().items():
    print(key + ':' , value)

name: Delhi NCR
country: IN
population: 21.935
coordinates: LatLong(lat=28.613889, long=77.208889)

_make()接受一个可迭代对象来生成类的一个实例, _asdict()把具名元组以collections.OrderedDict的形式返回.

2.3.5 作为不可变列表的元组

元组和列表的方法和属性中, 除了增减元素相关的方法外, 元组支持列表的其他所有方法.

2.4 切片 2.4.1 为什么切片和区间会忽略最后一个元素
  1. 当只有最后一个位置信息时, 我们也可以快速看出切片和区间里有几个元素
  2. 当起止位置信息都可见时, 我们可以快速计算出切片和区间的长度
  3. 可以利用任意一个下标把序列分割成不重叠的两部分, my_list[:x]和my_list[x:]
2.4.2 对对象进行切片

我们可以用s[a:b:c] 的形式对s在a和b之间以c为间隔取值, c为负意味着反向取值.

a:b:c这种用法只能作为索引或者下标用在[]中来返回一个切片对象: slice(a, b, c). 可以给切片命名, 如

invoice = """
0.....6.................................40........52...55........
1909 Pimoroni PiBrella                      $17.50    3    $52.50
1489 6mm Tactile Switch x20                  $4.95    2    $9.90
1510 Panavise Jr. - PV-201                  $28.00    1    $28.00
1601 PiTFT Mini Kit 320x240                 $34.95    1    $34.95
"""

SKU = slice(0, 6)
DESCRIPTION = slice(6, 40)
UNIT_PRICE = slice(40, 52)
QUANTITY = slice(52, 55)
ITEM_TOTAL = slice(55, None)

line_items = invoice.split('n')[2:]

for item in line_items:
    print(item[UNIT_PRICE], item[DESCRIPTION])
    $17.50   imoroni PiBrella                  
     $4.95   mm Tactile Switch x20             
    $28.00   anavise Jr. - PV-201              
    $34.95   iTFT Mini Kit 320x240    
2.4.3 多维切片和省略

[]运算符还可以使用以逗号分开的多个索引或者是切片, numpy.ndarray就用到了这个特性.

要正确处理这种[]运算符, 对象的__getitem__和__setitem__需要以元组的形式接收a[i, j]的值, python会调用a.__getitem__((i, j)).

省略的正确书写方法是三个英语句号(...). 他可以作为切片规范的一部分, 也可以用在函数的清单参数中. 在numpy中, 如果x是四维数组, 那么x[i, ...]就是x[i, :, :, :]的缩写.

2.4.4 给切片赋值

把切片放在赋值语句左边, 或作为del的对象, 可以对序列进行嫁接, 切除或就地修改的操作.

l = list(range(10))
l

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

l[2:5] = [20, 30]
l

[0, 1, 20, 30, 5, 6, 7, 8, 9]

del l[5:7]
l

[0, 1, 20, 30, 5, 8, 9]

l[3::2] = [11, 22]
l

[0, 1, 20, 11, 5, 22, 9]

l[2:5] = 100

TypeError: can only assign an iterable

对切片赋值时, 右侧必须是个可迭代对象

l[2:5] = [100]
l

[0, 1, 100, 22, 9]

2.5 对序列使用+和*

在拼接的过程中, 两个被操作的序列都不会被修改, Python会新建一个包含同样类型数据的序列来作为拼接的结果.

如果要将一个序列复制几份再拼接起来, 可以使用*.

l = [1, 2, 3]
l * 5

[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]

+和*都遵循这个规律, 不修改原有的操作对象.

建立由列表组成的列表.

board = [['_'] * 3 for i in range(3)]
board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[1][2] = 'X'
board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

如果列表中包含的是同一对象的引用, 则是错误的创建.

# wrong example
weird_board = [['_'] * 3] * 3
weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
weird_board[1][2] = 'X'
weird_board
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
2.6 序列的增量赋值

+=和*=的表现取决于他们的第一个操作对象. +=背后的特殊方法是__iadd__, 如果没有实现__iadd__的话, a+=b这个表达式的效果就变得跟a=a+b一样.

l = [1, 2, 3]
id(l)

1949033126720

l *= 2
l

[1, 2, 3, 1, 2, 3]

id(l)

1949033126720

可变序列ID不变.

t = (1, 2, 3)
print(id(t))
t *= 2
print(id(t))

1949033056256
1949033389120

不可变序列会创建新的对象.

一个关于+=的谜题
t = (1, 2, [30, 40])
t[2] += [50, 60]
print(t)

运行这段程序会报错

TypeError: ‘tuple’ object does not support item assignment

但是再对其输出

print(t)

(1, 2, [30, 40, 50, 60])

发现t还是被改变了.

  1. 不要把可变对象放在元组里面
  2. 增量赋值不是一个原子操作
  3. 查看Python的字节码对了解代码背后的运行机制很有帮助
2.7 list.sort方法和内置函数sorted

list.sort会就地排序列表, 因此其返回值是None.

与list.sort相反的是sorted,它会新建一个列表作为返回值, 可以接受任何形式的可迭代对象作为参数.

list.sort和sorted都有两个参数:

reverse:True则降序输出, 默认为False, 即升序

key: 一个函数, 它会被作用在每个元素上, 返回值用于比较大小.

fruits = ['grape', 'raspberry', 'apple', 'banana']
print(sorted(fruits))
print(fruits)
print(sorted(fruits, reverse=True))
print(sorted(fruits, key=len))
print(sorted(fruits, key=len, reverse=True))
print(fruits)
fruits.sort()
print(fruits)

[‘apple’, ‘banana’, ‘grape’, ‘raspberry’]
[‘grape’, ‘raspberry’, ‘apple’, ‘banana’]
[‘raspberry’, ‘grape’, ‘banana’, ‘apple’]
[‘grape’, ‘apple’, ‘banana’, ‘raspberry’]
[‘raspberry’, ‘banana’, ‘grape’, ‘apple’]
[‘grape’, ‘raspberry’, ‘apple’, ‘banana’]
[‘apple’, ‘banana’, ‘grape’, ‘raspberry’]

上述例子也可看出, list.sort会直接改变原列表, sorted 则不会.

2.8 用bisect来管理已排序的序列

bisect模块包含两个主要函数, bisect和insort, 它们利用二分查找来查找或插入.

2.8.1 用bisect来搜索

bisect(haystack, needle)在haystack中搜索needle的位置.

import bisect
import sys

HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]

ROW_FMT = '{0:2d} @ {1:2d}    {2}{0:<2d}'


def demo(bisect_fn):
    for needle in reversed(NEEDLES):
        position = bisect_fn(HAYSTACK, needle)
        offset = position * '  |'
        print(ROW_FMT.format(needle, position, offset))


if __name__ == '__main__':
    if sys.argv[-1] == 'left':
        bisect_fn = bisect.bisect_left
    else:
        bisect_fn = bisect.bisect

    print('DEMO:', bisect_fn.__name__)
    print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
    demo(bisect_fn)
DEMO: bisect_left
haystack ->  1  4  5  6  8 12 15 20 21 23 23 26 29 30
31 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |31
30 @ 13      |  |  |  |  |  |  |  |  |  |  |  |  |30
29 @ 12      |  |  |  |  |  |  |  |  |  |  |  |29
23 @  9      |  |  |  |  |  |  |  |  |23
22 @  9      |  |  |  |  |  |  |  |  |22
10 @  5      |  |  |  |  |10
 8 @  4      |  |  |  |8 
 5 @  2      |  |5 
 2 @  1      |2 
 1 @  0    1 
 0 @  0    0 
DEMO: bisect_right
haystack ->  1  4  5  6  8 12 15 20 21 23 23 26 29 30
31 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |31
30 @ 14      |  |  |  |  |  |  |  |  |  |  |  |  |  |30
29 @ 13      |  |  |  |  |  |  |  |  |  |  |  |  |29
23 @ 11      |  |  |  |  |  |  |  |  |  |  |23
22 @  9      |  |  |  |  |  |  |  |  |22
10 @  5      |  |  |  |  |10
 8 @  5      |  |  |  |  |8 
 5 @  3      |  |  |5 
 2 @  1      |2 
 1 @  1      |1 
 0 @  0    0 

上述例子分别展示了bisect_left和bisect_right 的查找位置.

例: 根据一个分数, 找到对应的成绩.

import bisect


def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
    i = bisect.bisect(breakpoints, score)
    return grades[i]


[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]

[‘F’, ‘A’, ‘C’, ‘C’, ‘B’, ‘A’, ‘A’]

2.8.2 用bisect.insort插入新元素

insort(seq, item)将变量item插入seq中, 并能保持seq的升序排列.

import bisect
import random

SIZE = 7
random.seed(1729)

my_list = []
for i in range(SIZE):
    new_item = random.randrange(SIZE * 2)
    bisect.insort(my_list, new_item)
    print('%2d ->' % new_item, my_list)

10 -> [10]
0 -> [0, 10]
6 -> [0, 6, 10]
8 -> [0, 6, 8, 10]
7 -> [0, 6, 7, 8, 10]
2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]

2.9 当列表不是首选时 2.9.1 数组

如果需要一个只包含数字的列表, 那么array.array比list更高效.

from array import array
from random import random

floats = array('d', (random() for i in range(10 ** 7)))
print(floats[-1])
fp = open('floats.bin', 'wb')
floats.tofile(fp)
fp.close()
floats2 = array('d')
fp = open('floats.bin', 'rb')
floats2.fromfile(fp, 10 ** 7)
fp.close()
print(floats2[-1])
print(floats2 == floats)

0.5963321947530882
0.5963321947530882
True

tofile可以将array存入到一个二进制文件, fromfile可以从二进制文件中读取.

array类型不支持如list.sort()这种就地排序方法.

2.9.2 内存视图

memoryview能让用户在不复制内容的情况下操作同一个数组的不同切片.

memoryview.cast的概念跟数组模块类似, 能用不同的方式读写同一块内存数据, 而且内容字节不会随意移动. memoryview.cast会把同一块内存里的内容打包成一个全新的memoryview对象.

# memoryview
numbers = array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
print(len(memv))
print(memv[0])
memv_oct = memv.cast('B')
print(memv_oct.tolist())
memv_oct[5] = 4
print(numbers)

5
-2
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array(‘h’, [-2, -1, 1024, 1, 2])

这里h表示短整型有符号整数, B表示无符号字符.

2.9.3 numpy和scipy
# numpy
import numpy

a = numpy.arange(12)
print(a)
print(type(a))
print(a.shape)
a.shape = 3, 4
print(a)
print(a[2])
print(a[2, 1])
print(a[:, 1])
print(a.transpose())
[ 0  1  2  3  4  5  6  7  8  9 10 11]

(12,)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
[ 8  9 10 11]
9
[1 5 9]
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]
2.9.4 双向队列和其他形式的队列

collections.deque是一个线程安全, 可以快速从两端添加或者删除元素的数据类型.

# deque
from collections import deque

dq = deque(range(10), maxlen=10)
print(dq)
dq.rotate(3)
print(dq)
dq.rotate(-4)
print(dq)
dq.appendleft(-1)
print(dq)
dq.extend([11, 22, 33])
print(dq)
dq.extendleft([10, 20, 30, 40])
print(dq)

deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)

maxlen确定了队列的最大长度.

rotate会将队列的所有数字向右移动, 若为负, 则左移.

appendleft会将数字添加到队列头部, 若超过最大长度, 则删除队列尾部的相应数量的数字.

extend会将列表中的三个数字依次添加到尾部, 并删掉头部的多余数字.

extendleft会添加到头部, 也是依次添加, 所以顺序是逆序的.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1014594.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号