-3

I'm trying to write code that simulates writing a text message using a multi-tap telephone keypad in Ruby. This is the telephone keypad:

  1    2    3
      ABC  DEF

  4    5    6
 GHI  JKL  MNO

  7    8    9
PQRS  TUV  WXYZ

       0
    (space)

I tried to define it in Ruby as: (doesn't work)

"0" = [" "] # (adds a space)
"1" = [""]  # (adds nothing)
"2" = ["a", "b", "c"]
"3" = ["d", "e", "f"]
"4" = ["g", "h", "i"]
"5" = ["j", "k", "l"]
"6" = ["m", "n", "o"]
"7" = ["p", "q", "r", "s"]
"8" = ["t", "u", "v"]
"9" = ["w", "x", "y", "z"]

I will explain how it works with two examples. First I will send the string goat. To send the g I press 4 once. Next, to send o I press 6 three times (as pressing 6 once would send m and pressing 6 twice would send n). For a press 2 once and for t press 8 once. We therefore would send

466628
g  oat

Next, consider cake. By the same procedure we would send

22225533
  ca k e

Here there is problem. When decoding this there are several possibilities for 2222. It could be aaaa, bb and so on. To overcome this ambiguity a "pause", represented as a space, is inserted after each string of digits that is followed by a string of the same digit. For cake, therefore, we would write

222 25533
  c a k e

I already have a hash with the numbers and its corresponding letters, and I know that I have to sort the numbers by how many times they repeat themselves. But I do not know which method I use for it.

Also, do I have to use the same logic in case I need to encode (number to letter)?

  • 1
    Please see "[ask]", "[Stack Overflow question checklist](https://meta.stackoverflow.com/questions/260648)" and all their linked pages. We expect to see your attempt to solve this. Currently you've given us a requirement that you're supposed to solve, and want us to write code for you. "[How do I ask and answer homework questions?](https://meta.stackoverflow.com/q/334822/128421)" will explain. – the Tin Man Jun 24 '20 at 04:14
  • @CarySwoveland it's a _pause_ so `222` can register as a `c`. Without a space, `2222` could be `aaaa`, `aab`, `aba`, `ac` or any other combination. – Stefan Jun 24 '20 at 07:15
  • g13, I trust that the edits that @Stefan and I did were consistent with what you are trying to do. If they were, you may wish to clarify the last sentence, which are your original words. If you clarify that sentence to state that you wish to just encode text, or to both encode and decode text, there is a chance the question will be re-opened. It *is* an interesting question. – Cary Swoveland Jun 24 '20 at 18:04
  • Hello @CarySwoveland and @Stefan! Sorry I took too long, I've been doing these homeworks for a long time so I decided to spend some time off from the computer and came back a little while ago. I tested both the encoding and decoding solutions and they worked. It encouraged me to look deeper into regex and map and it helped me solve other homeworks! Thank you very much for taking the time to help me. I'll be more careful when I have another question. Now I'll edit it with the progress that I made upon asking for help here. – gmonteiro13 Jul 01 '20 at 21:36

3 Answers3

3

You are given:

arr = [
  ["0", [" "]],
  ["1", [""]],
  ["2", ["a", "b", "c"]],
  ["3", ["d", "e", "f"]],
  ["4", ["g", "h", "i"]],
  ["5", ["j", "k", "l"]],
  ["6", ["m", "n", "o"]],
  ["7", ["p", "q", "r", "s"]],
  ["8", ["t", "u", "v"]],
  ["9", ["w", "x", "y", "z"]]
]

I have been puzzled by the inclusion of

  ["1", [""]],

which seems to serve no purpose. It would have a purpose, however, if instead of

222 25533

to represent the string, "cake", we used

222125533

That is, if two successive characters that are represented by strings of the same digit (such as "222" and "2") they are to be separated by a "1", rather than by a "pause", expressed as a space. If that were done we could encode and decode strings as follows.

Encoding

CHAR_TO_DIGITS = arr.each_with_object({}) do |(num, a),h|
  a.each.with_index(1) { |ltr,i| h[ltr] = num * i }
end
  #=> {" "=>"0", ""=>"1", "a"=>"2", "b"=>"22", "c"=>"222",
  #    "d"=>"3", "e"=>"33", "f"=>"333", "g"=>"4", "h"=>"44",
  #    "i"=>"444", "j"=>"5", "k"=>"55", "l"=>"555", "m"=>"6",
  #    "n"=>"66", "o"=>"666", "p"=>"7", "q"=>"77", "r"=>"777",
  #    "s"=>"7777", "t"=>"8", "u"=>"88", "v"=>"888", "w"=>"9",
  #    "x"=>"99", "y"=>"999", "z"=>"9999"} 

def encode(plain_text)
  plain_text.each_char.with_object('') do |c,s|
    digits = CHAR_TO_DIGITS[c]
    s << '1' if !s.empty? && digits[0] == s[-1]
    s << digits
  end
end

Then

encoded_1 = encode "cake"
  #=> "222125533"
encoded_2 = encode "my dog has fleas"
  #=> "69990366640442777703335553327777" 

Decoding

Decoding is even easier.

DIGITS_TO_CHAR = CHAR_TO_DIGITS.invert
  #=> {"0"=>" ", "1"=>"", "2"=>"a", "22"=>"b", "222"=>"c",
  #    "3"=>"d", "33"=>"e", "333"=>"f", "4"=>"g", "44"=>"h",
  #    "444"=>"i", "5"=>"j", "55"=>"k", "555"=>"l", "6"=>"m",
  #    "66"=>"n", "666"=>"o", "7"=>"p", "77"=>"q", "777"=>"r",
  #    "7777"=>"s", "8"=>"t", "88"=>"u", "888"=>"v", "9"=>"w",
  #    "99"=>"x", "999"=>"y", "9999"=>"z"} 

def decode(encoded_text)
  encoded_text.gsub(/(\d)\1*/, DIGITS_TO_CHAR)
end

Then

decode encoded_1
  #=> "cake"
decode encoded_2
  #=> "my dog has fleas"

This uses the form of String#gsub that employs a hash to make substitutions. See also Hash#invert.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
3

(I had the encoding part first when Cary Swoveland pointed out that you might want decoding. The answer now contains both ways and became quite long, I hope you don't mind)


Your example code doesn't work. You can't just assign to a string literal. However, you could use a hash like this to define your keypad in Ruby:

keypad = {
  '0' => [' '],
  '1' => [],        # <- you can leave this out
  '2' => %w[a b c],
  '3' => %w[d e f],
  '4' => %w[g h i],
  '5' => %w[j k l],
  '6' => %w[m n o],
  '7' => %w[p q r s],
  '8' => %w[t u v],
  '9' => %w[w x y z],
}

Decoding

To turn 222 25533 into cake, I'd start by splitting consecutive and space-delimited numbers. You could use a regex:

parts = '222 25533'.gsub(/(\d)\1*/).map(&:itself)
#=> ["222", "2", "55", "33"]

This can be converted to an array containing key/number-of-times pairs:

key_strokes = parts.map { |part| [part[0], part.length] }
#=> [["2", 3], ["2", 1], ["5", 2], ["3", 2]]

which can be converted to the letters using the keypad hash:

letters = key_strokes.map { |key, times| key_pad[key][times - 1] }
#=> ["c", "a", "k", "e"]

That - 1 is needed because array indices are zero-based. Finally, turn the letters into a word:

letters.join
#=> "cake"

Encoding

To convert characters to key strokes, I'd create a hash based on the keypad which maps each character to a key/number-of-times pair:

mapping = {}
key_pad.each do |key, values|
  values.each.with_index(1) do |char, times|
    mapping[char] = [key, times]
  end
end

mapping
#=> {
#     " "=>["0", 1], "a"=>["2", 1], "b"=>["2", 2], "c"=>["2", 3], "d"=>["3", 1],
#     "e"=>["3", 2], "f"=>["3", 3], "g"=>["4", 1], "h"=>["4", 2], "i"=>["4", 3],
#     "j"=>["5", 1], "k"=>["5", 2], "l"=>["5", 3], "m"=>["6", 1], "n"=>["6", 2],
#     "o"=>["6", 3], "p"=>["7", 1], "q"=>["7", 2], "r"=>["7", 3], "s"=>["7", 4],
#     "t"=>["8", 1], "u"=>["8", 2], "v"=>["8", 3], "w"=>["9", 1], "x"=>["9", 2],
#     "y"=>["9", 3], "z"=>["9", 4]
#   }

In the above hash, "c"=>["2", 3] means that in order to get c you have to press the 2 key 3 times. To render the sequence for a single key in Ruby, we can utilize String#* which repeats a string:

key, times = mapping['c']

key   #=> '2'
times #=> 3

key * times
#=> '222'

Getting the key strokes for an entire word (or sentence) is a matter of mapping each character to its respective hash value:

parts = 'cake'.each_char.map { |char| mapping[char] }
#=> [["2", 3], ["2", 1], ["5", 2], ["3", 2]]

To render the actual sequence, we have to first group consecutive runs of the same key:

chunks = parts.chunk_while { |(a, _), (b, _)| a == b }.to_a
#=> [
#     [["2", 3], ["2", 1]],
#     [["5", 2]],
#     [["3", 2]]
#   ]

We can now join identical key strokes via space and the chunks without space:

chunks.map { |chunk| chunk.map { |k, t| k * t }.join(' ') }.join
#=> "222 25533"
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • 1
    I'll check back in the morning, but have a look at the last sentence of the question (which I didn't touch). It suggests to me that the OP may want to decode instead of (or in addition to) encode. If you have time you might want to cover decoding in any event. I – Cary Swoveland Jun 24 '20 at 08:25
  • @CarySwoveland you might be right, I've totally overlooked that! (added it to my answer) – Stefan Jun 24 '20 at 08:38
  • @CarySwoveland good points, but this answer already took a bit too long I'll leave that as _"an exercise to the reader"_ :-) – Stefan Jun 24 '20 at 09:07
1

I'd start with converting "222 25533" into an array [[2,3],[2,1],[5,2],[3,2]] where the first number represents a digit and the second is a number of its occurrences. Having this you can easily find letters from the keypad.

Alexey Schepin
  • 386
  • 1
  • 13