-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPiDigitStream.py
More file actions
175 lines (136 loc) · 4.58 KB
/
Copy pathPiDigitStream.py
File metadata and controls
175 lines (136 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
from collections import deque
from mpmath import mp
import sys
import re
import os
class FileIterator:
'''
Read chars from a file. For better performance, use a buffer of size chunk_size.
'''
def __init__(self, filename: str, chunk_size=100):
self._filename = filename
self._file = None
self._next_chars = deque()
self._chunk_size = chunk_size
def __iter__(self):
return self
def __next__(self):
# Try to open the file, if this has not happened yet.
# Stop the iteration, if the file cannot be opened/found.
if self._file == None:
try:
self._file = open(self._filename, "r")
except:
raise StopIteration
if not self._next_chars:
chunk = self._file.read(self._chunk_size)
if not chunk:
self._file.close()
raise StopIteration
self._next_chars.extend(chunk)
char = self._next_chars.popleft()
if char == "\n":
self._file.close()
raise StopIteration
return char
class SkipIterator:
'''
Create an Iterator that skips the first n items of another iterator "source".
'''
def __init__(self, source, n):
self.source = iter(source)
self.n = n
self._skipped = False
def __iter__(self):
return self
def __next__(self):
if not self._skipped:
for _ in range(self.n):
try:
next(self.source)
except StopIteration:
break
self._skipped = True
return next(self.source)
class MpMathChunkFallback:
'''
Use MpMath to generate digits of Pi.
For better performance, a buffer of size chunk_size can be used.
'''
def __init__(self, start_offset=0, chunk_size=20):
self.start_offset = start_offset
self.chunk_size = chunk_size
self._buffer = deque()
self._digits = ""
self._index = start_offset
def _init_digits(self):
self._digits = str(mp.pi)[2:]
def _refill(self):
mp.dps = self._index + self.chunk_size + 2
self._init_digits()
chunk = self._digits[self._index:self._index+self.chunk_size]
self._buffer.extend(chunk)
def __iter__(self):
return self
def __next__(self):
if len(self._buffer) == 0:
self._refill()
self._index += 1
result = self._buffer.popleft()
return result
class FileThenFallback:
'''
Combine a finite stream (file_stream) and a fallback that is used after the first
stream has been consumed.
'''
def __init__(self, file_stream, fallback_stream):
self.file = iter(file_stream)
self.fallback = iter(fallback_stream)
self._file_alive = True
def __iter__(self):
return self
def __next__(self):
if self._file_alive:
try:
return next(self.file)
except StopIteration:
self._file_alive = False
return next(self.fallback)
class Pi:
@staticmethod
def _findFile():
'''
Find the .txt file that contains the most (precalculated) digits of Pi.
File names have the form [1-9][0-9]*.txt and the file name has to be the
number of digits.
'''
pattern = re.compile(r"^(\d+)\.txt$")
max_num = None
max_file = None
for filename in os.listdir("."):
match = pattern.match(filename)
if match:
num = int(match.group(1))
if max_num is None or num > max_num:
max_num = num
max_file = filename
return max_file, max_num # might be none
@staticmethod
def stream():
max_file, max_num = Pi._findFile()
if max_file is None:
# start with MpMath, if there is no file
return MpMathChunkFallback(start_offset=0)
else:
# combine pre-calculated digits and on-demand digits.
stream = FileIterator(max_file)
stream = SkipIterator(stream, 2)
fallback = MpMathChunkFallback(start_offset=max_num)
stream = FileThenFallback(stream, fallback)
return stream
if __name__ == "__main__":
stream = Pi.stream()
if len(sys.argv) < 2:
print("Please provide the number of digits.")
else:
print("3."+"".join([str(next(stream)) for _ in range(int(sys.argv[1])) ]))