Ruby Quiz, Haskell Solution: LCD Numbers

Posted: December 17th, 2009 | Author: Michel Rijnders | Filed under: Haskell, Ruby Quiz, Uncategorized | 2 Comments »

A solution to Ruby Quiz #14 in literate Haskell:

LCD Numbers
===========

Problem
-------

[original source](http://rubyquiz.com/quiz14.html)

This week's quiz is to write a program that displays LCD style numbers
at adjustable sizes.

The digits to be displayed will be passed as an argument to the
program. Size should be controlled with the command-line option -s
follow up by a positive integer. The default value for -s is 2.

For example, if your program is called with:

    $ lcd.rb 012345

The correct display is:

     --        --   --        --
    |  |    |    |    | |  | |
    |  |    |    |    | |  | |
               --   --   --   --
    |  |    | |       |    |    |
    |  |    | |       |    |    |
     --        --   --        -- 

And for:

    $ lcd.rb -s 1 6789

Your program should print:

     -   -   -   -
    |     | | | | |
     -       -   -
    | |   | | |   |
     -       -   - 

Note the single column of space between digits in both examples. For
other values of -s, simply lengthen the - and | bars.

Solution
--------

Module declaration and imports:

> module Main where
>
> import Data.Char (digitToInt)
> import Data.List (intersperse)
> import System.Console.GetOpt
> import System.Environment (getArgs)

First we define the numbers at size 1:

> n0 = [ " - "
>      , "| |"
>      , "   "
>      , "| |"
>      , " - "
>      ]
>
> n1 = [ "   "
>      , "  |"
>      , "   "
>      , "  |"
>      , "   "
>      ]
>
> n2 = [ " - "
>      , "  |"
>      , " - "
>      , "|  "
>      , " - "
>      ]
>
> n3 = [ " - "
>      , "  |"
>      , " - "
>      , "  |"
>      , " - "
>      ]
>
> n4 = [ "   "
>      , "| |"
>      , " - "
>      , "  |"
>      , "   "
>      ]
>
> n5 = [ " - "
>      , "|  "
>      , " - "
>      , "  |"
>      , " - "
>      ]
>
> n6 = [ " - "
>      , "|  "
>      , " - "
>      , "| |"
>      , " - "
>      ]
>
> n7 = [ " - "
>      , "  |"
>      , "   "
>      , "  |"
>      , "   "
>      ]
>
> n8 = [ " - "
>      , "| |"
>      , " - "
>      , "| |"
>      , " - "
>      ]
>
> n9 = [ " - "
>      , "| |"
>      , " - "
>      , "  |"
>      , " - "
>      ]
>

Put the numbers in  a list:

> numbers = [n0,n1,n2,n3,n4,n5,n6,n7,n8,n9]

Horizontal scaling function, given a string replicate the second
character n times:

> hscale n cs = head cs : replicate n (cs!!1) ++ [last cs]

Vertical scaling function, repeat the second and fourth row n times:

> vscale n css = head css : replicate n cs1 ++ [cs2] ++ replicate n cs3 ++ [cs4]
>   where cs1 = css !! 1
>         cs2 = css !! 2
>         cs3 = css !! 3
>         cs4 = last css

Scale function; note this function scales a single number:

> scale n = vscale n . map (hscale n)

Function that converts a list of numbers to a string of LCD numbers:

> lcd n = concat .
>         intersperse "\n" .
>         foldr1 (zipWith (++)) .
>         intersperse (replicate (3 + 2*n) " ") .
>         map (scale n . (numbers !!))

`main` function:

> main = do
>   args <- getArgs
>   let (n, digits) = parseArgs args
>   putStrLn $ lcd n $ map digitToInt digits

Command-line argument parsing:

> data Flag = Scale Int
>             deriving Eq
>
> options = [Option "s" [] (ReqArg (Scale . read) "") ""]
>
> parseArgs args =
>   case parse args of
>    (_, [], _)              -> error "Usage: lcd [-s n] digits"
>    ([], digits, [])        -> (2, head digits)
>    ([Scale n], digits, []) -> (n, head digits)
>    (_, _, _)               -> error "Usage: lcd [-s n] digits"
>   where
>     parse = getOpt RequireOrder options

2 Comments on “Ruby Quiz, Haskell Solution: LCD Numbers”

  1. 1 Eduard said at 20:53 on December 17th, 2009:

    And for a short, if not as sweet perl version:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    my ( undef, $scale, $input ) =  @ARGV == 3 ? @ARGV : ( '', '2', @ARGV );
    
    my @digits; push( @digits, $1) while $input =~ m/(\d)/g;
    
    my $empty   = '   ';
    my $line    = ' - ';
    my $sides   = '| |';
    my $right   = '  |';
    my $left    = '|  ';
    
    my @d;
    $d[0] = [$line,   $sides, $empty, $sides, $line   ];
    $d[1] = [$empty,  $right, $empty, $right, $empty  ];
    $d[2] = [$line,   $right, $line,  $left,  $line   ];
    $d[3] = [$line,   $right, $line,  $right, $line   ];
    $d[4] = [$empty,  $sides, $line,  $right, $empty  ];
    $d[5] = [$line,   $left,  $line,  $right, $line   ];
    $d[6] = [$line,   $left,  $line,  $sides, $line   ];
    $d[7] = [$line,   $right, $empty, $right, $empty  ];
    $d[8] = [$line,   $sides, $line,  $sides, $line   ];
    $d[9] = [$line,   $sides, $line,  $right, $line   ];
    
    for my $line (0..4) {
        for (1..($line % 2 == 1  ? $scale : 1))  {
            for my $digit ( @digits ) {
                $d[$digit][$line] =~ m/^(.)(.)(.)$/;
                print  $1 . ( $2 x $scale) . $3 . ' ';
            }
            print "\n";
        }
    }
    
  2. 2 Eduard Lohmann said at 12:02 on December 18th, 2009:

    Namens marius in bash en awk :)

    #!/bin/bash 
    
    DIGITS=""
    SCALE="2" 
    
    function parse_args {
        while getopts  "s:" flag
        do
            SCALE="$OPTARG"
        done
        shift $(($OPTIND-1))
        DIGITS="$1"
    } 
    
    SPRITE_LINE0=".-.....-..-.....-..-..-..-..-."
    SPRITE_LINE1="|.|..|..|..||.||..|....||.||.|"
    SPRITE_LINE2=".......-..-..-..-..-.....-..-."
    SPRITE_LINE3="|.|..||....|..|..||.|..||.|..|"
    SPRITE_LINE4=".-.....-..-.....-..-.....-..-." 
    
    NEWLINE_SUBSITUTE="L"
    READ_DELIM="X" 
    
    function digit_to_sprite {
        local DIGIT="$*"
        local SPRITE_TEMPLATE_OFFSET=$(($DIGIT*3))
        for SPRITE_LINE_NR in $(seq 0 4); do
            local VAR_SELECTOR="SPRITE_LINE$SPRITE_LINE_NR"
            echo -n ${!VAR_SELECTOR:$SPRITE_TEMPLATE_OFFSET:3}$NEWLINE_SUBSITUTE
        done
        echo
    } 
    
    function digits_to_sprites {
        local DIGITS="$*"
        DIGITS="$(echo $DIGITS | sed 's/\(.\)/\1 /g')" 
    
        for DIGIT in $DIGITS ; do
            digit_to_sprite $DIGIT
        done
    } 
    
    function calculate_scale {
        local SPRITES=$1
        local FIRST_SPRITE=$(echo $SPRITES | head -1) 
    
        local TOTAL_LINES=$(echo -n $FIRST_SPRITE | tr $NEWLINE_SUBSITUTE
    \\n | wc -l)
        echo $((($TOTAL_LINES-3)/2))
    } 
    
    function render_sprites {
        local SPRITES=""
        read  -d $READ_DELIM SPRITES
        local SPRITE_SCALE=$(calculate_scale $SPRITES) 
    
        for LINE_NR in $(seq 1 $((($SPRITE_SCALE * 2) + 3))); do
            local TOTAL_LINE=""
            for SPRITE in $SPRITES; do
                local PIECE=$(echo -n $SPRITE | cut -d $NEWLINE_SUBSITUTE
    -f $LINE_NR)
                TOTAL_LINE="$TOTAL_LINE$PIECE "
            done
            echo $TOTAL_LINE | tr '.' ' '
        done
    } 
    
    function scale_sprites {
        local SCALE=$1
        local SPRITES=""
        read -d $READ_DELIM SPRITES
        local VSCALE_AWK="
    /([|][.][.])|([.][.][|])|([|][.][|])/ {
      for (i = 1; i < scale; i++)
        print \$0
    }
    BEGIN {
      RS=\"$NEWLINE_SUBSITUTE\"
      ORS=\"$NEWLINE_SUBSITUTE\"
    }
    {
      print \$0
    }
    "
        local HSCALE_AWK="
    BEGIN {
      RS=\"$NEWLINE_SUBSITUTE\"
      ORS=\"$NEWLINE_SUBSITUTE\"
      FS=\"\"
      OFS=\"\"
    }
    {
      line = \$1
      for (i = 1; i <= scale; i++)
        line = line \$2
      line = line \$3
      print line
    }
    "
        AWK_VARIABLES="scale=$SCALE"
        for SPRITE in $SPRITES; do
            echo -n "$SPRITE" \
                | awk "$VSCALE_AWK" $AWK_VARIABLES \
                | awk "$HSCALE_AWK" $AWK_VARIABLES
            echo
        done
    } 
    
    parse_args $*
    digits_to_sprites $DIGITS | scale_sprites $SCALE | render_sprites
    

Leave a Reply