Se você pesquisar na Internet por uma biblioteca que possa gerar sequências, dificilmente encontrará uma, embora as sequências sejam um conceito central da matemática discreta e da ciência da computação.
Neste breve artigo, daremos uma olhada em uma biblioteca que escrevi para a geração de sequência chamada SeqGen .
Uma sequência (falando informalmente) é um conjunto de elementos (principalmente números) onde a produção de cada elemento é baseada no(s) elemento(s) anterior(es).
O exemplo mais básico é uma sequência linear simples de inteiros positivos onde o primeiro elemento é 0 e o próximo elemento é o elemento anterior mais um, então podemos obter o segundo elemento adicionando um ao primeiro, e podemos obter o terceiro elemento adicionando um ao segundo e assim por diante. A sequência linear ficaria assim: {0, 1, 2, 3, …, n}
.
Um exemplo mais complexo poderia ser a sequência de Fibonacci, onde os dois primeiros elementos são 0 e 1, e o próximo elemento é a soma dos dois elementos anteriores. A sequência de Fibonacci ficaria assim: {0, 1, 1, 2, 3, 5, 8, 13, 21, …, n}
Podemos notar acima que uma sequência é definida com duas propriedades:
2.0_
Dependências:A biblioteca SeqGen é escrita na linguagem de programação Rust; para acompanhar, você precisa ter o Rust instalado .
2.1_
Criando um Projeto:Vamos criar um novo projeto para utilizar a biblioteca SeqGen; podemos fazer isso com carga:
$ cargo new --bin sequence && cd sequence
Agora, vamos adicionar a biblioteca como dependência ao nosso projeto:
$ cargo add seqgen
Agora estamos prontos para usar a biblioteca.
No SeqGen, o processo de criação de sequência é mapeado diretamente para as duas propriedades da sequência que concluímos na seção O que é uma sequência; temos que definir os elementos iniciais e a função que produz o próximo elemento (chamada de função de transição no SeqGen).
Vamos criar a sequência linear:
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 }); }
O tipo Sequence
é uma estrutura que representa uma sequência; chamando a função associada new()
neste tipo, obtemos uma nova instância que é indefinida. Nesta instância indefinida, podemos chamar métodos para defini-la.
O primeiro método é initial_elements()
que aceita um vetor de elementos como argumento e o define como os elementos iniciais da instância.
O segundo método é transition_function()
que toma como argumento um encerramento que representa a transição dos elementos atualmente disponíveis para o próximo elemento.
Este encerramento tem acesso a dois argumentos: o primeiro é alive_elements
que representa os elementos atualmente disponíveis, e o segundo é current_element_index
(do tipo usize
) que é o índice do elemento atual em geração. Veja a tabela abaixo para uma ilustração da função de transição.
Elemento Atual na Geração | elementos_vivos | índice_elemento_atual |
---|---|---|
| | |
| | |
| | |
| | |
alive_elements
é do tipo SequencePart
, daremos uma olhada no tipo SequencePart
posteriormente neste artigo
Como o índice do elemento também é o seu valor na sequência linear, podemos simplificar o exemplo acima para:
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new().transition_function(|_, i| i); }
Aqui, não precisamos definir os elementos iniciais e não precisamos de acesso aos elementos ativos; precisamos apenas do índice (que é denominado i
neste caso) e simplesmente o retornamos.
Da mesma forma, podemos definir a sequência de Fibonacci:
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 }); }
Como a sequência de Fibonacci cresce exponencialmente, gerar mais de 187 elementos com esta definição fará com que u128
estoure. Uma definição melhor usaria big int em vez de u128
.
Após definirmos nossa sequência, podemos acessar seus elementos:
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}"); }
Aqui, estamos acessando o 111º elemento usando o método nth_element()
que retorna uma referência imutável ao elemento ( &usize
neste caso).
Observe que tornamos linear_seq
mutável. Isso ocorre porque o método nth_element()
irá transformar os elementos vivos da sequência.
Desta forma, podemos acessar qualquer elemento da sequência (desde o elemento com índice 0
até o elemento com índice usize::MAX
.)
Também podemos iterar sobre a sequência como qualquer iterador Rust:
use seqgen::prelude::*; fn main() { let linear_seq = Sequence::new().transition_function(|_, i| i); linear_seq.for_each(|e| println!("{e}")); }
Este código irá imprimir todos os elementos da sequência (do elemento 0
ao elemento usize::MAX
):
$ cargo run -q 0 1 2 3 4 5 6 7 8 9 10 11 12 13 ...
Podemos obter os elementos ímpares da sequência usando o seguinte código:
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}")); }
Saída:
$ cargo run -q 1 3 5 7 9 11 13 ...
A sequência que definimos é lenta, o que significa que os elementos não serão gerados a menos que sejam necessários (em caso de iteração) ou solicitados explicitamente (usando o método nth_element()
).
Às vezes precisamos trabalhar apenas com partes de uma sequência, neste caso temos partes de sequência.
Existem três tipos de parte de sequência:
AliveElementsPart
ImmutableRangePart
MutableRangePart
AliveElementsParte:
Podemos obter os elementos vivos usando o método alive_elements()
na sequência:
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} "); } }
Este código imprimirá todos os elementos vivos (0 a 110 neste caso porque pré-geramos 111 elementos).
FaixaImutávelParte:
As faixas imutáveis são as faixas dos elementos vivos; eles não podem alterar a sequência. Se você criar um intervalo imutável e nem todos os seus elementos estiverem ativos, você receberá um erro (erro DeadRange
).
Podemos criar um intervalo imutável usando o método range()
que retorna um Result
. A variante Ok
é ImmutableRangePart
e a variante Err
é RangeError
. O RangeError
pode ser a variante InvalidRange
(caso o início do intervalo seja maior que o final) ou pode ser a variante DeadRange
(caso nem todos os elementos do intervalo estejam ativos):
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}") } }
Este código entrará em pânico com o erro DeadRange
porque não há nenhum elemento ativo. Podemos corrigir isso com o seguinte:
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}") } }
Aqui, geramos 3 elementos para tornar o intervalo válido.
MutableRangePart:
Um intervalo mutável pode alterar a sequência (gerar elementos).
Podemos usar um intervalo mutável assim:
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}"); } }
Este código imprimirá os elementos de 0 a 110.
Obrigado por ler este artigo até o fim e espero que você tenha encontrado algo útil nele. Se você tiver alguma sugestão que possa melhorar esta biblioteca, abra um issue no GitHub , e se quiser contribuir com a biblioteca , isso seria maravilhoso.
Também publicado aqui