Reputation: 57
I'm writing a function that decodes a morse code message to plain text. The problem I'm running into, is that it doesn't add spaces where needed. Keep in mind that every morse code character/letter, is separated with a blank space and every full word, is separated by 3 spaces. I want the function to add a single blank space to the plain text when it detects 3 blank spaces in a row in the morse code. I can't figure it out so I've come here.
Here is the function as it is now.
public function decode($morseCode)
{
// 1 space between characters, 3 spaces between words
// split the string
$morseArray = preg_split('/\s+/', $morseCode, -1);
$plainText = '';
$blankSpaceCount = 0;
// set each string as a morse code character
foreach ($morseArray as $morseChar)
{
if ($morseChar != ' ')
{
// check if the morsecode character is in the array
if(isset($this->plainTextsByMorseCode[$morseChar]))
{
// if it is, convert it to the corresponding plain text
$plainText .= $this->plainTextsByMorseCode[$morseChar];
$blankSpaceCount = 0;
}
}
else {
$blankSpaceCount++;
if ($blankSpaceCount === 3)
{
$plainText .= ' '; // Append a single blank space
$blankSpaceCount = 0;
}
}
}
return trim($plainText);
}
If it helps, the phrase I'm trying to decode is as follows:
-.-- --- ..- ... .... .- .-.. .-.. -. --- - .--. .- ... ...
It says "YOU SHALL NOT PASS" and you can clearly see the triple blank spaces between the words and the singular blank space between the letters.
Upvotes: 1
Views: 319
Reputation: 17415
No need to go the regex way, just define a substitution. Every valid morse code sequence, with an optional trailing space, gets replaced by the according Latin letter. Note that the trailing space must be exactly zero or one space, zero spaces after the last letter. A triple space separating two morse letters will then become a single space in the output. For simplicity, you can pad the input string with a space at the end.
function morse_decode(string $morse): string
{
if (!$morse) {
return '';
}
$padded = $morse . ' ';
// https://www.php.net/manual/en/function.str-replace
$res = str_replace(
[
'--- ',
'..- ',
'-.-- ',
// ...
' '
], [
'O',
'U',
'Y',
// ...
' '
],
$padded
);
// https://www.php.net/manual/en/function.strspn.php
if (strspn($res, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ') !== strlen($res)) {
throw new DomainException('input is not valid morse code');
}
return $res;
}
Upvotes: 1
Reputation: 47903
Don't bother creating any temporary arrays and doing conditional iterations and other such convolutions.
You are translating a regularly formatted string into another string by leveraging a lookup array -- this is an ideal scenario for preg_replace_callback()
and/or strtr()
.
If this were my project, I'd probably declare $plainTextsByMorseCode
as a class-level constant instead of a class variable.
Code: (Demo)
class MyMorse
{
private array $plainTextsByMorseCode = [
'.-' => 'A',
'....' => 'H',
'.-..' => 'L',
'-.' => 'N',
'---' => 'O',
'.--.' => 'P',
'...' => 'S',
'-' => 'T',
'..-' => 'U',
'-.--' => 'Y',
];
public function decode(string $morseCode): string
{
return preg_replace_callback(
'/ ?(\S+)| {3}/',
fn($m) => ctype_space($m[0])
? ' ' // reduce 3 spaces to 1
: $this->plainTextsByMorseCode[$m[1]], // translate
$morseCode
);
}
}
Or the decode()
method could be done in two steps (translate, then handle spacing) if you find that clearer. (Demo)
public function decode(string $morseCode): string
{
return strtr(
preg_replace_callback('/[.-]+/', fn($m) => $this->plainTextsByMorseCode[$m[0]], $morseCode),
[' ' => ' ', ' ' => '']
);
}
In fact, this translation task can be completely handled by strtr()
. (Demo)
public function decode(string $morseCode): string
{
return strtr($morseCode, $this->plainTextsByMorseCode + [' ' => ' ', ' ' => '']);
}
To call:
$obj = new MyMorse();
var_export(
$obj->decode('-.-- --- ..- ... .... .- .-.. .-.. -. --- - .--. .- ... ...')
);
Output (with var_export()
to show no lingering/unwanted spaces):
'YOU SHALL NOT PASS'
The regex pattern matches an optional space before a sequence of non-whitespace characters and captures the visible characters; or it matches 3 consecutive spaces. In the callback function, if the match is only spaces, then return a single space (word separator); otherwise translate the sequence, omit the leading space, and return the letter.
Here is the inverse operation to encode plain text as morse code
Upvotes: 1
Reputation: 15361
As @Thefourthbird has pointed out, you are splitting into an array using spaces (regardless of the number) and then you are trying to count spaces again after that. It's irrelevant because you already parsed the words into an array of letters.
This simple test illustrates the mistake in your logic and the proper regex to break your cipher into an array of words.
<?php
$morseCode = '-.-- --- ..- ... .... .- .-.. .-.. -. --- - .--. .- ... ...';
$morseArray = preg_split('/\s{3}/', $morseCode, -1);
var_dump($morseArray);
The output is:
array(4) {
[0]=>
string(12) "-.-- --- ..-"
[1]=>
string(21) "... .... .- .-.. .-.."
[2]=>
string(8) "-. --- -"
[3]=>
string(15) ".--. .- ... ..."
}
Once you have the array of words, you are only left to convert to letters, and add a space back for all but the final word in the word array.
Upvotes: 0
Reputation: 163362
After the splitting, this part is always true if ($morseChar != ' ')
so you will not reach the else clause.
Here is another take on it, mapping the morse to the plain text characters exploding first on 3 spaces and then on a single space.
public function decode($morseCode)
{
$decodedWords = [];
foreach (explode(' ', $morseCode) as $morseWord) {
$decodedWords[] = implode('', array_map(
fn($morse) => $this->plainTextsByMorseCode[$morse] ?? '',
explode(' ', $morseWord)
));
}
return implode(' ', $decodedWords);
}
The output will be
YOU SHALL NOT PASS
Upvotes: 0
Reputation: 4157
$morsecode='your morse code';
//empty array for decode words
$decoded_words=[];
//split the code in words on 3 spaces
$morse_words=explode(' ',$morsecode); //3 spaces
//loop the words
foreach($morse_words as $word){
//empty string for decoded characters
$plain_word='';
//split the word into characters on 1 space
$morse_chars=explode(' ',$word); //1 space
//loop the characters, decode and add to the plain word string
foreach($morse_chars as $char){
$plain_word.=decode_morse_character($char);
}
//Add decoded word to the words array
$decoded_words[]=$plain_word;
}
//implode the decoded_words array with a space between every word
$decoded=implode(' ',$decoded_words);
Upvotes: 0