从网上下一个 950M 的 txt 文件,里面保存的是圆周率小数点后的 10 亿位数字。想使用 python 查找某个指定的 6 位或 8 位数字在其中的位置,现在直接读文件后用 str.find()查找实在太慢了,请教各位有什么比较快的办法吗?
文件下载地址: https://stuff.mit.edu/afs/sipb/contrib/pi/pi-billion.txt
1
renmu123 2021-11-10 17:45:01 +08:00 via Android
用滑动窗口应该能稍微快一点
|
2
Ediacaran 2021-11-10 17:46:44 +08:00 via iPhone 3
000 ~ 999 所在位置索引一个表
|
3
Junzhou 2021-11-10 17:48:48 +08:00 1
kmp
|
4
vvhhaaattt 2021-11-10 17:49:53 +08:00 via Android 1
没有限制的话,空间换时间,类似 ngram 索引
|
5
hahasong 2021-11-10 17:51:47 +08:00
读取文件了,直接操作内存,分片多线程查找
|
6
cclin 2021-11-10 17:52:25 +08:00 via Android
KMP 么
|
7
radiocontroller 2021-11-10 17:53:43 +08:00
字符串查找子串,kmp 算法
|
8
aircjm 2021-11-10 17:54:20 +08:00 via Android
盲猜从圆周率里面取日期
|
9
SmiteChow 2021-11-10 17:54:43 +08:00
倒排
|
10
cclin 2021-11-10 18:12:52 +08:00
我把文件下载下来了,用 str.find() 挺快的呀,读取文件 3.66s ,查找 0.007s
|
11
Vegetable 2021-11-10 18:24:49 +08:00
都是什么宰牛刀啊都,直接全量塞数据库啊!才多大点啊
|
12
3dwelcome 2021-11-10 18:26:06 +08:00
PI 属于随机数那种,索引都不好建。
怕是没什么好办法。只能挨个查找。 |
14
xx6412223 2021-11-10 18:30:31 +08:00
都是 O(n),
事先加载文件并事先分段并多线程 |
15
hidemyself 2021-11-10 18:31:41 +08:00
楼上说 kmp 的有没有看过 python 对于 find 的实现哇。。
|
17
nazor 2021-11-10 18:35:06 +08:00
有个数据结构叫后缀数组,特别适合你提出的这种文本不变,模式串不同的查询需求。
|
18
oOoOoOoOoOo 2021-11-10 18:39:10 +08:00 via Android
|
19
oOoOoOoOoOo 2021-11-10 18:40:13 +08:00 via Android
分片 线程查找
|
20
3dwelcome 2021-11-10 18:44:04 +08:00
“这是题吗,没看出来啊,这种问题最优解就是空间换时间,再怎么做不还是索引吗”
问题的关键,是如何去建索引。完全乱序的数字,没办法建立有效的索引结构。 |
21
datou 2021-11-10 18:50:05 +08:00
两秒出结果,很慢么?
|
22
djFFFFF 2021-11-10 18:50:29 +08:00
预处理,用空间换时间是最优解法。只是六位到八位(而且盲猜是出生日期?那更简单了)的话存一张表轻松解决。
@hidemyself cpython 我印象里 str.find() 是用的 BMH 算法?反正虽然这个题面是个标准的 KMP 算法的场景,现实生产环境谁用谁是傻子。 |
23
546L5LiK6ZOt 2021-11-10 18:59:37 +08:00 via iPhone 4
https://nullprogram.com/blog/2014/09/18/
这个老外尝试了多种方法,可以参考下 |
24
lonenol 2021-11-10 20:21:46 +08:00
最粗暴的就是 hash 呗,key 是数字,value 是位置,第一次构建比较慢,剩余的查询就都是 O(1)的了
|
25
lonenol 2021-11-10 20:22:30 +08:00
不好意思,python 里叫字典,我习惯用 hash 指代 Java 里的 HashMap 了
|
26
yianing 2021-11-10 20:28:46 +08:00
trie 就行了吧,只是加载需要点时间,搞个常驻进程就行,我用 go 试了下内存大约 1G ,加载不到 10 分钟
![stats]( https://imgur.com/GjcslkB) |
27
GrayXu 2021-11-10 20:42:48 +08:00
这如果是个题,考的自然是子串匹配,Boyer-Moore 等。
就算建索引,也是用 trie 树系列,用 hash 有点太异想天开。。。 |
28
tianq 2021-11-10 20:57:58 +08:00 via iPhone 6
好久以前研究过在 pi 里找生日:
https://lil-q.github.io/blog/pi/ |
29
searene 2021-11-10 21:08:07 +08:00
我也把文件下下来了,1.6 秒左右就找到了。如果是题目的话,这道题目是不合格的,因为现实情况就是用 find 就可以了,建索引还更慢
|
30
Jelebi 2021-11-10 22:32:26 +08:00
Ctrl + F
|
31
vanton 2021-11-10 22:36:15 +08:00
本地跑 str.find() 最多几秒种,速度足够了。
如果你有特别的需求,比如高并发服务,那就索引,数据库或者 hash 都行,不要读文本。 |
32
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)) ``` |
33
lesismal 2021-11-10 23:53:39 +08:00
#32 文件尾、打开写文件的好像都有问题,平时不写 py ,实在不熟悉,v 站发代码也确实难受,对齐好像都没了
|
34
lesismal 2021-11-11 00:22:06 +08:00 2
算了,忍不住还是调试了下,完整版的:
https://gist.github.com/lesismal/c4528eacc35db33f754ac2f8eb9e7634 |
35
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 相等,则停下来进一步比对字符串 |
36
c0xt30a 2021-11-11 05:45:27 +08:00
当然 直接乘以 10 加上新来的数字再对 10^7 取 mode 以更新 hash 也行
|
37
kuangwinnie 2021-11-11 05:51:31 +08:00
950MB 塞内存里也没多大啊。
|
38
murmur 2021-11-11 08:28:30 +08:00
950m 进内存配合现在的处理器可能有发帖时间都做出来了吧,这是跑 leetcode 限制内存了?
|
39
gulugu 2021-11-11 08:42:44 +08:00
分割了,然后分布式查询
|
40
ihainan 2021-11-11 08:52:56 +08:00
固定 6 位和 8 位的话或许可以考虑 Rabin-Karp 算法求哈希值。
|
41
rrfeng 2021-11-11 09:12:37 +08:00 via Android
那要查的数字范围 6/8 的前 N 位枚举出来遍历一下做位置索引,N 取值可以做个测试找到空间和时间的平衡点。
盲猜你要查生日,那查询目标才没几个,全量索引都不为过。 |
42
xiao109 2021-11-11 09:30:00 +08:00
重点是查,所以建立索引结构的时间应该不会纳入耗时的计算。
按 6 或 8 位截取数字映射到索引中,然后再搜。 |
43
arthurire 2021-11-11 09:36:19 +08:00
这是啥算法题啊...
算法不就是 KMP 之类 你还能突破理论极限不成? 要是比速度就建立各种索引,然后 O(1) 别侮辱算法题啊 |
44
xz410236056 2021-11-11 10:34:23 +08:00
str.find() 是 Boyer-Moore 和 Horspool 算法的结合,这都慢用 KMP 能快吗?
|
45
lizytalk 2021-11-11 10:35:47 +08:00
如果是查多次的话, 可以把整个文档处理成后缀数组 (只需要常数空间), 然后每次查询可以做到对数时间 O(P log (T)), T 是整个文档的长度, P 是查询的长度.
至于建索引, 时间倒是 O(1)的, 但是索引的空间可是指数级别的. |
46
lesismal 2021-11-11 10:37:33 +08:00
|
47
irobbin 2021-11-11 10:40:03 +08:00 3
如果算生日的话,365*100= 36500 ,总共就这么点数据,找个时间整体遍历一遍,查完存起来搞定。
|
48
neptuno 2021-11-11 11:18:18 +08:00
遍历所有数字排列,存数据库
|
49
asmoker 2021-11-11 11:48:47 +08:00
sublime 或者 vim 😀
|
50
MegrezZhu 2021-11-11 11:50:58 +08:00
才 6 到 8 位…就算用上 O(n+m)的 kmp 也撑死优化几倍的时间,有这个工夫用 C++写个暴力就得了
|
51
trcnkq 2021-11-11 15:48:31 +08:00
|
52
wnma3mz 2021-11-12 09:36:49 +08:00
盲猜是查找生日字符串之类的,之前实现过,供参考
https://github.com/wnma3mz/birthday_in_pi/blob/master/read_large_pi.py#L20-L30 主要问题还是放到服务器里面内存问题啥的。 暴力的方法,还可以建表。。 |