从 10 亿位数字里查找指定的数字,怎样才能快一些?

2021-11-10 17:42:13 +08:00
 grimpil

从网上下一个 950M 的 txt 文件,里面保存的是圆周率小数点后的 10 亿位数字。想使用 python 查找某个指定的 6 位或 8 位数字在其中的位置,现在直接读文件后用 str.find()查找实在太慢了,请教各位有什么比较快的办法吗?

文件下载地址: https://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt

7656 次点击
所在节点    Python
53 条回复
datou
2021-11-10 18:50:05 +08:00
两秒出结果,很慢么?
djFFFFF
2021-11-10 18:50:29 +08:00
预处理,用空间换时间是最优解法。只是六位到八位(而且盲猜是出生日期?那更简单了)的话存一张表轻松解决。

@hidemyself cpython 我印象里 str.find() 是用的 BMH 算法?反正虽然这个题面是个标准的 KMP 算法的场景,现实生产环境谁用谁是傻子。
546L5LiK6ZOt
2021-11-10 18:59:37 +08:00
https://nullprogram.com/blog/2014/09/18/
这个老外尝试了多种方法,可以参考下
lonenol
2021-11-10 20:21:46 +08:00
最粗暴的就是 hash 呗,key 是数字,value 是位置,第一次构建比较慢,剩余的查询就都是 O(1)的了
lonenol
2021-11-10 20:22:30 +08:00
不好意思,python 里叫字典,我习惯用 hash 指代 Java 里的 HashMap 了
yianing
2021-11-10 20:28:46 +08:00
trie 就行了吧,只是加载需要点时间,搞个常驻进程就行,我用 go 试了下内存大约 1G ,加载不到 10 分钟
![stats]( https://imgur.com/GjcslkB)
GrayXu
2021-11-10 20:42:48 +08:00
这如果是个题,考的自然是子串匹配,Boyer-Moore 等。
就算建索引,也是用 trie 树系列,用 hash 有点太异想天开。。。
tianq
2021-11-10 20:57:58 +08:00
好久以前研究过在 pi 里找生日:
https://lil-q.github.io/blog/pi/
searene
2021-11-10 21:08:07 +08:00
我也把文件下下来了,1.6 秒左右就找到了。如果是题目的话,这道题目是不合格的,因为现实情况就是用 find 就可以了,建索引还更慢
Jelebi
2021-11-10 22:32:26 +08:00
Ctrl + F
vanton
2021-11-10 22:36:15 +08:00
本地跑 str.find() 最多几秒种,速度足够了。

如果你有特别的需求,比如高并发服务,那就索引,数据库或者 hash 都行,不要读文本。
lesismal
2021-11-10 23:46:44 +08:00
用数据库存上也是慢,内存里缓存起来性能最好了,下面代码大概意思是 converter 先统计好索引到数组,然后把数组写入到文件,finder 读入文件初始化数组,然后再查找。没仔细调试,因为太烧机器了,有兴趣的同学可以完善下:

1. converter.py
```python
# -*- coding:utf-8 -*-
#!/usr/bin/python3

import datetime

class PIConverter:
def __init__(self, minNum=100000, maxNum=99999999):
self.minNum = minNum
self.maxNum = maxNum
self.positions = [0]*(self.maxNum+1-self.minNum)

def convert(self, srcFile, dstFile):
fsrc = open(srcFile,'r')
fsrc.read(2)
try:
lastStr = ""
readSize = 1024*8
currPos = 0
readed = 0

starttime = datetime.datetime.now()

offset = len(str(self.minNum)) - 1
while True:
s = fsrc.read(readSize)
s = lastStr + s # 这里可以再优化下
currPos -= len(lastStr)
for i in range(len(s)-8):
strLen = len(str(self.minNum))
while strLen <= len(str(self.maxNum)):
subs = s[i:i+strLen]
strLen += 1
num = int(subs)
index = num - self.minNum
if self.positions[index] == 0:
self.positions[index] = currPos + i

if len(s) == 0:
break

lastStr = s[len(s)-5:]
currPos += readSize
readed += readSize
if readed % (1024*1024*8) == 0:
print("total read: {}, time used: {}s".format(readed, (datetime.datetime.now() - starttime).seconds))

print("total read: {}, time used: {}s".format(readed, (datetime.datetime.now() - starttime).seconds))
print("done")

try:
fdst = open(dstFile,'rw+')
for index in range(self.positions):
fdst.write(str(index)+"\n")
finally:
fdst.close()
finally:
fsrc.close()

def find(self, n):
if n < self.minNum or n > 99999999:
return -1
return self.positions[n - self.minNum]

piConverter = PIConverter()

# 把已经统计出来的生成更小的文件
piConverter.convert("./pi-billion.txt", "./pi-position.txt")

# converter 初始化太慢了,所以最好还是先 piConverter.convert 把已经统计出来的生成更小的文件,finder.py 用该文件初始化和做查找
# print("141592:", piConverter.find(141592))
# print("415926:", piConverter.find(415926))
```

2. finder.py
```python
# -*- coding:utf-8 -*-
#!/usr/bin/python3

class PIFinder:
def __init__(self, fname, minNum=100000, maxNum=99999999):
self.minNum = minNum
self.maxNum = maxNum
self.positions = [0]*(self.maxNum+1-self.minNum)
f = open(fname,'r')
try:
i = 0
for line in f:
num = int(line)
self.positions[i] = num
finally:
f.close()

def find(self, n):
if n < self.minNum or n > 99999999:
return -1
return self.positions[n - self.minNum]

piFinder = PIFinder("./pi-position.txt")
print("141592:", piFinder.find(141592))
print("415926:", piFinder.find(415926))
```
lesismal
2021-11-10 23:53:39 +08:00
#32 文件尾、打开写文件的好像都有问题,平时不写 py ,实在不熟悉,v 站发代码也确实难受,对齐好像都没了
lesismal
2021-11-11 00:22:06 +08:00
算了,忍不住还是调试了下,完整版的:
https://gist.github.com/lesismal/c4528eacc35db33f754ac2f8eb9e7634
c0xt30a
2021-11-11 02:03:53 +08:00
我提一个用素数来 Hash 查找的方法,大致如下:

1. 将 0-9 映射为 前 10 个素数 [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
2. 用一个定长为 6/8 的滑动窗口遍历这个 pi 的字符串,每次增长的时候,当前的 hash 先除以最后一位数字对应的素数再乘以新增数字对应的素数,可以得到最新的 hash 数值
3. 如果当前 hash 数值与要寻找的数字的 hash 相等,则停下来进一步比对字符串
c0xt30a
2021-11-11 05:45:27 +08:00
当然 直接乘以 10 加上新来的数字再对 10^7 取 mode 以更新 hash 也行
kuangwinnie
2021-11-11 05:51:31 +08:00
950MB 塞内存里也没多大啊。
murmur
2021-11-11 08:28:30 +08:00
950m 进内存配合现在的处理器可能有发帖时间都做出来了吧,这是跑 leetcode 限制内存了?
gulugu
2021-11-11 08:42:44 +08:00
分割了,然后分布式查询
ihainan
2021-11-11 08:52:56 +08:00
固定 6 位和 8 位的话或许可以考虑 Rabin-Karp 算法求哈希值。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/814478

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX