-
Notifications
You must be signed in to change notification settings - Fork 0
/
oeis.py
318 lines (232 loc) · 9.68 KB
/
oeis.py
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
py-oeis
A Python library to access the OEIS (Online Encyclopedia of Integer Sequences).
Sumant Bhaskaruni
v2.1
"""
__version__ = '2.1'
from collections import Iterable
import pendulum
import requests
class NonExistentSequence(LookupError):
"""Raised when a sequence with the ID passed does not exist."""
class NegativeIndexError(IndexError):
"""Raised when a negative index is passed."""
class IndexTooHighError(IndexError):
"""Raised when an index too high to handle is passed."""
class EmptyQuery(ValueError):
"""Raised when an empty list of query terms is passed."""
class Sequence(object):
"""An object to represent a single OEIS sequence.
Initializer arguments:
seq_id (int): the OEIS sequence ID
Instance attributes:
seq_id (int): the OEIS sequence ID
sequence (list): the first few values of the sequence
info (dict): information pertaining to the sequence including:
name (str): the name of the sequence
formula (str): the text of the 'Formula' section on OEIS
comments (str): the text of the 'Comments' section on OEIS
example (str): the text of the 'Example' section on OEIS
crossrefs (str): the text of the 'Crossrefs' section on OEIS
keywords (list): the keywords (tags) of the sequence
author (str): the author of the sequence
created (float): when the sequence was created (epoch timestamp)
url (str): the URL of the sequence on OEIS
"""
def __init__(self, seq_id):
"""See class docstring for details."""
info = requests.get('https://oeis.org/search?fmt=json&q=id:A{:d}'.
format(seq_id)).json()['results']
if info is None:
raise NonExistentSequence(
'Sequence with the ID {:d} was not found.'.format(seq_id))
else:
info = info[0]
self.seq_id = info['number']
self.sequence = list(map(int, info['data'].split(',')))
self.info = {
'name':
info['name'],
'formula':
'\n'.join(info.get('formula', '')),
'comments':
'\n'.join(info.get('comment', '')),
'example':
'\n'.join(info.get('example', '')),
'crossrefs':
'\n'.join(info.get('xref', '')),
'keywords':
info.get('keyword', '').split(','),
'author':
info.get('author', '').replace('_', ''),
'created':
pendulum.parse(
info['created']).astimezone('utc').timestamp(),
'url':
'https://oeis.org/A{:06d}'.format(info['number'])
}
def __contains__(self, item):
return self.contains(item)
def __getitem__(self, key):
if isinstance(key, int):
return self.nth_term(key)
elif isinstance(key, Iterable):
return self.index(key)
elif isinstance(key, slice):
return self.subsequence(key.start, key.stop, key.step)
def __iter__(self):
for value in self.sequence:
yield value
def __len__(self):
return len(self.sequence)
def __eq__(self, other):
if not isinstance(other, Sequence):
return False
return self.seq_id == other.seq_id
def __str__(self):
return '<Sequence [A{0:06d}: {1:s}]>'.format(self.seq_id,
self.info['name'])
def __repr__(self):
return '<Sequence [A{0:06d}: {1:s}]>'.format(self.seq_id,
self.info['name'])
def fetch_sequence(self):
"""Fetch all the values of the sequence from OEIS. Only do this if you
want a *lot* of values."""
seq_page = requests.get('https://oeis.org/A{0:d}/b{0:06d}.txt'.format(
self.seq_id)).text.rstrip('\n').split('\n')
seq_page = filter(lambda x: not x.startswith('#'), seq_page)
self.sequence = [int(item.split()[1]) for item in seq_page]
def contains(self, item):
"""Check if the sequence contains the specified item. Note that this
has the limit of the amount of numbers that OEIS holds.
Positional arguments:
item (int): the item to check for
Returns whether the sequence contains item.
"""
return item in self.sequence
def find(self, item, instances = None):
"""Find specified number of instances of the specified item. Note that
this has the limit of the amount of numbers that OEIS hols.
Positional arguments:
item (int): the item to find
instances (int): the number of instances of item to find
(default: 1)
Returns a list of sequence indices.
"""
if instances is None:
instances = 1
result = []
for index, value in enumerate(self.sequence):
if len(result) == instances:
return result
if value == item:
result.append(index)
return result
def next(self, item):
"""Find the number that comes after the specified number in the
sequence.
Positional arguments:
item (int): the number to find the number after
Returns the number after item in the sequence.
"""
return self.sequence[self.find(item) + 1]
def prev(self, item):
"""Find the number that comes before the specified number in the
sequence.
Positional arguments:
item (int): the number to find the number before
Returns the number before item in the sequence.
"""
return self.sequence[self.find(item) - 1]
def nth_term(self, index):
"""Get the nth element of the sequence. 0-indexed. Raises an exception
if the index is negative or too high.
Positional arguments:
index (int): the index of the element
Returns the element of the sequence at index.
"""
if index < 0:
# sequences don't have negative indices, ya silly goofball
raise NegativeIndexError(
'The index passed ({:d}) is negative.'.format(index))
if index >= len(self):
# OEIS only holds values up to a certain limit
raise IndexTooHighError('{0:d} is higher than the amount of '
'values fetched ({1:d}).'.format(
index, len(self)))
return self.sequence[index]
def index(self, index):
"""Get the nth term of the sequence for every n in index. 0-indexed.
Positional arguments:
index (list): the list of integers to index
Returns a list of sequence elements.
"""
return [self.nth_term(i) for i in index]
def subsequence(self, start = None, stop = None, step = None):
"""Get a subsequence of the sequence. 0-indexed. Raises an exception if
either of the indices are negative or too high.
Positional arguments:
start (int): the starting index of the subsequence (default: 0)
stop (int): the ending index of the subsequence
(default: len(sequence))
step (int): the amount by which the index increases (default: 1)
Returns a list of sequence items.
"""
if start is None:
start = 0
if stop is None:
stop = len(self)
if step is None:
step = 1
if start < 0:
raise NegativeIndexError(
'The index passed ({:d}) is negative.'.format(start))
if stop < 0:
raise NegativeIndexError(
'The index passed ({:d}) is negative.'.format(stop))
if start > len(self):
raise IndexTooHighError('{0:d} is higher than the amount of '
'values fetched ({1:d}).'.format(
start, len(self)))
if stop > len(self):
raise IndexTooHighError('{0:d} is higher than the amount of '
'values fetched ({1:d}).'.format(
stop, len(self)))
return self.sequence[start:stop:step]
def first(self, items):
"""Get the first n terms of the sequence. Raises an exception if n is
negative or too high.
Positional arguments:
items (int): the amount of terms to return
Returns a list of sequence items.
"""
return self.subsequence(start = 0, stop = items)
def query(terms, start = None, results = None):
"""Query the OEIS for sequences that match the terms.
See https://oeis.org/hints.html for more information.
Positional arguments:
terms (list): the terms to search for
start (int): how far down the list of results to return sequences from
(default: 0)
results (int): how many sequences to return (default: 10)
Returns a list of Sequence objects.
"""
if not terms:
raise EmptyQuery('List of query terms passed is empty.')
if start is None:
start = 0
if results is None:
results = 10
terms = requests.utils.quote(' '.join(terms))
result = []
search = requests.get(
'https://oeis.org/search?fmt=json&q={0:s}&start={1:d}'.format(
terms, start)).json()['results']
if search is None:
return []
for seq in search[:results]: # search results :P
result.append(Sequence(seq['number']))
return result