CJam, 44 42 40 bytes
qN+ee_{Xa/~\+XW=eu__el=!\'@-*m<Xa+}fXWf=
Output contains a trailing linefeed.
Test it here.
Explanation
Instead of moving the letters through the string, I repeatedly remove a letter, rotate the string accordingly, and then reinsert the letter. There's one catch for doing this: we need to be able to distinguish the beginning of the string from the end of the string (which we can't after a simple rotation). That's why we insert a linefeed at the end as a guard (letter before the linefeed is the end of the string, letter after it is the beginning). The bonus is that this automatically returns the final string to the correct rotation where the linefeed actually is at the end of the string.
lN+ e# Read input and append a linefeed.
ee e# Enumerate the array, so input "bob" would become [[0 'b] [1 'o] [2 'b] [3 N]]
e# This is so that we can distinguish repeated occurrences of one letter.
_{ e# Duplicate. Then for each element X in the copy...
Xa/ e# Split the enumerated string around X.
~ e# Dump the two halves onto the stack.
\+ e# Concatenate them in reverse order. This is equivalent to rotating the current
e# character to the front and then removing it.
XW= e# Get the character from X.
eu e# Convert to upper case.
_ e# Duplicate.
_el=! e# Check that convert to lower case changes the character (to ensure we have a
e# letter).
\'@- e# Swap with the other upper-case copy and subtract '@, turning letters into 1 to
e# 26 (and everything else into junk).
* e# Multiply with whether it's a letter or not to turn said junk into 0 (that means
e# everything which is not a letter will be moved by 0 places).
m< e# Rotate the string to the left that many times.
Xa+ e# Append X to the rotated string.
}fX
Wf= e# Extract the character from each pair in the enumerated array.
To see why this ends up in the right position, consider the last iteration of the hi*bye
example. After we've processed the e
, the enumerated string is in this position:
[[4 'y] [6 N] [2 '*] [0 'h] [1 'i] [3 'b] [5 'e]]
First, we split around the linefeed and concatenate the parts in reverse order:
[[2 '*] [0 'h] [1 'i] [3 'b] [5 'e] [4 'y]]
The linefeed would now be either at the beginning or the end of this string. But since the linefeed is just a guard that marks the end of the string, this means that the characters are actually in the right order. Now the linefeed is not a letter, so that the array is not rotated at all. Thus, when we append the linefeed, it goes where it belongs, and everything is in the order we're looking for:
[[2 '*] [0 'h] [1 'i] [3 'b] [5 'e] [4 'y] [6 N]]
Some additional results if someone wants to compare longer test cases:
Hello, World!
,W oeHlo!lrld
Programming Puzzles & Code Golf
ago fgliPomomnrr elP& uC dezzsG
The quick brown fox jumps over the lazy dog
t eg chbi ko qfTounyzrj omw epx ueoahs rlvd
abcdefghijklmnopqrstuvwxyz
aqbrcdsetfguhivjwklxmnyozp
zyxwvutsrqponmlkjihgfedcba
abcdefghijklmnopqrstuvwxyz
I like that last one. :)