NumPy向量化散列表,用于快速集合和字典操作。
项目描述
NumPy向量化散列表,用C语言编写,用于快速(大约快10倍)的集合/字典-like操作。
自由软件:MIT许可证
hirola.HashTable与dict的关系类似于numpy.array与list的关系。通过施加一些约束、向量化并将代码转换为C语言,可以显著提高速度。对于hirola,这些约束包括:
键必须全部是相同预定的类型和大小。
表的最大大小必须事先选择并显式管理。
为了获得任何性能提升,操作应批量进行。
元素不能被删除。
如果上述任何一项不符合您的使用场景,则不要使用hirola。
安装
使用pip安装Hirola
pip install hirola
快速入门
散列表
在最原始和最污秽的层面,存在着HashTable类。可以将HashTable想象成一个只对值进行枚举的dict。要构造一个空的哈希表
import numpy as np
from hirola import HashTable
table = HashTable(
20, # <--- Maximum size for the table - up to 20 keys.
"U10", # <--- NumPy dtype - strings of up to 10 characters.
)
可以单独添加键...
>>> table.add("cat")
0
...但批量添加更有效率。返回值是每个键首次添加时的时间枚举。重复的键不会被重新添加。
>>> table.add(["dog", "cat", "moose", "gruffalo"])
array([1, 0, 2, 3])
多维度输入给出匹配形状的多维度输出。
>>> table.add([["rabbit", "cat"],
... ["gruffalo", "moose"],
... ["werewolf", "gremlin"]])
array([[4, 0],
[3, 2],
[5, 6]])
通过keys属性检查到目前为止添加的所有键。(注意,与dict.keys()不同,它是一个属性而不是一个方法。)
>>> table.keys
array(['cat', 'dog', 'moose', 'gruffalo', 'rabbit', 'werewolf', 'gremlin'],
dtype='<U10')
可以通过table.get(key)或直接table[key]检索键索引。再次强调,检索是NumPy向量化的,如果给定大量输入数组而不是单个输入,则速度更快。
>>> table.get("dog")
1
>>> table[["moose", "gruffalo"]]
array([2, 3])
与Python的dict类似,使用table[key]会在键缺失时引发KeyError,但使用table.get(key)会返回一个可配置的默认值。与Python的dict不同,默认值是-1。
>>> table["tortoise"]
KeyError: "key = 'tortoise' is not in this table."
>>> table.get("tortoise")
-1
>>> table.get("tortoise", default=99)
99
>>> table.get(["cat", "bear", "tortoise"], default=[100, 101, 102])
array([ 0, 101, 102])
选择最大大小
与Python的set和dict不同,Hirola默认情况下不会自动管理其大小(尽管它可以重新配置)。为了防止浪费性的大小调整(这是Python在底层所做的),您可以完全控制并负责表使用多少空间。显然,表的大小必须足够大,以容纳其中的所有键。此外,当哈希表接近满载时,它会变得非常慢。根据您对速度和内存的重视程度,您应该添加20-50%的额外空间。如果您打算查找大量相同的小值集,则如果将最大值增加到最小值的2-3倍,它可以继续运行得更快。
结构化键数据类型
要表示数组轴应被视为单个键,请使用NumPy的结构化dtype。在以下示例中,数据类型(points.dtype, 3)表示一个三维点(三个浮点数的组合)应被视为一个对象。有关指定dtypes的更多信息,请参阅help(HashTable.dtype)。只有最后一个轴或最后几个轴可以被视为单个键。对于其他设置,请首先使用numpy.transpose()进行转换。
import numpy as np
from hirola import HashTable
# Create a cloud of 3D points with duplicates. This is 3000 points in total,
# with up to 1000 unique points.
points = np.random.uniform(-30, 30, (1000, 3))[np.random.choice(1000, 3000)]
# Create an empty hash table.
# In practice, you generally don't know how many unique elements there are
# so we'll pretend we don't either an assume the worst case of all 3000 are
# unique. We'll also give 25% padding for speed.
table = HashTable(len(points) * 1.25, (points.dtype, 3))
# Add all points to the table.
ids = table.add(points)
可以通过table.keys访问无重复内容
>>> table.keys # <--- These are `points` but with no duplicates.
array([[ 3.47736554, -15.17112511, -9.51454466],
[ -6.46948046, 23.64504329, -16.25743105],
[-27.02527253, -16.1967225 , -10.11544157],
...,
[ 3.75972597, 1.24130412, -8.14337206],
[-13.62256791, 11.76551455, -13.31312988],
[ 0.19851678, 4.06221179, -22.69006592]])
>>> table.keys.shape
(954, 3)
table.add()返回每个点在table.keys中的位置,类似于numpy.unique(..., return_args=True)。
>>> ids # <--- These are the indices in `table.keys` of each point in `points`.
array([ 0, 1, 2, ..., 290, 242, 669])
>>> np.array_equal(table.keys[ids], points)
True
使用table.get()查找点索引,而无需添加它们。
处理几乎满的哈希表
当HashTable几乎满载时,它会变得非常慢。从v0.3.0版本开始,如果表超出90%的容量,将发出效率警告。此警告可以被重新配置为错误、静音或自动调整表大小以腾出空间。以下示例构造函数演示了这些方法
# The default: Issue a warning when the table is 90% full.
HashTable(..., almost_full=(0.9, "warn"))
# Disable all "almost full" behaviours.
HashTable(..., almost_full=None)
# To consider a table exceeding 80% full as an error use:
HashTable(..., almost_full=(0.8, "raise"))
# To automatically triple in size whenever the table exceeds 80% full use:
HashTable(..., almost_full=(0.8, 3.0))
调整表的大小很慢,这就是为什么默认情况下不启用它。除非您真的不知道表需要多大,否则应避免调整大小。
食谱
HashTable可以用来复制一个dict、set或一个collections.Counter。这些可能在未来成为它们自己的类,也可能不会。
将HashTable用作dict
字典需要一个用于值的第二个数组。应该将HashTable.add()和HashTable.get()的输出用作values的索引。
import numpy as np
from hirola import HashTable
# The `keys` - will be populated with names of African countries.
countries = HashTable(40, (str, 20))
# The `values` - will be populated with the names of each country's capital city.
capitals = np.empty(countries.max, (str, 20))
使用模式values[table.add(key)] = value添加或设置项。
capitals[countries.add("Algeria")] = "Al Jaza'ir"
或批量操作
new_keys = ["Angola", "Botswana", "Burkina Faso"]
new_values = ["Luanda", "Gaborone", "Ouagadougou"]
capitals[countries.add(new_keys)] = new_values
与Python字典一样,覆盖值与写入值完全相同。
使用values[table[key]]检索值。
>>> capitals[countries["Botswana"]]
'Gaborone'
>>> capitals[countries["Botswana", "Algeria"]]
array(['Gaborone', "Al Jaza'ir"], dtype='<U20')
使用table.keys和values[:len(table)]查看所有键和值。由于HashTable会记住键首次添加的顺序,因此这个字典自动是排序字典。
# keys
>>> countries.keys
array(['Algeria', 'Angola', 'Botswana', 'Burkina Faso'], dtype='<U20')
# values
>>> capitals[:len(countries)]
array(["Al Jaza'ir", 'Luanda', 'Gaborone', 'Ouagadougou'], dtype='<U20')
根据使用场景,可能需要或不需要dict.items()的等效功能。如果您需要等效功能,请使用numpy.rec.fromarrays([table.keys, values[:len(table)]]),可能还需要添加一个names=选项。
>>> np.rec.fromarrays([countries.keys, capitals[:len(countries)]],
... names="countries,capitals")
rec.array([('Algeria', "Al Jaza'ir"), ('Angola', 'Luanda'),
('Botswana', 'Gaborone'), ('Burkina Faso', 'Ouagadougou')],
dtype=[('countries', '<U20'), ('capitals', '<U20')])
如果键和值具有相同的dtype,则numpy.c_也适用。
>>> np.c_[countries.keys, capitals[:len(countries)]]
array([['Algeria', "Al Jaza'ir"],
['Angola', 'Luanda'],
['Botswana', 'Gaborone'],
['Burkina Faso', 'Ouagadougou']], dtype='<U20')
将HashTable用作set
要从HashTable中获得类似于集合的功能,利用contains()方法。在这些示例中,我们将尝试3和7的整数倍。
import numpy as np
of_3s = np.arange(0, 100, 3)
of_7s = np.arange(0, 100, 7)
我们只需要将一个数组转换为散列表。另一个可以保持为数组。如果两者都是散列表,只需使用一个表的keys属性作为数组即可。
from hirola import HashTable
table_of_3s = HashTable(len(of_3s) * 1.25, of_3s.dtype)
table_of_3s.add(of_3s)
使用table.contains()作为in的矢量化版本。
>>> table_of_3s.contains(of_7s)
array([ True, False, False, True, False, False, True, False, False,
True, False, False, True, False, False])
从上面的例子中,可以使用以下方法推导出常见的集合操作
set.intersection() - 数组和集合中的值
>>> of_7s[table_of_3s.contains(of_7s)]
array([ 0, 21, 42, 63, 84])
集合减法 - 数组中不在集合中的值
>>> of_7s[~table_of_3s.contains(of_7s)]
array([ 7, 14, 28, 35, 49, 56, 70, 77, 91, 98])
set.union() - 表或测试数组中的值(无重复)
>>> np.concatenate([table_of_3s.keys, of_7s[~table_of_3s.contains(of_7s)]], axis=0)
array([ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48,
51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99,
7, 14, 28, 35, 49, 56, 70, 77, 91, 98])
将HashTable用作collections.Counter
对于这个例子,让我们做一些更实质性的工作。统计莎士比亚的《哈姆雷特》剧本中的单词频率是collections.Counter的流行例子,我们也将使用它。
from urllib.request import urlopen
import re
import numpy as np
hamlet = urlopen("https://gist.githubusercontent.com/provpup/2fc41686eab7400b796b/raw/b575bd01a58494dfddc1d6429ef0167e709abf9b/hamlet.txt").read()
words = np.array(re.findall(rb"([\w']+)", hamlet))
计数器只是一个具有整数值的字典,而字典只是一个具有单独值数组的散列表。
from hirola import HashTable
word_table = HashTable(len(words), words.dtype)
counts = np.zeros(word_table.max, dtype=int)
在将散列表用作字典中未定义的唯一新功能是计数键作为它们添加的能力。要计数新元素,请使用相当奇怪的行np.add(counts, table.add(keys), 1)。
np.add.at(counts, word_table.add(words), 1)
该行执行您可能期望的counts[word_table.add(words)] += 1操作,但由于NumPy的工作方式,如果words包含重复项,则后者形式无法将每个计数增加超过一次。
使用NumPy的间接排序函数获取最常或最不常用的键。
# Get the most common word.
>>> word_table.keys[counts[:len(word_table)].argmax()]
b'the'
# Get the top 10 most common words. Note that these are unsorted.
>>> word_table.keys[counts[:len(word_table)].argpartition(-10)[-10:]]
array([b'it', b'and', b'my', b'of', b'in', b'a', b'to', b'the', b'I',
b'you'], dtype='|S14')
# Get all words in ascending order of commonness.
>>> word_table.keys[counts[:len(word_table)].argsort()]
array([b'END', b'whereat', b"griev'd", ..., b'to', b'and', b'the'],
dtype='|S14')
安全提示
与Python的内置hash()函数不同,该函数被set和dict内部使用,hirola在启动时不会随机化哈希种子,这使得运行hirola的在线服务器更容易受到拒绝服务攻击。在这种攻击中,攻击者通过发送他知道会导致哈希冲突的请求来堵塞你的服务器,从而减慢其速度。而Python哈希表的大小总是可预测的,是len(table) * 3 / 2以上下一个8的幂,而hirola.HashTable()可以是任何大小,这意味着你可以通过为你的哈希表大小添加一些随机性来使攻击更加困难。但如果你正在编写基于用户输入进行字典查找的在线服务器,而你的用户群不喜欢你或者你有非常敌意的竞争对手,那么我建议你不要使用这个库。
致谢
这个包最初是用Cookiecutter和audreyr/cookiecutter-pypackage项目模板的一个分支创建的。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分布
构建分布
hirola-0.3.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 68fc96b00be7b55f9268dba03d25eba24692a2684e0f81778a2021a2a3789194 |
|
MD5 | d30b6959b06863dd40a49979b159edee |
|
BLAKE2b-256 | 9ef8808540c0c674d3a098caa0ccd1955d5f9bf020b77903072f176c7eb331df |
hirola-0.3.0-py3-none-win_arm64.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 98d67c42304e2885b2f66ecbd4ffdbf70e6b092e5f426f903e0049b4b3c8cf9d |
|
MD5 | af75edf7dace34df5344ea7eec124f67 |
|
BLAKE2b-256 | 6f8401d88448b423b0971c650468f2d059fe71345294a6984d03913d0426fa94 |
hirola-0.3.0-py3-none-win_amd64.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 907b3a6102da1dc24312df8884f0fda74d3564d499f680b670144aa924f01573 |
|
MD5 | 01dbe537f369c88ca2a8b3e283fc6da5 |
|
BLAKE2b-256 | ff6c229526d659d5ac872f684dcc821635e91f2f571cd0e0959e2892e4969df7 |
hirola-0.3.0-py3-none-win32.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 2f641758a35f014c680bf2b11d41274ac2ef7e59a9659b33237e0ca8e2bc13b7 |
|
MD5 | d4f96e8b81805bbb9528fcc066773e71 |
|
BLAKE2b-256 | edbe5d397fd1c24d9ca9014aa230d082730388c1352efbe8f1bbed4bf0d1cd67 |
hirola-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | efa212fe8ee42293fdd317f74f3a8565c658e23369548cc102708090225d1871 |
|
MD5 | e452602455d554b384209a57a1c26862 |
|
BLAKE2b-256 | ac8e0eccb528701273640dd4f13678a0c1352176166aecd1ee7f4fa29355132b |
hirola-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | b2f823b3ccbed238e6dd979409f4d84f7828041622aae71292f57553a8b66391 |
|
MD5 | 21fcdcd1d78a916718ffff4ea5538e93 |
|
BLAKE2b-256 | 980f919059255415debec0c372f35eef794d54d4684435c284e8be1b243f8bd5 |
hirola-0.3.0-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | a3823e98d6ec17ed73c71caacc3d30d64c80c5e359992859537541579e723440 |
|
MD5 | 18531861f9dc2d55ccf5a4534649f298 |
|
BLAKE2b-256 | 5e9a43fafb3a67f22636324bc1b218063052688beeec01efe5ac3389458a547c |
hirola-0.3.0-py3-none-manylinux_2_5_i686.manylinux1_i686.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | d0656d9cc5dd588d035d43b05cfa79c242d509a6e2da706cbd4c63525ebc777e |
|
MD5 | 35dee7aa2847eb02e53658bd71bf939c |
|
BLAKE2b-256 | 32cea99dda38eb13798b922769c138de47abd94d0b98dd36364b094deec7bd25 |
哈希值 for hirola-0.3.0-py3-none-macosx_10_6_universal2.whl
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 5ef89e18ee4186b2e0d0595728a2d6d46d0258a3043c9f0f9a348249b8d9b3a5 |
|
MD5 | a3781ef5eab8b21e6759a3983ef32a37 |
|
BLAKE2b-256 | b2169298cca16e26d2fc3751a2b93469cc14a6f8bb9fb5f4087977926d936383 |