Numbers for Letters

CJam, 58 57 54 51 50 49 bytes

Just when I wrote up the explanation, I noticed that one of the alternative 50 byte versions can be shortened by a byte...

q_el_eu&S-A,s--S%S*{i_32md\2*5-*48md@)A%'a+\?}%S*

Test it here.

50 byte solutions:

q_el_eu&S-A,s--S%S*{_A,s&\i_)A%'a+\32md\2*5-*?}%S*
q_el_eu&S-A,s--S%'`*{i32md:D;(_(2*(D*D3+A%'a+?}%S*
q_el_eu&S-A,s--S%'`*{i32md\(_@_@(2*(*\3+A%'a+?}%S*
q_el_eu&S-A,s--S%'`*{i32md\(_(2*(g@*_z3+A%'a+?}%S*

Explanation

q         e# Read input.
_el_eu&   e# Intersect a lower-case version with an upper-case version to remove
          e# all letters.
S-        e# Remove spaces from that string.
A,s-      e# Remove digit characters from that string. It now contains all the
          e# the characters from the input we should ignore.
-         e# Remove these characters from the input.
S%S*      e# Split on runs of spaces and join by spaces, collapsing multiple into one.
{         e# Map this block onto each character...
  i_      e#   Convert to character code and make a copy.
  32md    e#   Get divmod 32. Note that digits have character codes 32 + something,
          e#   the upper case letters have character codes 64 + n (where n is the 
          e#   absolute value we want), while lower case letters have codes 96 + n. 
          e#   So the div gives 2 or 3 to distinguish capitalisation (and 1 for digits) 
          e#   and the mod gives the correct absolute value for letters.
          e#   As it happens, the mod also gives 0 for spaces.
  \2*5-   e#   Pull up the div, duplicate, subtract 5. Turns 2 into -1 and 3 into 1. 
          e#   It also turns 1 (digits) into -3.
  *       e#   Multiply the mod by this sign.
          e#   We now have the correct result for everything but digits. Note that
          e#   the absolute value for digits is more than 26, and for everything
          e#   else it's less than 27.
  48md    e#   Get divmod 48. This gives div 0 and mod n for all correct results n.
          e#   For digits it gives div -1 and we don't care about the mod. We'll
          e#   use the div as a truthy/falsy value to select the right result.
  @)A%    e#   Pull up the other copy of the character code, increment 
          e#   (range 49..58), take modulo 10.
          e#   This gives 9 for 0 and n-1 for any other digit n.
  'a+     e#   Add to the character a.
  \?      e#   Select the correct result based on the div 48.
}%
S*        e# Join the resulting values by spaces.

This must be the first time, that CJam's modulo behaviour for negative values was useful for me.


JavaScript (ES6), 110 107 133 120 bytes

Take that, old me!

a=>[...a.replace(/[\W_]*?( ?)[\W_]*/g,'$1')].map(x=>(c=x.charCodeAt())<40?0:c<60?'jabcdefghi'[x]:c<91?64-c:c-96).join` `

There's potentially a lot more room for golfing, especially in the regexes nope, got that one down pretty well. Ungolfed version:

function f(a) {
  // Replaces each run of bad chars and spaces with
  // a space if it contained one, nothing otherwise:
  a = a.replace(/[\W_]*?( ?)[\W_]*/g, '$1');

  var b = a.split('');
  b = b.map(function(x) {
    var c = x.charCodeAt();
    if (c == 32)     // space
      return 0;
    else if (c < 60) // numbers
      return 'jabcdefghi'.charAt(x);
    else if (c < 91)
      return 64 - c; // uppercase
    else
      return c - 96; // lowercase
  });
  b = b.join(' ');
  return b;
}

Suggestions welcome!


Pyth, 50 49 bytes

jdm?>d26C+70ddm-xZd26:-z-z=Zs[_rG1dGjk.<UT1)" +"d

Try it out here.

Edit: restructured string sanitisation to ensure underscores are handled correctly. It even saved a byte too, yay!

This program creates a lookup string, which is used to sanitise the input. This is then mapped to the corresponding index in that string. Finally, any index greater than 26 is converted to the correct ASCII character.

                                                     Implicit: z=input(), d=' ', ,
                                                       k='', G=[a-z]
                              _rG1                   Reversed, capitalised alphabet
                                  d                  Single space
                                   G                 Lower case alphabet
                                    jk.<UT1          '1234567890'
                            s[             )         Concatenate the 4 previous statements
                          =Z                         Store in Z
                        -z                           Setwise difference of input and above
                                                       (to get all illegal characters)
                      -z                             Setwise difference of input and illegal chars
                     :                      " +"d    Regex replace to lose multiple spaces
              m                                      Map the above over d:
                xZd                                    Get index of d in Z
               -   26                                  Subtract 26
  m                                                  Map the above over d:
   ?>d26                                               If d > 26
        C+70d                                            Convert (d+70) to ASCII
             d                                         Otherwise, select d
jd                                                   Join on spaces and print

Previous version, which made use of \W regex, at 50 bytes:

jdm?>d26C+70ddm-xs[_rG1\ Gjk.<UT1)d26::z"\W"d" +"d