|Ruby Quiz|

Straightforward and memory-greedy solution.

= Tables

== table.rb

# An interface to subsequent tables

class Table
  
  def initialize
    @table = {}
    compose
  end
  
  def compose
  end
    
  def [](value)
    @table[value]
  end  
  
end


== morse.rb

require 'table'

class Morse < Table
  
  def compose
    @table['.'] = 'E'
    @table['..'] = 'I'
    @table['.-'] = 'A'
    @table['...'] = 'S'
    @table['..-'] = 'U'
    @table['....'] = 'H'
    @table['...-'] = 'V'
    @table['..-.'] = 'F'
    @table['.-.'] = 'R'
    @table['.--'] = 'W'
    @table['.-..'] = 'R'
    @table['.--.'] = 'P'
    @table['.---'] = 'G'
    @table['-'] = 'T'
    @table['-.'] = 'N'
    @table['--'] = 'M'
    @table['-..'] = 'D'
    @table['-.-'] = 'K'
    @table['-...'] = 'B'
    @table['-..-'] = 'X'
    @table['-.-.'] = 'C'
    @table['-.--'] = 'Y'
    @table['--.'] = 'G'
    @table['---'] = 'O'
    @table['--..'] = 'Z'
    @table['--.-'] = 'Q'
  end

end

== probability.rb

# I've decided to use letters probabilities approach. Output strings
# will be sorted by difference (Euclidean metric) between input distribution and control
# distribution for English language (taken from [1]).

# However possible benefits are visual only in large texts. But large
# texts are processed very lo-o-ong...

# In few signals' string it's just sends less meaningful values like
# 'EEEETTTEEE' to end.

# In SOFIA/EUGENIA example: SOFIA was found at 1824's position and
# EUGENIA at 935's from 5104 strings.

# [1] http://www.fortunecity.com/skyscraper/coding/379/lesson1.htm

require 'table'

class Probability < Table
  
  def compose
    @table['E'] = 0.127
    @table['T'] = 0.091
    @table['A'] = 0.082
    @table['O'] = 0.075
    @table['I'] = 0.070
    @table['S'] = 0.063
    @table['N'] = 0.067
    @table['H'] = 0.061
    @table['R'] = 0.060
    @table['L'] = 0.040
    @table['C'] = 0.028
    @table['U'] = 0.028
    @table['M'] = 0.024
    @table['W'] = 0.023
    @table['F'] = 0.022
    @table['G'] = 0.020
    @table['P'] = 0.019
    @table['B'] = 0.015
    @table['V'] = 0.010
    @table['K'] = 0.008
    @table['J'] = 0.002
    @table['Y'] = 0.002
    @table['Q'] = 0.001
    @table['X'] = 0.001
    @table['Z'] = 0.001
  end
  
  def metric(vec1, vec2)
    vec = []
    vec1.each_index do |index|
      vec << [vec1[index], vec2[index]]
    end
    metr = vec.inject(0) do |sum, item|
      sum + (item[0]-item[1]) ** 2
    end
    Math.sqrt(metr)
  end
  
  def to_vector
    table = @table.sort.to_a
    table.inject([]) do |acc, item|
      acc << item[1]
    end
  end
  
  def distance(other)
    metric(self.to_vector, other)
  end
  
end

= Implementation

Approach:

(0 step) Input Morse string is sent to accumulator

(n step) Take first element from accumulator. Separate it in two
parts: head with decoded letters and tail with Morse code.

If tail is not empty:

  Find letter representation for first four codes in tail:
   - decode letter if it's represented by one signal
   - decode letter if it's represented by two signals (if possible)
   - decode letter if it's represented by three signals (if possible)
   - decode letter if it's represented by four signals (if possible)

  Construct new strings (no more than 4):
   - previously decoded head plus decoded on this step letter plus
     intact Morse code
  and append them to accumulator

If tail is empty:

  Append to output.
 
== telegraphist.rb

require 'probability'
require 'morse'
require 'enumerator'

class Telegraphist
  
  ALPHABET = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ'

  # SILENT mode writes output to file
  SILENT = true
  
  def initialize
    @probability = Probability.new
    @morse = Morse.new
  end
  
  def listen
    @code = $stdin.gets.chomp        
  end
  
  def say(words)
    if SILENT 
      File.open("check.txt", "w") do |file|
        file.puts words
      end
    else
      $stdout.puts words
    end
  end
  
  def decode
    sort(translate(@code))
  end

  def translate(text)
    txt = text.split(//)
    accumulator = [] << txt
    result = []
    while !accumulator.empty?
      element = accumulator.shift
      if element.include?('.') or element.include?('-')
        head = element.select do |item|
          item =~ /\w/
        end       
        tail = element.select do |item|
          item =~ /[.-]/
        end       
        if tail.length < 4 
          iterate = tail.length
        else
          iterate = 4
        end
        (1..iterate).each do |index|
           letter = @morse[tail[0, index].join]
           accumulator << head + [letter] + tail[index, element.length] if letter
        end
      else
        result << element.join
      end
    end
    result  
  end

  def sort(lines)
    lines.sort_by do |line|
      @probability.distance(probabilities(line))
    end
  end
  
  def probabilities(str)
    abc = ALPHABET.split(//)
    acc = []
    abc.each_index do |index|
      acc[index] = str.count(abc[index])
    end
    acc.map do |item|
      item / str.length.to_f
    end
  end
  
end

= Application

== app.rb

require 'telegraphist'

operator = Telegraphist.new
operator.listen
operator.say(operator.decode)


-- 
I. P.     2007-04-22T17:09