36
\$\begingroup\$

Goal

Morse code is often represented as sound. Given a stream of bits that represent whether sound is on or off, translate the stream into letters and numbers and spaces.

International Morse Code

Specifics

  • The bit stream is analysed based on the length of repeating ON/OFF bits.
    • 1 ON bit is a dot
    • 3 ON bits are a dash
    • 1 OFF bit delimits dots and dashes
    • 3 OFF bits delimits characters
    • 7 OFF bits delimits words (space)
  • The input may be a string or array. Only two unique characters/values of your choice are allowed in the input. (eg. 0/1, true/false, comma/space)
  • The output returns a string or is printed to the standard output.

Example

Input:    101010100010001011101010001011101010001110111011100000001011101110001110111011100010111010001011101010001110101
Analysis: \--H--/   E   \---L---/   \---L---/   \----O----/\-- --/\---W---/   \----O----/   \--R--/   \---L---/   \--D--/
Output:   HELLO WORLD

Assumptions

  • The stream always starts and ends with an ON bit.
  • There is no leading or trailing whitespace.
  • The input is always valid.
  • All letters (case-insensitive) and digits are supported.

Test Cases

101010100010001011101010001011101010001110111011100000001011101110001110111011100010111010001011101010001110101
HELLO WORLD

10100000001011100011101110000000101110000000101011101000101000101010001010101
I AM A FISH

1010111011101110001110111011101110111000101110111011101110001110111010101
2017

101010001110111011100010101
SOS

Scoring

This is code golf. The lowest byte-count code by this time next week wins.

\$\endgroup\$
  • \$\begingroup\$ Can output have trailing whitespace? \$\endgroup\$ – Brian J Nov 16 '17 at 17:24

19 Answers 19

9
\$\begingroup\$

APL (Dyalog), 65 62 60 57 bytes

-3 thanks to ngn.

Tacit prefix function.

⎕CY'dfns'
morse'/|[-.]+'⎕S'&'∘(⊃∘'/. -'¨6|'1+|(00)+'⎕S 1)

Try it online! Header, f←, and Footer are just to allow calling the function from Input while retaining TIO's byte count. In a normal APL session (corresponding to TIO's Input field), it would not be needed.

⎕CY'dfns'copy the dfns workspace (library)

() apply this tacit function:
'1+|(00)+'⎕S 1 PCRE Search 1-runs and even-length 0-runs and return matches' lengths
6| division remainder when divided by 6
⊃∘'/. -'¨ for each match length, pick the corresponding character from this string
'/|[-.]+'⎕S'&'∘ PCRE Search slashes and dash/dot-runs and return those
morse translate from Morse code to regular text

\$\endgroup\$
  • 5
    \$\begingroup\$ Wow, never knew Dyalog had a builtin for morse code. \$\endgroup\$ – Zacharý Nov 16 '17 at 16:51
  • \$\begingroup\$ @Zacharý There are many, many builtins in the dfns. \$\endgroup\$ – Erik the Outgolfer Nov 16 '17 at 18:33
  • \$\begingroup\$ @Zacharý Always check dfns! \$\endgroup\$ – Adám Nov 17 '17 at 9:33
  • \$\begingroup\$ You're linking to an older version. \$\endgroup\$ – Erik the Outgolfer Nov 17 '17 at 17:44
  • \$\begingroup\$ A BF function ... >_<, wow. \$\endgroup\$ – Zacharý Nov 17 '17 at 18:26
8
\$\begingroup\$

Python 2, 142 135 bytes

lambda s:''.join(' E-T----Z-Q---RSWU--2FH-V980NIMA--1CBYX-6--GDOK534PLJ-7'[int('0'+l.replace('111','3'),16)%57]for l in s.split('000'))

Try it online!

Explanation:

Splits the string into letters on 000 (0 thus means space)

Replaces each 111 with 3, and converts into base 16.

Then each number is modded by 57, which gives a range of 0..54, which is the index of the current character.


Previous version that converted to base 3:

Python 2, 273 252 247 bytes

lambda s:''.join(chr(dict(zip([0,242,161,134,125,122,121,202,229,238,241]+[2]*7+[5,67,70,22,1,43,25,40,4,53,23,49,8,7,26,52,77,16,13,2,14,41,17,68,71,76],[32]+range(48,91)))[int('0'+l.replace('111','2').replace('0',''),3)])for l in s.split('000'))

Try it online!

Previous version that converted to binary:

Python 2, 282 261 256 bytes

lambda s:''.join(chr(dict(zip([0,489335,96119,22391,5495,1367,341,1877,7637,30581,122333]+[2]*7+[23,469,1885,117,1,349,477,85,5,6007,471,373,119,29,1911,1501,7639,93,21,7,87,343,375,1879,7543,1909],[32]+range(48,91)))[int('0'+l,2)])for l in s.split('000'))

Try it online!

\$\endgroup\$
5
\$\begingroup\$

Ruby, 123 bytes

->s{s.split(/0000?/).map{|r|r[0]?"YE9_0IZTO_BHKU58V_GR_SFA__1_4NP_60X_____C__D_ML7WQ3__2__J"[r.to_i(2)%253%132%74]:" "}*""}

Try it online!

Split the input string on character limit. Use 3 or 4 OFF bits so that spaces are converted to empty strings. Take the base 2 value of every character, and bring into a reasonable range (less than 60 possible values) using modulo on 3 successive divisions.

\$\endgroup\$
  • \$\begingroup\$ Very nicely done. \$\endgroup\$ – iamnotmaynard Nov 16 '17 at 18:10
  • 2
    \$\begingroup\$ I'm note sure if it works for all cases, but if you remove 0? from the Regexp it still works for the four test cases. \$\endgroup\$ – Jordan Nov 17 '17 at 18:23
4
\$\begingroup\$

Python, 175 168 bytes

s=lambda t,f=''.join:f('; TEMNAIOGKDWRUS;;QZYCXBJP;L;FVH09;8;;;7;;;;;;;61;;;;;;;2;;;3;45'[int('1'+f('0'if j[1:]else j for j in i.split('0')),2)]for i in t.split('000'))

First convert the string into list of 0(dash)/1(dot) string, add a prefix 1 (to prevent leading zeroes and deal with whitespaces), then convert to binary.

Since every code have length no more than 5, the result ranges from 0 to 63 and can be listed in a string.

\$\endgroup\$
  • 1
    \$\begingroup\$ I independently got basically the same solution, but 169 bytes: lambda s:''.join("_ TEMNAIOGKDWRUS__QZYCXBJP_L_FVH09_8___7_______61_______2___3_45"[int('1'+filter(int,l).replace('2','0'),2)]for l in s.replace('111','2').split('000')) \$\endgroup\$ – Alex Varga Nov 17 '17 at 4:06
  • \$\begingroup\$ @AlexVarga Nice usage of Python 2 filter! \$\endgroup\$ – Colera Su Nov 17 '17 at 4:30
3
\$\begingroup\$

Jelly, 67 62 bytes

ṣ0L€«2ḅ2“>.&" þ/7;=¥çı¿¢×ÆФ怌©¦Çß÷µ¬£®Ñ½ðȷñ‘iịØB;⁶¤
œṣ0ẋ3¤Ç€

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Wait, link numbering wraps around? \$\endgroup\$ – Zacharý Nov 16 '17 at 17:34
  • \$\begingroup\$ @Zacharý Yes it does, line-links have modular indices. \$\endgroup\$ – Erik the Outgolfer Nov 16 '17 at 18:09
3
\$\begingroup\$

Visual Basic .NET (.NET Core), 252 bytes

-7 bytes thanks to @recursive

Function A(i)
For Each w In i.Split({"0000000"},0)
For Each l In w.Split({"000"},0)
Dim c=0
For Each p In l.Split("0")
c=c*2+1+p.Length\2
Next
A &="!ETIANMSURWDKGOHVF!L!PJBXCYZQ!!54!3!!!2!!!!!!!16!!!!!!!7!!!8!90"(c)
Next
A+=" "
Next
End Function

A function that takes a string of 1s and 0s, and returns a string. (Actually, only the 0 for the OFF is a hard requirement. Anything not OFF is assumed to be ON).

The string literal is Morse code setup as a binary heap in array form. VB.NET lets you index strings as arrays of characters. The \ is integer division, taking the left sub heap for 1 or the right sub heap for 111.

I used ! as a blank for when there isn't a value in that heap spot. It's only necessary to properly pad out the indices.

VB.NET lets you return by assigning a value to the function name (in this case, A). I just iteratively do string concatenations (&) to build up the output string. The very first time I need to use & because using + leaves a leading null char, but any other time I can use +, which behaves the same as & for strings.

Try it online!

\$\endgroup\$
  • 1
    \$\begingroup\$ You can save 7 bytes by using "!ETIANMSURWDKGOHVF!L!PJBXCYZQ!!5473!!8290!!!!!16", and then indexing using M(c-c\48*22), and then you can save another 4 by not even using M, but just using the string literal inline. \$\endgroup\$ – recursive Nov 16 '17 at 23:42
  • \$\begingroup\$ @recursive I understand the 4 byte trick, thanks for the help! I'm having trouble understanding the way you're changing the index. If I replace the string literal, and then use M(c-c\48*22), I get index out of bounds on the 2017 case. I think VB will do division and multiplication at the same precedence; am I missing a parentheses? \$\endgroup\$ – Brian J Nov 17 '17 at 14:24
  • \$\begingroup\$ You are right about precedence. c\48*22 will be either 0 or 22. It's a way of conditionally subtracting 22 from c, to make M shorter by "folding" the end of the string. If that isn't working out for you, you can always remove the parens from A &=(" ") for another 2 bytes. :) \$\endgroup\$ – recursive Nov 17 '17 at 16:34
  • \$\begingroup\$ And then you can change &= to +=, and remove another two spaces. \$\endgroup\$ – recursive Nov 17 '17 at 16:35
  • \$\begingroup\$ @recursive Oh, duh! too many extra parens. The issue with changing to plus is then I have a leading null character at the start of my string. Maybe that's not a big deal, though. \$\endgroup\$ – Brian J Nov 17 '17 at 16:45
3
\$\begingroup\$

JavaScript (ES6), 170 131 bytes

s=>s.split`000`.map(e=>'  ETIANMSURWDKGOHVF L PJBXCYZQ'[c=+`0b${1+e.replace(/0?(111|1)/g,d=>+(d>1))}`]||'473168290 5'[c%11]).join``


How it works:

If you change the dots to 0s and the dashes to 1s, and prefix with a 1, you get binary numbers, which when converted to decimal gives you:

  1. Letters: 2 - 18, 20, and 22 - 29.
    These can be converted to the correct letters by indexing into ' ETIANMSURWDKGOHVF L PJBXCYZQ'.
  2. Numbers: 32, 33, 35, 39, 47, 48, 56, 60, 62, and 63.
    If we take these numbers modulus 11, we get the numbers 0 - 8 and 10, which can be converted to the correct numbers by indexing into '473168290 5'.

The program splits on characters, then converts each character into dots and dashes, which are converted into the appropriate output based on the above rules.


Test Cases:

let f=

s=>s.split`000`.map(e=>'  ETIANMSURWDKGOHVF L PJBXCYZQ'[c=+`0b${1+e.replace(/0?(111|1)/g,d=>+(d>1))}`]||'473168290 5'[c%11]).join``

console.log(f('101010100010001011101010001011101010001110111011100000001011101110001110111011100010111010001011101010001110101'))
console.log(f('10100000001011100011101110000000101110000000101011101000101000101010001010101'));
console.log(f('1010111011101110001110111011101110111000101110111011101110001110111010101'));
console.log(f('101010001110111011100010101'));

\$\endgroup\$
3
\$\begingroup\$

Python 2, 127 bytes

lambda s:''.join("IVMB  T  K 9LZF 1HWO3 GUS4 8 7A  E QR 26   NJX    Y0P 5D  C"[(int('0'+l)^2162146)%59]for l in s.split('000'))

Try it online!

Building off of TFeld's solution by removing replace and by working in base 10, at the cost of a bitwise xor and a longer reference string.

\$\endgroup\$
2
\$\begingroup\$

PHP, 321 284 bytes

Saved 37 bytes thanks to @ovs

$a=array_flip([242,161,134,125,122,121,202,229,238,241,5,67,70,22,1,43,25,40,4,53,23,49,8,7,26,52,77,16,13,2,14,41,17,68,71,76]);foreach(split('0000000',$argv[1])as$w){foreach(split('000',$w)as$m){echo($v=$a[base_convert(str_replace([111,0],[2,],$m),3,10)])>9?chr($v+55):$v;}echo' ';}  

Previous version (321 bytes)

$a=array_flip([22222,12222,11222,11122,11112,11111,21111,22111,22211,22221,12,2111,2121,211,1,1121,221,1111,11,1222,212,1211,22,21,222,1221,2212,121,111,2,112,1112,122,2112,2122,2211]);foreach(split('0000000',$argv[1])as$w){foreach(split('000',$w)as$m){echo($v=$a[str_replace([111,0],[2,],$m)])>9?chr($v+55):$v;}echo' ';}

Try it online!

Ungolfed version :

$a=array_flip(
// Building an array $a with every Morse letter representation (1=dot, 2=dash) and flip it
               [22222,12222,11222,11122,11112,
                // 01234
                11111,21111,22111,22211,22221,
                // 56789
                12,2111,2121,211,1,1121,221,
                // ABCDEFG
                1111,11,1222,212,1211,22,
                // HIJKLM
                21,222,1221,2212,121,111,2,
                // NOPQRST
                112,1112,122,2112,2122,2211]);
                // UVWXYZ
foreach (split('0000000', $argv[1]) as $w){
// for each word (separate with 7 consecutive zeroes)
    foreach (split('000',$w) as $m){
    // for each letter (separate with 3 consecutive zeroes)
        echo ($v = $a[str_replace([111,0],[2,],$m)]) > 9
        // Replace '111' with '2' and '0' with nothing and find $v, the corresponding entry in the array $a
            ? chr($v+55)
            // If > 9th element, then letter => echo the ASCII code equal to $v+55
            : $v;
            // Else echo $v
    }
    echo ' ';
    // Echo a space
}
\$\endgroup\$
2
\$\begingroup\$

Java (OpenJDK 8), 370 bytes

s->{String r="";for(String t:s.split("0000000")){for(String u:t.split("000"))for(int x[]={1,7,5,21,29,23,87,93,119,85,117,341,375,343,373,471,477,349,469,1877,1367,1909,1879,1501,1911,1885,7637,5495,7543,7639,6007,30581,22391,122333,96119,489335},i=x.length;i-->0;)if(u.equals(Long.toString(x[i],2)))r+="ETISNAURMHD5WVLKGFB64ZXPOC73YQJ82910".charAt(i);r+=" ";}return r;}

Try it online!

  • 3 bytes saved thanks to @Jeutnarg.
\$\endgroup\$
  • 1
    \$\begingroup\$ can shave off a few by using Long.toString(x[i],2) instead of Integer.toString(x[i],2) \$\endgroup\$ – Jeutnarg Nov 16 '17 at 23:35
2
\$\begingroup\$

GNU sed, 261 + 1 = 262 bytes

+1 byte for -r flag.

s/000/;/g
s/111/_/g
s/0//g
s/$/;:51111141111_3111__211___i1____6_11117__1118___119____10_____H1111V111_F11_1L1_11P1__1J1___B_111X_11_C_1_1Y_1__Z__11Q__1_S111U11_R1_1W1__D_11K_1_N__1G__1O___I11A1_M__E1T_/
:
s/([1_]+);(.*([^1_])\1)/\3\2/
t
y/i/1/
s/;/ /g
s/:.*//g

Try it online!

Explanation

This is a very basic lookup table solution.

The first three lines transform the input so dashes are _s and dots are 1s. First, 000s are replaced with ;, so characters are separated by ; and words by ;;0. Then 111s are replaced by _ and all remaining 0s are discarded, leaving 1s for dots.

s/000/;/g
s/111/_/g
s/0//g

The next line appends the lookup table. It takes the form cmcmcm... where c is a character and m is the sequence of _s and 1s representing it. i is substituted for 1 in the table for disambiguation. Since regular expressions in sed are always greedy, the table is sorted from longest to shortest code (so e.g. 1_ matches A1_ instead of i1____).

s/$/;:51111141111_3111__211___i1____6_11117__1118___119____10_____H1111V111_F11_1L1_11P1__1J1___B_111X_11_C_1_1Y_1__Z__11Q__1_S111U11_R1_1W1__D_11K_1_N__1G__1O___I11A1_M__E1T_/

Next, in a loop, each sequence of _s and 1s (and the subsequent ;) is replaced by the corresponding character:

:
s/([1_]+);(.*([^1_])\1)/\3\2/
t

Finally, cleanup: is are replaced with 1s, remaining ;s are spaces, and the lookup table is deleted:

y/i/1/
s/;/ /g
s/:.*//g
\$\endgroup\$
1
\$\begingroup\$

Jelly, 67 bytes

ŒrḌṣ7ṣ€3ṣ€€1’H_4F€€ḅ3i@€€“ṗƙ⁶}zyȮẓŀȯ¦CFæ¢+ı(¥5ç1®¬ȷ4MÑÆ£Ç)×DGL‘ịØBK

Try it online!

\$\endgroup\$
1
\$\begingroup\$

JavaScript (ES6), 104 102 101 99 bytes

s=>s.split`000`.map(n=>" _T__9VEFO0K7MX_CGS__LU1RYIJ845__Z_B_D6QP_3__AHNW2"[n*1741%8360%51]).join``

Test cases

let f =

s=>s.split`000`.map(n=>" _T__9VEFO0K7MX_CGS__LU1RYIJ845__Z_B_D6QP_3__AHNW2"[n*1741%8360%51]).join``

console.log(f("101010100010001011101010001011101010001110111011100000001011101110001110111011100010111010001011101010001110101"))
console.log(f("10100000001011100011101110000000101110000000101011101000101000101010001010101"))
console.log(f("1010111011101110001110111011101110111000101110111011101110001110111010101"))
console.log(f("101010001110111011100010101"))

How?

Because converting from binary to decimal costs bytes, we use a hash function which works directly on binary blocks interpreted in base 10.

Example

dot dash dot dot = 101110101
101110101 * 1741 = 176032685841
176032685841 % 8360 = 3081
3081 % 51 = 21

--> The 21st character in the lookup table is 'L' (0-indexed).
\$\endgroup\$
  • \$\begingroup\$ I like this one-step approach very much. How big a search did you perform to fit these 37 outputs into a perfect hash of size 50 with a short enough function? \$\endgroup\$ – jayprich Sep 27 '18 at 11:15
  • \$\begingroup\$ @jayprich This was brute-forced. It was almost 1 year ago, though, so I don't remember exactly how. :) Chances are that I tried all n*p%m0%m1 for \$1\le p<10000\$, \$1<m_0<10000\$ and \$1<m_1<100\$. \$\endgroup\$ – Arnauld Sep 27 '18 at 11:28
1
\$\begingroup\$

Retina, 144 138 130 103 bytes

T`d`@#
^|@@@

 @?#
E
{T`L#@`R6_BI_Z5S1C_GD8__\L\HNF3P__7_`\w@#
T`589B-INPRSZ#@`490XYKT2\OVAMJWUQ_`\w##

Try it online! Link includes test cases. Explanation:

T`d`@#

Change the binary digits to other characters because 0 and 1 are valid outputs.

^|@@@
 

Insert a space before every character and two spaces between words.

 @?#
E

Assume that all characters are Es.

{T`L#@`R6_BI_Z5S1C_GD8__\L\HNF3P__7_`\w@#

Translate all letters assuming that they will be followed by a dot. For instance, if we have an E, and we see a second dot (we consumed the first when we inserted the E) then it translates to an I. For letters that can only be legally followed by a dash, they are translated with that assumption, and then the dash is consumed by the next stage. Other letters are deleted (keeping L costs a byte).

T`589B-INPRSZ#@`490XYKT2\OVAMJWUQ_`\w##

If it transpires that they were in fact followed by a dash, then fix up the mistranslations. This also consumes the dash when it was assumed by the previous stage. Both translations are repeated until all of the dots and dashes are consumed.

\$\endgroup\$
0
\$\begingroup\$

Perl 5, 241 + 1 (-p) = 242 bytes

%k=map{(23,469,1885,117,1,349,477,85,5,6007,471,373,119,29,1911,1501,7639,93,21,7,87,343,375,1879,7543,1909,489335,96119,22391,5495,1367,341,1877,7637,30581,122333)[$i++]=>$_}A..Z,0..9;map{$\.=$k{oct"0b$_"}for split/000/;$\.=$"}split/0{7}/}{

Try it online!

\$\endgroup\$
0
\$\begingroup\$

PHP, 181+1 bytes

foreach(explode(_,strtr($argn. 0,[1110=>1,10=>0,"0000"=>_A,"00"=>_]))as$t)echo$t<A?~$t[-5]?(10+substr_count($t,0)*(1-2*$t[-5]))%10:__ETIANMSURWDKGOHVF_L_PJBXCYZQ[bindec("1$t")]:" ";

Run as pipe with -nR or try it online.

\$\endgroup\$
0
\$\begingroup\$

ES6, 268 bytes

Uses ASCII encoding after mapping from a base36 representation of the morse to an index position. Not my best golf day, but it only took around 15 mins.

s=>s.split('00000').map(w=>String.fromCharCode.apply({},w.split('000').map(c=>"ahkn,225z,h9z,48n,11z,9h,1g5,5w5,nlh,2me5,,,,,,,,n,d1,1gd,39,1,9p,d9,2d,5,4mv,d3,ad,3b,t,1h3,15p,5w7,2l,l,7,2f,9j,af,1g7,1h1".split(',').indexOf(parseInt(c,2).toString(36))+48))).join(' ')

Easier to read (kinda):

s=>
s
.split('00000')
.map(w=>
	String.fromCharCode.apply({},
		w.split('000')
			.map(c=>
				"ahkn,225z,h9z,48n,11z,9h,1g5,5w5,nlh,2me5,,,,,,,,n,d1,1gd,39,1,9p,d9,2d,5,4mv,d3,ad,3b,t,1h3,15p,5w7,2l,l,7,2f,9j,af,1g7,1h1"
				.split(',')
				.indexOf(
					parseInt(c,2).toString(36)
				)+48)
			)
	).join(' ')

\$\endgroup\$
0
\$\begingroup\$

Wolfram Language (Mathematica), 288 bytes

Thought about reading in the data as binary from a file but that gets hard to explain. Base 36 seemed like a good compromise way to store the data efficiently lexically.

Takes a string of 0's and 1's as input. Does a series of replacements, starting with the runs of 7 zeros, then the runs of 3, then the longest binary letters down to the shortest. Order of replacement is important.

StringReplace[#,Thread@Rule[Join[{"0000000","000"},#~FromDigits~36~IntegerString~2&/@StringSplit@"ahkn 2me5 225z nlh h9z 5w7 5w5 5tj 4mv 48n 1h3 1h1 1gd 1g7 1g5 15p 11z d9 d3 d1 af ad 9p 9j 9h 3b 39 2l 2f 2d t n l 7 5 1"],Insert[Characters@" 09182Q7YJ3OZCX6P4GKBWLFV5MDRUHNASTIE","",2]]]&

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Wait, Mathematica doesn't have a morse code built-in? \$\endgroup\$ – Zacharý Nov 17 '17 at 1:24
  • \$\begingroup\$ Not yet! I checked. \$\endgroup\$ – Kelly Lowder Nov 17 '17 at 3:03
0
\$\begingroup\$

Perl 5, 195 bytes

194 bytes code + 1 for -p.

%h=map{$_,(A..Z,0..9)[$i++]}unpack"S26I2S7I","\xd5]u]\xddUw\xd7uww\xdd\xd7]WWwWwuwwwwwWwWUU\xd5uw\xdd\xdd";s/0{7}/ /g;s/(\d+?)(000|\b)/$h{oct"0b$1"}/ge

I couldn't get this working with just a standard packed binary string, I had to escape the higher-byte chars otherwise I'd be on 171, if anyone knows what I've missed, or why it's breaking that'd be great!

Try it online!

Explanation

The binary string is a packed list of the numbers that relate to the morse characters (101011101 - 349 for F etc) and this is zipped with the ranges A..Z,0..9 and used as a lookup. The s/// expressions replace all runs of seven 0s with space and then all runs of digits, separated with three 0s or word boundaries \b, with their corresponding key from the %h hash.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.