paint-brush
SeqGen: シーケンス生成用に作成したライブラリ@crazyrat
502 測定値
502 測定値

SeqGen: シーケンス生成用に作成したライブラリ

crazyrat7m2023/12/15
Read on Terminal Reader

長すぎる; 読むには

シーケンス (非公式に言うと) は、各要素の生成が先行する要素に基づいて行われる要素 (主に数値) のセットです。 最も基本的な例は、最初の要素が 0 で、次の要素が前の要素に 1 を加えたものである正の整数の単純な線形シーケンスです。したがって、最初の要素に 1 を加算することで 2 番目の要素を取得でき、3 番目の要素を取得できます。 2 番目の要素に 1 を追加するなどして要素を追加します。線形シーケンスは次のようになります: {0, 1, 2, 3, …, n}。
featured image - SeqGen: シーケンス生成用に作成したライブラリ
crazyrat HackerNoon profile picture

導入:

シーケンスを生成できるライブラリをインターネットで検索すると、シーケンスは離散数学とコンピューター サイエンスの中核概念であるにもかかわらず、ほとんど見つかりません。


この短い記事では、 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型) です。遷移関数の説明については、以下の表を参照してください。


生成中の現在の要素

生きている要素

現在の要素インデックス

1

[0]

1

2

[0, 1]

2

3

[0, 1, 2]

3

n

[0, 1, 2, 3, …, n]

n


alive_elementsSequencePart型です。この記事の後半で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 で問題を開いてください。また、ライブラリに貢献したい場合は、それは素晴らしいことです。


ここでも公開されています