Homework: Morse code to English characters

218 Views Asked by At

In the following code only the first . or - are picked up and it doesn't match to any of the letters defined in the switch statements below. Any idea how I can change it to see the entire Morse letters?

#include <stdio.h>
#include<string.h>

int main (void){

    char str[20]=".-/.-/.-";
    int i;
    char letter;


//  printf("%s is %d long", str,strlen(str));


    for(i=0; i<strlen(str); i++){
    
        switch(str[i]){ 
                            /*switch statements set out decimal values for Hex digits*/
            case '.-':
                letter ='A';
                break;
            case '-...':
                letter = 'B';
                break;
            case '-.-.':
                letter = 'C';
                break;
            case '-..':
                return 'D';
                break;
            case '.':
                letter = 'E';
                break;
            case '..-.':
                letter = 'F';
                break;
            case '--.':
                letter = 'G';
                break;
            case '....':
                letter = 'H';
                break;
            case '..':
                letter = 'I';
                break;
            case '.---':
                letter = 'J';
                break;
            case '-.-.':
                letter = 'K';
                break;
            case '.-..':
                letter = 'L';
                break;
            case '--':
                letter = 'M';
                break;
            case '-.':
                letter = 'N';
                break;
            case '---':
                letter = 'O';
                break;
            case '.--.':
                letter = 'P';
                break;
            case '--.-':
                letter = 'Q';
                break;
            case '.-.':
                letter = 'R';
                break;
            case '...':
                letter = 'S';
                break;
            case '-':
                letter = 'T';
                break;
            case '..-':
                letter = 'U';
                break;
            case '...-':
                letter = 'V';
                break;
            case '.--':
                letter = 'W';
                break;
            case '-..-':
                letter = 'X';
                break;
            case '-.--':
                letter = 'Y';
                break;
            case '--..':
                letter = 'Z';
                break;
            default:
            printf("Something went wrong\n");
            break;
    }

}
printf("%d",letter);
}

I tried changing the single quotes '' I had initially for the Morese codes to double quotes "" for strings, but I can only pick up the first - or . in it either way.

5

There are 5 best solutions below

0
On

Another approach which may help you.

#include <stdio.h>
#include <string.h>

#define MAX_LETTER_COUNT 4

// Size's of below arrays must be equal
const char *morse_codes[] = {".-","-..."};  
const char alphabet[] = {'A','B'};    

const int morse_code_size = sizeof(morse_codes)/sizeof(*morse_codes);

int main() {
  char str[] = ".-/-.../.-/-...";
  char result[MAX_LETTER_COUNT+1];
  const char delim = '/';

  char *token = strtok(str, &delim);
  int letterCount = 0;
  while(token != NULL) {
    for(int i=0;i<morse_code_size;i++) {
      // compare morse codes with substrings
      if(!strcmp(morse_codes[i], token)) {
        // if equal add alpahabet char to result array
        // and increase letter count
        result[letterCount] = alphabet[i];
        letterCount++;
      }
    }
    token = strtok(NULL, &delim);
  }

  result[letterCount] = '\0';
  printf("%s", result);

  return 0;
}
1
On

Case can switch on '.-' yet these are (multi-) character constants and not 2,3,4 character strings. There are also a certainly a problem passed 4 characters, as needed with Morse code characters like digits and punctuation.

It is challenging to portably form the matching constant from a sub-string.

An alternative, similar to @Ted Lyngmo, is to form an index from the several '.', '-' into an index and look up the ASCII character in a table rather than a switch.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Morse code dit/dash to hash code.
#define M1(a)           ( 2 + ((a)=='-')* 1 + 0)
#define M2(a,b)         ( 4 + ((a)=='-')* 2 + M1((b)) - 2)
#define M3(a,b,c)       ( 8 + ((a)=='-')* 4 + M2((b),(c)) - 4)
#define M4(a,b,c,d)     (16 + ((a)=='-')* 8 + M3((b),(c),(d)) - 8)
#define M5(a,b,c,d,e)   (32 + ((a)=='-')*16 + M4((b),(c),(d),(e)) - 16)
#define M6(a,b,c,d,e,f) (64 + ((a)=='-')*32 + M5((b),(c),(d),(e),(f)) - 32)

// dit/dash string to hash code
int morse(size_t length, const char *s) {
  int m = 0;
  for (size_t i = 0; i < length; i++) {
    m *= 2;
    switch (s[i]) {
      case '-':
        m++;
        break;
      case '.':
        break;
      default:
        return -1;
    }
  }
  m += 1 << (length + 0);
  return m;
}

// Table size <= 2^(5+1) or 64
static const char morse_table[] = { //
    // Letters
    [M2('.', '-')] = 'A', //
    [M4('-', '.', '.', '.')] = 'B', //
    [M4('-', '.', '-', '.')] = 'C', //
    [M3('-', '.', '.')] = 'D', //
    [M1('.')] = 'E', //
    [M4('.', '.', '-', '.')] = 'F', //
    [M3('-', '-', '.')] = 'G', //
    [M4('.', '.', '.', '.')] = 'H', //
    [M2('.', '.')] = 'I', //
    [M4('.', '-', '-', '-')] = 'J', //
    [M3('-', '.', '-')] = 'K', //
    [M4('.', '-', '.', '.')] = 'L', //
    [M2('-', '-')] = 'M', //
    [M2('-', '.')] = 'N', //
    [M3('-', '-', '-')] = 'O', //
    [M4('.', '-', '-', '.')] = 'P', //
    [M4('-', '-', '.', '-')] = 'Q', //
    [M3('.', '-', '.')] = 'R', //
    [M3('.', '.', '.')] = 'S', //
    [M1('-')] = 'T', //
    [M3('.', '.', '-')] = 'U', //
    [M4('.', '.', '.', '-')] = 'V', //
    [M3('.', '-', '-')] = 'W', //
    [M4('-', '.', '.', '-')] = 'X', //
    [M4('-', '.', '-', '-')] = 'Y', //
    [M4('-', '-', '.', '.')] = 'Z', //
    // Digits
    [M5('.', '-', '-', '-', '-')] = '1', //
    [M5('.', '.', '-', '-', '-')] = '2', //
    [M5('.', '.', '.', '-', '-')] = '3', //
    [M5('.', '.', '.', '.', '-')] = '4', //
    [M5('.', '.', '.', '.', '.')] = '5', //
    [M5('-', '.', '.', '.', '.')] = '6', //
    [M5('-', '-', '.', '.', '.')] = '7', //
    [M5('-', '-', '-', '.', '.')] = '8', //
    [M5('-', '-', '-', '-', '.')] = '9', //
    [M5('-', '-', '-', '-', '-')] = '0', //
    };

long morse_convert(size_t size, char ascii[size], const char *morse_string) {
  if (size == 0) {
    return -1;  // Size too small.
  }
  size--;
  size_t i;
  for (i = 0; i < size; i++) {
    size_t offset = strspn(morse_string, ".-");
    if (offset == 0) {
      ascii[i] = '\0'; // No dot/dash pattern found.
      return -1;
    }
    int m = morse(offset, morse_string);
    if (m < 0 || (unsigned) m >= sizeof morse_table / sizeof morse_table[0]) {
      ascii[i] = '\0'; // Not in table
      return -1;
    }
    ascii[i] = morse_table[m];
    if (ascii[i] == 0) {
      return -1; // No matching character.
    }
    morse_string += offset;
    if (*morse_string == '\0') {
      i++;
      break;
    }
    if (*morse_string != '/') {
      ascii[i] = '\0'; // Unexpected separator.
      return -1;
    }
    morse_string++;
  }
  ascii[i] = '\0';
  if (*morse_string) {  // Destination too small.
    return -1;
  }
  return (long) i;
}

Test

int main(void) {
  char str[] = ".-/-.../-.-./-.././..-./--./..../../.---/"
      "-.-/.-../--/-./---/.--./--.-/.-./.../-/"
      "..-/...-/.--/-..-/-.--/--../"
      ".----/..---/...--/....-/...../-..../--.../---../----./-----";
  char dest[sizeof str / 2 + 1];
  int len = morse_convert(sizeof dest, dest, str);
  if (len < 0) {
    puts("Error");
  } else {
    printf("<%s>\n<%s>\n", str, dest);
  }
}

Output

<.-/-.../-.-./-.././..-./--./..../../.---/-.-/.-../--/-./---/.--./--.-/.-./.../-/..-/...-/.--/-..-/-.--/--../.----/..---/...--/....-/...../-..../--.../---../----./----->
<ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890>
1
On

Constructs such as '.-' are known as multicharacter constants, which have implementation defined values (of type int). While providing a scalar value that can be used in a switch & case, they are hardly portable, and rarely used.

By contrast, ".-" is a string (literal). As strings are not scalar values (rather comprised thereof), they cannot be used in a switch or case. To compare strings you can make use of strcmp (or strncmp).

The loop

for (i = 0; i < strlen(str); i++)

means this switch

switch (str[i])

is operating on a single character value at a time (one of '.', '-', or '/').

You will need to tokenize your initial string into substrings. This can be done destructively with strtok, or non-destructively with functions such as strspn / strcspn.

Each substring can then be compared against every possible Morse code string to find the associated character.


Here is a cursory (incomplete) implementation using strcspn and strncmp.

#include <stdio.h>
#include <string.h>

static char morse_to_repr(const char *seg, size_t length)
{
    static const struct {
        char repr;
        const char *morse;
    } set[] = {
        { 'A', ".-" },
        { 'B', "-..." },
        /* ... and so on */
    };

    if (!length)
        return ' ';

    for (size_t i = 0; i < (sizeof set / sizeof *set); i++)
        if (0 == strncmp(set[i].morse, seg, length))
            return set[i].repr;

    return '?';
}

int main(void)
{
    char string[] = ".-/.-/.-";
    size_t offset = 0;

    /* until we reach the null-terminating character */
    while (string[offset]) {
        /* find the distance to the next delimiting character */
        size_t length = strcspn(string + offset, "/");

        putchar(morse_to_repr(string + offset, length));

        /* advance the position to the next delimiting character */
        offset += length;
        /* step over the delimiting character,
         * if it is NOT the null-terminating character */
        if (string[offset])
            offset++;
    }

    putchar('\n');
}
AAA
5
On

You can't switch on strings in C. The case statement must be a constant expression, and a string literal does not count as such.

You could either switch on each individual letter (which would produce 5 levels of switches where -, . and \0 need to be handled) or you could do something simpler, like converting each letter to a numeric value:

int toValue(const char *m) {
    int value = 0;
    for (; *m; ++m) {
        value *= 3;
        switch (*m) {
            case '-':
                value += 2;
                break;
            case '.':
                value += 1;
                break;
            default:
                return -1;  // error
        }
    }
    return value;
}

And then switch on that:

Example:

char fromMorse(const char *m) {
    switch (toValue(m)) {
        case  5: return 'A';    // .-
        case 67: return 'B';    // -...
        case 70: return 'C';    // -.-.
        case 22: return 'D';    // -..
        case  1: return 'E';    // .
        case 43: return 'F';    // ..-.
        case 25: return 'G';    // --.
        case 40: return 'H';    // ....
        case  4: return 'I';    // ..
        case 53: return 'J';    // .---
        case 23: return 'K';    // -.-
        case 49: return 'L';    // .-..
        case  8: return 'M';    // --
        case  7: return 'N';    // -.
        case 26: return 'O';    // ---
        case 52: return 'P';    // .--.
        case 77: return 'Q';    // --.-
        case 16: return 'R';    // .-.
        case 13: return 'S';    // ...
        case  2: return 'T';    // -
        case 14: return 'U';    // ..-
        case 41: return 'V';    // ...-
        case 17: return 'W';    // .--
        case 68: return 'X';    // -..-
        case 71: return 'Y';    // -.--
        case 76: return 'Z';    // --..
        default: return -1;     // error
    }
}

I can't say if the actual Morse letters are correct. I found one error in your original (C and K are equal) that I corrected.

0
On

The illegitimacy of attempting to use a C string (an array) as the token of a case has been covered sufficiently in other answers.

Back in the day, to avoid 'n' discrete assignment statements, it was up to the coder to pre-compute translations such as "Morse code character sequence to integer value." With that mapping done, one (or two) look-up tables could be entered into code and tested. Below is the result of such an effort.

Both functions use loops to either convert a bit pattern for an ASCII character to the string of dots & dashes that is its Morse counterpart, or decompose a string of dots & dashes to a bit pattern that is the index of a particular ASCII character. The OP is looking for the unmorsify() version.

Note: In Morse, a single space separates message characters and a triple space separates words. strtok() will NOT allow one to distinguish these two cases.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

char *morsify( char c ) {
    unsigned char oct[] = {
//  NUL  SOH  STX  ETX  EOT  ENQ  ACK  BEL   BS   HT   LF   VT   FF   CR   SO   SI
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,

//  DLE  DC1  DC2  DC3  DC4  NAK  SYN  ETB  CAN   EM  SUB  ESC   FS   GS   RS   US
      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,

//   SP    !    "    #    $    %    &    '    (    )    *    +    ,    -    .    /
    001,0153,0122,   0,0211,   0, 050,0136, 066,0155,   0, 052,0163,0141,0125, 062,

//    0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ?
    077, 057, 047, 043, 041, 040, 060, 070, 074, 076,0170,0152,   0, 061,   0,0114,

//    @    A    B    C    D    E    F    G    H    I    J    K    L    M    N    O
   0132, 005, 030, 032, 014, 002, 022, 016, 020, 004, 027, 015, 024, 007, 006, 017,

//    P    Q    R    S    T    U    V    W    X    Y    Z    [    \    ]    ^    _
    026, 035, 012, 010, 003, 011, 021, 013, 031, 033, 034,   0,   0,   0,   0, 045,

//    `    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o
      0, 005, 030, 032, 014, 002, 022, 016, 020, 004, 027, 015, 024, 007, 006, 017,

//    p    q    r    s    t    u    v    w    x    y    z    {    |    }    ~  DEL
    026, 035, 012, 010, 003, 011, 021, 013, 031, 033, 034,   0,   0,   0,   0,   0,
    };

    uint8_t at = 0, morse = oct[ c ], m = 0x80;
    while( m && !(m & morse) ) m >>= 1;
    static char obuf[10];
    for( m >>= 1; m ; m >>= 1 )
        obuf[ at++] = "-."[!(m & morse)];
    obuf[ at ] = '\0';

    return obuf;
}

char *unmorsify( char *cp, char *c ) {
    char rev[] = {
        'x', ' ', 'E', 'T', 'I', 'A', 'N', 'M',  // 00
        'S', 'U', 'R', 'W', 'D', 'K', 'G', 'O',
        'H', 'V', 'F',   0, 'L',   0, 'P', 'J',  // 02
        'B', 'X', 'C', 'Y', 'Z', 'Q',   0,   0,
        '5', '4',   0, '3', ',', '_',   0, '2',  // 04
        '&',   0, '+',   0,   0,   0,   0, '1',
        '6', '=', '/',   0,   0,   0, '(',   0,  // 06
        '7',   0,   0,   0, '8',   0, '9', '0',
          0,   0,   0,   0,   0,   0,   0,   0,  // 10
          0,   0,   0,   0, '?',   0,   0,   0,
          0,   0, '"',   0,   0, '.',   0,   0,  // 12
          0,   0, '@',   0,   0,   0,'\'',   0,
          0, '-',   0,   0,   0,   0,   0,   0,  // 14
          0,   0, ';', '!',   0, ')',   0,   0,
          0,   0,   0, ',',   0,   0,   0,   0,  // 16
        ':',   0,   0,   0,   0,   0,   0,   0,
          0,   0,   0,   0,   0,   0,   0,   0,  // 20
          0, '$',   0,   0,   0,   0,   0,   0,  // 21
    };

    uint8_t m = 0x01; // seed
    while( *cp && ( *cp == '.' || *cp == '-' ) )
        m = (uint8_t)( (m << 1) + (*cp++ == '-') ); // dash is hi bit
    *c = rev[m];
    return cp;
}

int main( void ) {
    char *str =
        "'Twas brillig, and the slithy toves "
        "Did gyre and gimble in the wabe:";
    char obuf[2048], *at = obuf;

    puts( str );
    for( ; *str; str++ )
        if( *str == ' ' )
            at += sprintf( at, "  " );
        else
            at += sprintf( at, "%s ", morsify( *str ) );
    puts( obuf );

    getchar();

    for( char *cp = obuf; *cp; ) {
        if( cp[0] == ' ' )
            if( cp[1] == ' ' && cp[2] == ' ' ) {
                putchar( ' ' );
                cp += 3;
                continue;
            }
            else
                cp++;

        char c;
        cp = unmorsify( cp, &c );
        putchar( c );
    }
    puts( "" );

    return 0;
}

Result:

'Twas brillig, and the slithy toves Did gyre and gimble in the wabe:
.----. - .-- .- ...   -... .-. .. .-.. .-.. .. --. --..--   .- -. -..   - .... .   ... .-.. .. - .... -.--   - --- ...- . ...   -.. .. -..   --. -.-- .-. .   .- -. -..   --. .. -- -... .-.. .   .. -.   - .... .   .-- .- -... . ---...

'TWAS BRILLIG, AND THE SLITHY TOVES DID GYRE AND GIMBLE IN THE WABE:

As this uses only simple, low-level C code, my expectation is that the reader does not need it commented. Happy to answer any questions you may have, though.

It should be noted that there are subtle variations of Morse code. Adapting this "coder unfriendly" version may be an effort. It works as well as it works, but requires focus and meticulous testing if/when changes might be required.