Sequence without 1-on-1 in Crystal
This is the solution for the weekly challenge called Sequence without 1-on-1 in which we are asked to generate a sequence of numbers starting with 1 where each element can contain only the digits 1, 2, or 3 and there can't be 1 twice next to each other. That is "11" cannot be part of the decimal representation of the number.
This solution is written in the Crystal programming language.
Directory layout
. ├── spec │ └── seq_spec.cr └── src ├── list_seq.cr ├── seq.cr └── use_seq.cr
The implementation
examples/crystal/sequence-without-1-on-1/src/seq.cr
def seq(&block) val = 0 loop do val += 1 next if val.to_s =~ /[04-9]|11/ yield val end end def seq(n : Int32) : Int32 seq {|num| n -= 1 return num if n <= 0 } end
Crystal has multiple-dispatch so we can defined the same function with different signatures. Here we have two definitions of the seq function. The first one expects a block and will call the block on every iteration when the code reaches the yield statement. The function declaration contains &block but that's optional and I only included in the hope that it makes it clearer that the function expects a block of code.
Inside we have a condition-less loop do ... end which is basically an endless loop. (even though we do have the end word there ... I know, this was a bad pun)
Inside the loop we increment the value (there is no ++ in Crystal), then use a regular expression on the stringified (to_s) version of the number. =~ is the regex match operator just as in Perl. // delimiters the regex, just as in Perl. next is the same as in Perl (other languages have continue instead).
In Crystal an if can be written as the suffix of an expression and it becomes a statement modifier as it is called in Perl. The condition checked first and only if it true then next is called.
Then we have the yield num statement that will execute the block passing num as an argument. You can see this in use in the list_seq.cr file below and also in the second implementation of the seq function.
Here we are expecting an Int32 number and we also declare that we are going to return an Int32 number. Inside we call seq with a block (so this will execute the first implementation). We count down from N to 0 and thus return the Nth element of the sequence.
Use the sequence generator
examples/crystal/sequence-without-1-on-1/src/list_seq.cr
require "./seq" seq {|num| puts num break if num > 200 }
In this example we use the sequence generator directly. If we did not have some explicit statement to leave the block it would iterate till we run out of Int32 and then it would raise an exception: Unhandled exception: Arithmetic overflow (OverflowError)
break if num > 200 still first checks the condition and only if it is true then it will break out of the block.
examples/crystal/sequence-without-1-on-1/src/use_seq.cr
require "./seq" if ARGV.size != 1 abort("Usage: #{PROGRAM_NAME} NUM", 1) end puts seq(ARGV[0].to_i)
This is the example in which you can pass a number on the command line and it will print the Nth value in the sequence.
crystal src/use_seq.cr 60
Testing / Spec
This is the spec file. One could cd into the root of the "project" and type crystal spec to execute the tests.
examples/crystal/sequence-without-1-on-1/spec/seq_spec.cr
require "spec" require "../src/seq" describe "Seq" do it "verify" do seq(5).should eq(13) seq(10).should eq(32) seq(60).should eq(2223) end end
Published on 2021-07-02