Ruby Quiz - Challenge #7 - Type Inference - Convert Strings to Null, Number, Not a Number (NaN), Date & More


(Gerald Bauer) #1

Hello,

  It's Friday. Ruby Quiz time! Join us. Let's keep going with a new
Ruby Quiz [1] every Friday. Here we go:

Challenge #7 - Type Inference - Convert Strings to Null, Number, Not a
Number (NaN), Date & More

Type inference is the new hotness.
Let's (auto)-convert a list of strings into properly typed values.

The challenge: Code a `convert` method that passes the RubyQuizTest :slight_smile: [2].

    def convert( values )
      # ...
    end

For the starter level 1 turn a random list of strings:

    ["2018", "2018.12", "2018.12.25", "25 Gems", "NaN", "1820",
"18.20", "Nil", "Null"]

into a properly typed list of values:

    [2018, 2018.12, Date.new(2018,12,25), "25 Gems", Float::NAN, 1820,
18.2, nil, nil]
    # => [2018, 2018.12, #<Date: 2018-12-25>, "25 Gems", NaN, 1820,
18.2, nil, nil]

Note: `NaN` is short for Not a Number (that is, `Float::NAN`).
To pass the RubyQuizTest all type classes must match too, that is:

    [Integer, Float, Date, String, Float, Integer, Float, NilClass, NilClass]

To make sure the order in the list doesn't matter, that is, integer
before float numbers
or float before integer numbers or date before integer numbers
and so on -
convert gets called five times
with a shuffled sample with a different "randomized" order every time.

Start from scratch or, yes, use any library / gem you can find.

To qualify for solving the code challenge / puzzle you must pass the test:

require 'minitest/autorun'
require 'date'

class RubyQuizTest < MiniTest::Test

  def test_convert

    pairs = [
      ["2018",       2018],
      ["2018.12",    2018.12],
      ["2018.12.25", Date.new(2018,12,25)],
      ["25 Gems",    "25 Gems"],
      ["NaN",        Float::NAN],
      ["1820",       1820],
      ["18.20",      18.2],
      ["Nil",        nil],
      ["Null",       nil]]

    5.times do
      values, exp_values = pairs.shuffle.reduce( [[],[]]) { |acc,pair|
acc[0] << pair[0]; acc[1] << pair[1]; acc }

      assert_equal exp_values, convert( values )
      assert_equal exp_values.map { |v| v.class }, convert( values
).map { |v| v.class }
    end

  end # method test_convert
end # class RubyQuizTest

Post your code snippets on the "official" Ruby Quiz Channel,
that is, the ruby-talk mailing list right here.

Happy data wrangling and type inferencing with Ruby.

[1] https://github.com/planetruby/quiz/tree/master/007
[2] https://github.com/planetruby/quiz/blob/master/007/test.rb


(Frank J. Cameron) #2

Gerald Bauer wrote:

Challenge #7 - Type Inference - Convert Strings to Null, Number, Not a
Number (NaN), Date & More

$ ruby lib/007.rb
Run options: --seed 42919
# Running:
.
Finished in 0.015037s, 66.5023 runs/s, 665.0233 assertions/s.
1 runs, 10 assertions, 0 failures, 0 errors, 0 skips

$ cat lib/007.rb
require_relative '../007/test.rb'

gem 'parslet', '~> 1.8.2'
require 'parslet'

class RubyQuizTest
  @@parser = Class.new(Parslet::Parser) do
    rule(:dte) {
                 (
                   match('[0-9]').repeat(4)>>(
                     match('[.]')>>match('[0-9]').repeat(2)
                   ).repeat(2)
                 ).as(:dte)
               }
    rule(:flt) {
                 (
                   match('[0-9]').repeat>>match('\.')>>match('[0-9]').repeat
                 ).as(:flt)
               }
    rule(:int) {
                 match('[0-9]').repeat.as(:int)
               }
    rule(:nan) {
                 (
                   match('N')>>match('a')>>match('N')
                 ).as(:nan)
               }
    rule(:nul) {
                 (
                   match('N')>>match('i')>>match('l') |
                   match('N')>>match('u')>>match('l')>>match('l')
                 ).as(:nul)
               }
    rule(:str) {
                 match('.').repeat.as(:str)
               }
    rule(:foo) { nul | nan | dte | flt | int | str }
    root(:foo)
  end.new

  @@transf = Class.new(Parslet::Transform) do
    rule(:dte => simple(:dte)) { Date.strptime(dte, '%Y.%m.%d') }
    rule(:flt => simple(:flt)) { flt.to_f }
    rule(:int => simple(:int)) { int.to_i }
    rule(:nan => simple(:nan)) { Float::NAN }
    rule(:nul => simple(:nan)) { nil }
    rule(:str => simple(:str)) { str.to_s }
  end.new

  def convert(values)
    values.map{|v|
      @@transf.apply(@@parser.parse(v))
    }
  end
end

RubyQuizTest.new('fjc')