OEIS Demo
Copy MarkdownMix.install([
{:oeis, "~> 0.7.1"},
{:kino, "~> 0.14.0"},
{:kino_vega_lite, "~> 0.1.13"}
])Introduction
This notebook serves as an interactive guide to the oeis Elixir library. We will explore how to search the On-Line Encyclopedia of Integer Sequences (OEIS), retrieve detailed data, and visualize the results.
Basic Search
The primary entry point is the OEIS.search/2 function. It is designed to be flexible, accepting sequence IDs, lists of integers, or keyword queries.
Searching by ID
We will start by examining Recamán's sequence (A005132). This sequence is known for its non-monotonic, "hopping" behavior, which makes it particularly interesting to analyze.
recaman_seq =
"A005132"
|> OEIS.search()
|> then(fn {:single, seq} -> seq end)
|> tap(fn seq -> IO.puts("Found: #{seq.name}") end)
recaman_seq
|> Enum.with_index()
|> Enum.map(fn {val, idx} -> %{index: idx, value: val} end)
|> Kino.DataTable.new()Note: The
OEIS.Sequencestruct implements theEnumerableprotocol. This means you can pipe it directly into functions likeEnum.with_index/1, treating the sequence object as if it were a simple list of its terms.
Searching by Sequence
If you have a list of numbers and want to identify the sequence, you can pass the list directly to the search function.
# Searching for Catalan numbers: 1, 1, 2, 5, 14
[1, 1, 2, 5, 14]
|> OEIS.search()
|> elem(1)
|> List.first()Searching by Keyword/Author
You can also filter results by author or keyword. For instance, here is how to find sequences authored by Neil Sloane that are tagged with the "core" keyword.
[author: "Sloane", keyword: "core"]
|> OEIS.search()
|> then(fn {_status, search_results} -> search_results end)
|> Enum.map(fn seq ->
%{id: seq.id, name: seq.name, author: seq.author}
end)
|> Kino.DataTable.new()Streaming Results
For queries that may return a large number of results, the library provides a streaming interface. By passing stream: true, OEIS.search/2 returns a lazy Stream that fetches results in pages of 10 as you consume them.
This is particularly useful for building UIs that support infinite scrolling or for processing large numbers of sequences without loading them all into memory at once.
# Lazily fetch the first 25 sequences related to "fib"
"fib"
|> OEIS.search(stream: true)
|> Stream.take(25)
|> Enum.map(fn seq -> %{id: seq.id, name: seq.name} end)
|> Kino.DataTable.new()Fetching More Data
Search results usually provide just a glimpse of the sequence—typically the first few dozen terms. To truly understand the behavior of a sequence like Recamán's, we need a larger dataset.
We can use OEIS.fetch_more_terms/2 to retrieve the full "b-file" from the OEIS, which often contains thousands of terms. This allows us to see the bigger picture.
expanded_seq =
recaman_seq
|> tap(fn seq -> IO.puts("Original count: #{Enum.count(seq)}") end)
|> OEIS.fetch_more_terms()
|> then(fn {:ok, seq} -> seq end)
|> tap(fn seq -> IO.puts("New count: #{Enum.count(seq)}") end)Now, let's visualize this data. By plotting the sequence, we can move beyond abstract numbers and observe the beautiful, chaotic structure that emerges to clearly see the individual steps.
expanded_seq
|> Enum.with_index()
|> Enum.map(fn {y, x} -> %{"x" => x, "y" => y} end)
|> then(fn data_points ->
VegaLite.new(width: 800, height: 400)
|> VegaLite.data_from_values(data_points, only: ["x", "y"])
|> VegaLite.mark(:line, point: true, tooltip: true)
|> VegaLite.encode_field(:x, "x", type: :quantitative, title: "Index")
|> VegaLite.encode_field(:y, "y", type: :quantitative, title: "Value")
end)
|> Kino.VegaLite.new()Fetching Related Sequences
Mathematics is all about connections. Sequences in the OEIS often reference other related sequences. For example, Recamán's sequence might be related to other recurrence relations or number theoretic properties.
The OEIS.fetch_xrefs/2 function parses these cross-references and fetches their definitions for you, automatically handling the network requests in parallel.
# Fetch sequences related to Recamán's sequence (A005132)
# We use the recaman_seq variable from earlier
related_sequences = OEIS.fetch_xrefs(recaman_seq, max_concurrency: 5)
related_sequences
|> Enum.map(fn seq -> %{id: seq.id, name: seq.name} end)
|> Kino.DataTable.new()For sequences with a large number of cross-references, you can also use stream: true to process them lazily.
# Lazily fetch the first 5 sequences related to Fibonacci (A000045)
"A000045"
|> OEIS.search()
|> then(fn {:single, seq} -> seq end)
|> OEIS.fetch_xrefs(stream: true)
|> Stream.take(5)
|> Enum.map(fn seq -> %{id: seq.id, name: seq.name} end)
|> Kino.DataTable.new()Standardized Options
To give you precise control over your queries, most functions in this library support a consistent set of options. Here is how you can tune the behavior:
:timeout- The maximum time (in milliseconds) to wait for a response from the OEIS server. Default is 15,000ms.:max_concurrency- When fetching multiple items (like related sequences), this limits how many requests happen at once. Default is 5.:may_truncate- If set totrue(the default), the library will truncate the provided terms and remove leading 0s or 1s. This often increases the chances of finding a match when you only have a partial or slightly different sequence of terms.:respect_sign- When searching by a list of numbers, this determines if negative signs matter. Default istrue.:start- The zero-based index of the first result to return. Useful for paging through many results. Default is 0.:stream- Iftrue, returns an Elixir Stream that lazily fetches and emits results. Default isfalse.
Interactive Search Tool
Now it is your turn to explore. Use the form below to perform your own searches. You can try:
- An ID: "A000032" (Lucas numbers)
- A name: "Lucas numbers"
- A list of terms: "1, 2, 3, 6, 11, 23"
Experiment with different queries to see what you can discover!
input = Kino.Input.text("Search Query")
form = Kino.Control.form([query: input], submit: "Search")
frame = Kino.Frame.new()
Kino.listen(form, fn %{data: %{query: query}} ->
Kino.Frame.render(frame, Kino.Text.new("Searching..."))
# Determine if input is a list of numbers or a string
search_arg =
if Regex.match?(~r/^[\d\s,]+$/, query) do
query
|> String.split([",", " "], trim: true)
|> Enum.map(&String.to_integer/1)
else
query
end
search_arg
|> OEIS.search()
|> case do
{:single, seq} ->
[
{"Info", Kino.Tree.new(seq)},
{"Data", seq |> Enum.join(", ") |> Kino.Text.new()}
]
|> Kino.Layout.tabs()
{status, seqs} when status in [:multi, :partial] ->
seqs
|> Enum.map(&%{id: &1.id, name: &1.name})
|> Kino.DataTable.new()
{:no_match, msg} ->
Kino.Text.new(msg)
error ->
"Error: #{inspect(error)}" |> Kino.Text.new()
end
|> then(&Kino.Frame.render(frame, &1))
end)
Kino.Layout.grid([form, frame], columns: 1)