[SOLUTION] Ruby Quiz #14 LCD Numbers

Here is my solution:

#!/usr/bin/env ruby
# Program : Ruby Quiz #14 LCD Numbers
(http://www.grayproductions.net/ruby_quiz/quiz14.html)
# Author : David Tran
# Date : 2005-01-07

class LCD
  DEFAULT_SIZE = 2
  LCD_CODES = [ # code for digits's each horizontal line (for size == 1)
    [:h1, :v3, :h0, :v3, :h1], #0
    [:h0, :v1, :h0, :v1, :h0], #1
    [:h1, :v1, :h1, :v2, :h1], #2
    [:h1, :v1, :h1, :v1, :h1], #3
    [:h0, :v3, :h1, :v1, :h0], #4
    [:h1, :v2, :h1, :v1, :h1], #5
    [:h1, :v2, :h1, :v3, :h1], #6
    [:h1, :v1, :h0, :v1, :h0], #7
    [:h1, :v3, :h1, :v3, :h1], #8
    [:h1, :v3, :h1, :v1, :h1], #9
  ]

  def initialize(number, size)
    @number = number.to_s.split(//).collect { |c| c.to_i }
    @size = (size || DEFAULT_SIZE).to_i
    @size = DEFAULT_SIZE if @size <= 0
    @gap = ' ' # gap between each digit

    line_codes = { # For size == 1
      :h0 => ' ' + ' ' * @size + ' ', # h0 = " "
      :h1 => ' ' + '-' * @size + ' ', # h1 = " - "
      :v0 => ' ' + ' ' * @size + ' ', # v0 = " " (same as h0)
      :v1 => ' ' + ' ' * @size + '|', # v1 = " |"
      :v2 => '|' + ' ' * @size + ' ', # v2 = "| "
      :v3 => '|' + ' ' * @size + '|', # v3 = "| |"
    }

    @lines = []
    (0..4).each { |line| @lines << @number.inject('') { |s, d| s +=
line_codes[LCD_CODES[d][line]] + @gap } }
  end

  def each_line
    return unless block_given?
    last_line = (@size + 1) * 2
    middle_line = last_line / 2
    (0..last_line).each do |line|
      index = case line
              when 0: 0
              when 1...middle_line: 1
              when middle_line: 2
              when last_line: 4
              else 3
              end
      yield @lines[index]
    end
  end

end

key, size = ARGV.slice!(ARGV.index('-s'), 2) if ARGV.include?('-s')
raise "Usage: #$0 [-s size] number" if ARGV.empty?
LCD.new(ARGV.first, size).each_line { |line| puts line }

···

=========================
~~~ http://www.doublegifts.com ~~~

email55555 email55555 wrote:

Here is my solution:

#!/usr/bin/env ruby
# Program : Ruby Quiz #14 LCD Numbers
(Gray Soft / Not Found)
# Author : David Tran
# Date : 2005-01-07 [cut]

David a few things that I have noticed while running the program.
It's missing a # in front of (http://...) but this is trivial of course :-).

If I launch the program without arguments I get:

../lcd.rb:61: Usage: ./lcd.rb [-s size] number (RuntimeError)

The (RuntimeError) shouldn't be there. Furthermore if you pass a string like 123CIAO4 it will output 12300004. You can check more strictly for the input given to the program and print something like "1234CIAO4 is not a valid number" or as you wish.

HTH
Cheers,
Antonio

···

--
My programming blog: http://www.antoniocangiano.com

My own solution. It looks a long but it's mostly the digits, arguments and usage.

James Edward Gray II

#!/usr/bin/env ruby

DIGITS = [
  [ " - ",
    "| |",
    " ",
    "| |",
    " - " ],
  [ " ",
    " |",
    " ",
    " |",
    " " ],
  [ " - ",
    " |",
    " - ",
    "| ",
    " - " ],
  [ " - ",
    " |",
    " - ",
    " |",
    " - " ],
  [ " ",
    "| |",
    " - ",
    " |",
    " " ],
  [ " - ",
    "| ",
    " - ",
    " |",
    " - " ],
  [ " - ",
    "| ",
    " - ",
    "| |",
    " - " ],
  [ " - ",
    " |",
    " ",
    " |",
    " " ],
  [ " - ",
    "| |",
    " - ",
    "| |",
    " - " ],
  [ " - ",
    "| |",
    " - ",
    " |",
    " - " ]
]

def scale( num, size )
  bigger = [ ]
  num.each do |l|
    row = l.dup
    row[1, 1] = row[1, 1] * size
    if row =~ /\|/
      size.times { bigger << row }
    else
      bigger << row
    end
  end
  bigger
end

s = 2
if ARGV.size >= 2 and ARGV[0] == '-s' and ARGV[1] =~ /^[1-9]\d*$/
  ARGV.shift
  s = ARGV.shift.to_i
end

unless ARGV.size == 1 and ARGV[0] =~ /^\d+$/
  puts "Usage: #$0 [-s SIZE] DIGITS"
  exit
end
n = ARGV.shift

num = [ ]
n.each_byte do |c|
  num << [" "] * (s * 2 + 3) if num.size > 0
  num << scale(DIGITS[c.chr.to_i], s)
end

num = ([""] * (s * 2 + 3)).zip(*num)
num.each { |l| puts l.join }

__END__

Here's my solution: http://www.io.com/~jimm/rubyquiz/quiz14/

Jim

···

--
Jim Menard, jimm@io.com, http://www.io.com/~jimm/
"If at first you don't succeed, don't go skydiving." -- Unknown

# lcd.rb
require 'optparse'

opts = OptionParser.new
size = 2 # default
opts.on("-s","--size VAL", Integer){|val| size=val}
digit_entry = opts.parse(*ARGV).first

raise "size must be greater than zero (given #{size})" if size <= 0
raise "'#{digit_entry}' contains non-digit characters" if digit_entry =~ /\D/

# to hold LCD format information
LCD = Struct.new(:upper_crossbar, :upper_uprights,
                 :middle_crossbar,:lower_uprights,:lower_crossbar)

# lcd segment formats
nothing = " #{' '*size} "
crossbar = " #{'-'*size} "
leftpost = "|#{' '*size} "
rightpost = " #{' '*size}|"
uprights = "|#{' '*size}|"

# digits in LCD format
LCDs = {'0'=>LCD[crossbar,uprights,nothing,uprights,crossbar],
        '1'=>LCD[nothing,rightpost,nothing,rightpost,nothing],
        '2'=>LCD[crossbar,rightpost,crossbar,leftpost,crossbar],
        '3'=>LCD[crossbar,rightpost,crossbar,rightpost,crossbar],
        '4'=>LCD[nothing,uprights,crossbar,rightpost,nothing],
        '5'=>LCD[crossbar,leftpost,crossbar,rightpost,crossbar],
        '6'=>LCD[crossbar,leftpost,crossbar,uprights,crossbar],
        '7'=>LCD[crossbar,rightpost,nothing,rightpost,nothing],
        '8'=>LCD[crossbar,uprights,crossbar,uprights,crossbar],
        '9'=>LCD[crossbar,uprights,crossbar,rightpost,crossbar]}

# simulate LCD panel display
digits = digit_entry.split(//).collect{|d| LCDs[d]}
LCD.members.each_with_index do |segment_name, index|
    panel_segment = digits.collect{|lcd| lcd[segment_name]}.join(' ')
    repeat = index%2==0 ? 1 : size # only repeat 'upright' segments
    repeat.times{puts panel_segment}
end

You can see my solution at http://www.dave.burt.id.au/ruby/lcd_digits.rb

I used two functions, one to turn something like '9' into a big ASCII-art
digital-looking 9, and one like UNIX's paste command to stick them next to
each other.

Then like this:

number = ARGV[0].scan(/\d/)
puts paste(number.map {|digit| lcd_digit(digit, size) })

Cheers,
Dave

require 'getoptlong'

class LCD
    attr_accessor( :size, :spacing )

···

#
    # This hash is used to define the segment display for the
    # given digit. Each entry in the array is associated with
    # the following states:
    #
    # HORIZONTAL
    # VERTICAL
    # HORIZONTAL
    # VERTICAL
    # HORIZONTAL
    # DONE
    # # The HORIZONTAL state produces a single horizontal line. There
    # are two types:
    #
    # 0 - skip, no line necessary, just space fill
    # 1 - line required of given size
    #
    # The VERTICAL state produces a either a single right side line,
    # a single left side line or a both lines.
    #
    # 0 - skip, no line necessary, just space fill
    # 1 - single right side line
    # 2 - single left side line
    # 3 - both lines
    #
    # The DONE state terminates the state machine. This is not needed
    # as part of the data array.
    #
    @@lcdDisplayData = {
        "0" => [ 1, 3, 0, 3, 1 ], "1" => [ 0, 1, 0, 1, 0 ],
        "2" => [ 1, 1, 1, 2, 1 ],
        "3" => [ 1, 1, 1, 1, 1 ],
        "4" => [ 0, 3, 1, 1, 0 ],
        "5" => [ 1, 2, 1, 1, 1 ],
        "6" => [ 1, 2, 1, 3, 1 ],
        "7" => [ 1, 1, 0, 1, 0 ],
        "8" => [ 1, 3, 1, 3, 1 ],
        "9" => [ 1, 3, 1, 1, 1 ]
    }

    @@lcdStates = [
        "HORIZONTAL",
        "VERTICAL",
        "HORIZONTAL",
        "VERTICAL",
        "HORIZONTAL",
        "DONE"
    ]

    def initialize( size=1, spacing=1 )
        @size = size
        @spacing = spacing
    end

    def display( digits )
        states = @@lcdStates.reverse
        0.upto(@@lcdStates.length) do |i|
            case states.pop
            when "HORIZONTAL"
                line = ""
                digits.each_byte do |b|
                    line += horizontal_segment( @@lcdDisplayData[b.chr][i] )
                end
                print line + "\n"
                               when "VERTICAL"
                1.upto(@size) do |j|
                    line = ""
                    digits.each_byte do |b|
                        line += vertical_segment( @@lcdDisplayData[b.chr][i] )
                    end
                    print line + "\n"
                end
            when "DONE"
                break
            end
        end
    end

    def horizontal_segment( type )
        case type
        when 1
            return " " + ("-" * @size) + " " + (" " * @spacing)
        else
            return " " + (" " * @size) + " " + (" " * @spacing)
        end
    end

    def vertical_segment( type )
        case type
        when 1
            return " " + (" " * @size) + "|" + (" " * @spacing)
        when 2
            return "|" + (" " * @size) + " " + (" " * @spacing)
        when 3
            return "|" + (" " * @size) + "|" + (" " * @spacing)
        else
            return " " + (" " * @size) + " " + (" " * @spacing)
        end
    end
end

##### Main

opts = GetoptLong.new(
    [ "--size", "-s", GetoptLong::REQUIRED_ARGUMENT ],
    [ "--spacing", "--sp", "-p", GetoptLong::REQUIRED_ARGUMENT ]
)

lcd = LCD.new

opts.each do |opt, arg|
    case opt
    when "--size"
        lcd.size = arg.to_i

    when "--spacing"
        lcd.spacing = arg.to_i
    end
end

lcd.display( ARGV.shift )

"Sean Ross" <sross@connectmail.carleton.ca> wrote in message
news:8JiEd.3477$TN6.223560@news20.bellglobal.com...
[snip]

# lcd segment formats
nothing = " #{' '*size} "
crossbar = " #{'-'*size} "
leftpost = "|#{' '*size} "
rightpost = " #{' '*size}|"
uprights = "|#{' '*size}|"

[snip]

Needs a little refactoring:

gap = ' '*size
nothing = " #{gap} "
crossbar = " #{'-'*size} "
leftpost = "|#{gap} "
rightpost = " #{gap}|"
uprights = "|#{gap}|"

Dave Burt wrote:

You can see my solution at http://www.dave.burt.id.au/ruby/lcd_digits.rb

I used two functions, one to turn something like '9' into a big ASCII-art digital-looking 9, and one like UNIX's paste command to stick them next to each other.

Your solution is very similar to mine. We both represented the number faces via bit masks and decided to first render the individual digits and then stick them next to each other.

I'm keeping the lines of a digit as an Array and sticking them together with this code:

ary.transpose.map do |line|
   line.join(" ")
end.join("\n")

Maybe that would also be an option for you.

My newsreader missed your post :frowning:
I was able to read it on ruby-talk.com via the Ruby Quiz site (which is
pretty cool for all its failings) :slight_smile:

Sticking together like this?

def paste(array_of_strings, delim = ' ')
  array_of_strings.map {|s| s.split(/\n/)}.transpose.map do |line|
    line.join(delim)
  end.join("\n")
end

Yes, that's neater than my paste(), and plugs right in. These strings all
have the same number of lines, so it doesn't matter that it fails if they
don't.

It's an interesting one. The process we used seemed to me to be the obvious
one, yet everyone's come up with a different way, stretching, golfing, etc.

I should also borrow your comments for my bitmask hash.

Cheers,
Dave

···

"Florian Gross" <flgr@ccan.de> wrote:

Dave Burt wrote:

You can see my solution at http://www.dave.burt.id.au/ruby/lcd_digits.rb

I used two functions, one to turn something like '9' into a big ASCII-art
digital-looking 9, and one like UNIX's paste command to stick them next
to each other.

Your solution is very similar to mine. We both represented the number
faces via bit masks and decided to first render the individual digits and
then stick them next to each other.

I'm keeping the lines of a digit as an Array and sticking them together
with this code:

ary.transpose.map do |line|
  line.join(" ")
end.join("\n")

Maybe that would also be an option for you.