Translate Morse code based on tone duration




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


  • 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.


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


  • 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






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


Posted 2017-11-16T11:47:35.720

Reputation: 7 912

Can output have trailing whitespace? – Brian J – 2017-11-16T17:24:52.193



APL (Dyalog), 65 62 60 57 bytes

-3 thanks to ngn.

Tacit prefix function.

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


Posted 2017-11-16T11:47:35.720

Reputation: 37 779

5Wow, never knew Dyalog had a builtin for morse code. – Zacharý – 2017-11-16T16:51:08.553

@Zacharý There are many, many builtins in the dfns. – Erik the Outgolfer – 2017-11-16T18:33:47.857

@Zacharý Always check dfns!

– Adám – 2017-11-17T09:33:52.780

You're linking to an older version. – Erik the Outgolfer – 2017-11-17T17:44:50.447

A BF function ... >_<, wow. – Zacharý – 2017-11-17T18:26:00.650

shorter: ⎕io←0 ⋄ morse'/|[-.]+'⎕s'&'∘(⊃∘'/. -'¨6|'1+|(00)+'⎕s 1) – ngn – 2017-11-18T18:24:11.090

@ngn ⎕io←0 doesn't count, but ⎕CY'dfns' does. Still, your golf brings this down to 57 bytes. – Erik the Outgolfer – 2017-11-19T11:26:16.723

@ngn That is such a different (an ingenious) answer that you should post that separately. – Adám – 2017-11-19T20:50:10.543

@Adám well, it's still regexes and a call to the dfns ws... I derived it from yours. – ngn – 2017-11-20T00:01:04.997


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!


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!


Posted 2017-11-16T11:47:35.720

Reputation: 19 246


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.


Posted 2017-11-16T11:47:35.720

Reputation: 11 099

Very nicely done. – Reinstate Monica -- notmaynard – 2017-11-16T18:10:58.850

2I'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. – Jordan – 2017-11-17T18:23:36.653


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.

Colera Su

Posted 2017-11-16T11:47:35.720

Reputation: 2 291

1I 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')) – Alex Varga – 2017-11-17T04:06:58.427

@AlexVarga Nice usage of Python 2 filter! – Colera Su – 2017-11-17T04:30:36.943


Jelly, 67 62 bytes

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

Try it online!

Erik the Outgolfer

Posted 2017-11-16T11:47:35.720

Reputation: 38 134

Wait, link numbering wraps around? – Zacharý – 2017-11-16T17:34:06.270

@Zacharý Yes it does, line-links have modular indices. – Erik the Outgolfer – 2017-11-16T18:09:21.343


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")
A &="!ETIANMSURWDKGOHVF!L!PJBXCYZQ!!54!3!!!2!!!!!!!16!!!!!!!7!!!8!90"(c)
A+=" "
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!

Brian J

Posted 2017-11-16T11:47:35.720

Reputation: 653

1You 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. – recursive – 2017-11-16T23:42:49.893

@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? – Brian J – 2017-11-17T14:24:19.000

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. :) – recursive – 2017-11-17T16:34:13.410

And then you can change &= to +=, and remove another two spaces. – recursive – 2017-11-17T16:35:11.270

@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. – Brian J – 2017-11-17T16:45:22.707


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``


Rick Hitchcock

Posted 2017-11-16T11:47:35.720

Reputation: 2 461


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.


Posted 2017-11-16T11:47:35.720

Reputation: 31


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 :

// Building an array $a with every Morse letter representation (1=dot, 2=dash) and flip it
                // 01234
                // 56789
                // ABCDEFG
                // HIJKLM
                // NOPQRST
                // 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


Posted 2017-11-16T11:47:35.720

Reputation: 351


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.

Olivier Grégoire

Posted 2017-11-16T11:47:35.720

Reputation: 10 647

1can shave off a few by using Long.toString(x[i],2) instead of Integer.toString(x[i],2) – Jeutnarg – 2017-11-16T23:35:58.360


GNU sed, 261 + 1 = 262 bytes

+1 byte for -r flag.

s/;/ /g

Try it online!


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.


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____).


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


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

s/;/ /g


Posted 2017-11-16T11:47:35.720

Reputation: 5 001


Jelly, 67 bytes


Try it online!


Posted 2017-11-16T11:47:35.720

Reputation: 26 575


Retina, 144 138 130 103 bytes



Try it online! Link includes test cases. Explanation:


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.


Assume that all characters are Es.


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).


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.


Posted 2017-11-16T11:47:35.720

Reputation: 95 035


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``



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


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).


Posted 2017-11-16T11:47:35.720

Reputation: 111 334

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? – jayprich – 2018-09-27T11:15:23.807

@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$. – Arnauld – 2018-09-27T11:28:01.317


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!


Posted 2017-11-16T11:47:35.720

Reputation: 7 671


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.


Posted 2017-11-16T11:47:35.720

Reputation: 13 814


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):

 ).join(' ')


Posted 2017-11-16T11:47:35.720

Reputation: 151


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!

Kelly Lowder

Posted 2017-11-16T11:47:35.720

Reputation: 3 225

Wait, Mathematica doesn't have a morse code built-in? – Zacharý – 2017-11-17T01:24:08.057

Not yet! I checked. – Kelly Lowder – 2017-11-17T03:03:12.193


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!


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.

Dom Hastings

Posted 2017-11-16T11:47:35.720

Reputation: 16 415