シーケンスを生成できるライブラリをインターネットで検索すると、シーケンスは離散数学とコンピューター サイエンスの中核概念であるにもかかわらず、ほとんど見つかりません。
この短い記事では、 SeqGenと呼ばれるシーケンス生成用に私が作成したライブラリを見ていきます。
シーケンス (非公式に言うと) は、各要素の生成が先行する要素に基づいて行われる要素 (主に数値) のセットです。
最も基本的な例は、最初の要素が 0 で、次の要素が前の要素に 1 を加えたものである正の整数の単純な線形シーケンスです。したがって、最初の要素に 1 を加算することで 2 番目の要素を取得でき、3 番目の要素を取得できます。 2 番目の要素に 1 を追加するなどして要素を追加します。線形シーケンスは次のようになります: {0, 1, 2, 3, …, n}
。
より複雑な例は、最初の 2 つの要素が 0 と 1 で、次の要素が前の 2 つの要素の合計であるフィボナッチ数列です。フィボナッチ数列は次のようになります: {0, 1, 1, 2, 3, 5, 8, 13, 21, …, n}
上記のことから、シーケンスが 2 つのプロパティで定義されていることがわかります。
2.0_
依存関係:SeqGen ライブラリは Rust プログラミング言語で書かれています。フォローアップするには、 Rust をインストールする必要があります。
2.1_
プロジェクトの作成:SeqGen ライブラリを使用する新しいプロジェクトを作成しましょう。貨物を使ってそれを行うことができます。
$ cargo new --bin sequence && cd sequence
次に、ライブラリを依存関係としてプロジェクトに追加しましょう。
$ cargo add seqgen
これで、ライブラリを使用する準備が整いました。
SeqGen では、シーケンス作成プロセスは、「シーケンスとは」セクションで結論付けたシーケンスの 2 つのプロパティに直接マッピングされます。初期要素と次の要素を生成する関数 (SeqGen では遷移関数と呼ばれます) を定義する必要があります。
線形シーケンスを作成しましょう。
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new() .initial_elements(vec![0]) .transition_function(|alive_elements, current_element_index| { alive_elements.last_element().unwrap() + 1 }); }
Sequence
型はシーケンスを表す構造体です。この型に対して関連する関数new()
を呼び出すと、未定義の新しいインスタンスが取得されます。この未定義のインスタンスでは、メソッドを呼び出して定義できます。
最初のメソッドは、 initial_elements()
で、要素のベクトルを引数として受け取り、それをインスタンスの初期要素として設定します。
2 番目のメソッドは、 transition_function()
で、現在利用可能な要素から次の要素への遷移を表すクロージャを引数として受け取ります。
このクロージャは 2 つの引数にアクセスできます。1 つ目は現在利用可能な要素を表すalive_elements
で、2 つ目は生成中の現在の要素のインデックスであるcurrent_element_index
( usize
型) です。遷移関数の説明については、以下の表を参照してください。
生成中の現在の要素 | 生きている要素 | 現在の要素インデックス |
---|---|---|
| | |
| | |
| | |
| | |
alive_elements
はSequencePart
型です。この記事の後半でSequencePart
型について説明します。
要素のインデックスは線形シーケンス内の値でもあるため、上記の例を次のように単純化できます。
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new().transition_function(|_, i| i); }
ここでは、初期要素を定義する必要はなく、ライブ要素にアクセスする必要もありません。インデックス (この場合はi
という名前) が必要なだけで、それを返すだけです。
同様に、フィボナッチ数列を定義できます。
use seqgen::prelude::*; fn main() { let fib_seq = Sequence::new() .initial_elements(vec![0, 1_u128]) .transition_function(|alive_elements, i| { let x = alive_elements.nth_element(i - 1).unwrap(); let y = alive_elements.nth_element(i - 2).unwrap(); x + y }); }
フィボナッチ数列は指数関数的に増加するため、この定義で 187 を超える要素を生成すると、 u128
がオーバーフローします。より適切な定義では、 u128
の代わりに big int を使用します。
シーケンスを定義したら、その要素にアクセスできます。
use seqgen::prelude::*; fn main() { let mut linear_seq = Sequence::new().transition_function(|_, i| i); let some_element = linear_seq.nth_element(111); println!("{some_element}"); }
ここでは、要素への不変参照 (この場合は&usize
) を返すnth_element()
メソッドを使用して 111 番目の要素にアクセスしています。
linear_seq
を変更可能にしたことに注意してください。これは、 nth_element()
メソッドがシーケンスの生きている要素を変更するためです。
このようにして、シーケンスの任意の要素 (インデックス0
の要素からusize::MAX
のインデックスの要素まで) にアクセスできます。
Rust イテレータと同様にシーケンスを反復処理することもできます。
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new().transition_function(|_, i| i); linear_seq.for_each(|e| println!("{e}")); }
このコードは、シーケンスのすべての要素 (要素0
から要素usize::MAX
まで) を出力します。
$ cargo run -q 0 1 2 3 4 5 6 7 8 9 10 11 12 13 ...
次のコードを使用して、シーケンスから奇数の要素を取得できます。
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new().transition_function(|_, i| i); let odd_elements = linear_seq.filter(|e| e % 2 != 0); odd_elements.for_each(|e| println!("{e}")); }
出力:
$ cargo run -q 1 3 5 7 9 11 13 ...
私たちが定義するシーケンスは遅延的です。つまり、必要な場合 (反復の場合)、または明示的に要求された場合 ( nth_element()
メソッドを使用) がない限り、要素は生成されません。
場合によっては、シーケンスの一部のみを操作する必要がある場合があります。この場合はシーケンス部分があります。
シーケンス部分には次の 3 種類があります。
AliveElementsPart
ImmutableRangePart
MutableRangePart
AliveElementsPart:
シーケンス上でalive_elements()
メソッドを使用してライブ要素を取得できます。
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new() .transition_function(|_, i| i) .pre_generate(111); let alive_elements = linear_seq.alive_elements(); for alive_element in alive_elements { print!("{alive_element} "); } }
このコードは、生きているすべての要素 (この場合は 111 個の要素を事前に生成したため、0 ~ 110) を出力します。
不変範囲パート:
不変範囲は、生きている要素の範囲です。配列を変異させることはできません。不変範囲を作成し、そのすべての要素が有効ではない場合は、エラー ( DeadRange
エラー) が発生します。
Result
を返すrange()
メソッドを使用して、不変の範囲を作成できます。 Ok
バリアントはImmutableRangePart
で、 Err
バリアントはRangeError
です。 RangeError
InvalidRange
バリアント (範囲の始点が終点よりも大きい場合) である場合もあれば、 DeadRange
バリアント (範囲のすべての要素が有効ではない場合) である場合もあります。
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new().transition_function(|_, i| i); let range = linear_seq.range(0, 3).unwrap(); for e in range { println!("{e}") } }
生きている要素がないため、このコードはDeadRange
エラーでパニックになります。これは次のように修正できます。
use seqgen::prelude::*; fn main() { let mut linear_seq = Sequence::new().transition_function(|_, i| i); linear_seq.generate(3); let range = linear_seq.range(0, 3).unwrap(); for e in range { println!("{e}") } }
ここでは、範囲を有効にするために 3 つの要素を生成しました。
MutableRangePart:
変更可能な範囲では、シーケンスを変更する (要素を生成する) ことができます。
次のように可変範囲を使用できます。
use seqgen::prelude::*; fn main() { let mut linear_seq = Sequence::new().transition_function(|_, i| i); let mut_range = linear_seq.range_mut(0, 111).unwrap(); for e in mut_range { println!("{e}"); } }
このコードは 0 から 110 までの要素を出力します。
この記事を最後まで読んでいただきありがとうございます。何かお役に立てば幸いです。このライブラリを改善できる提案がある場合は、 GitHub で問題を開いてください。また、ライブラリに貢献したい場合は、それは素晴らしいことです。
ここでも公開されています