Letters, Get Moving!

Given a string you must move each letter (starting from the first letter) by its position in the alphabet. If you reach the end of the string you must wrap around. Non-letters don't need to be moved.

Example:

Dog

D is the fourth letter in the alphabet so we move it four spots to the right. After wrapping around, that changes the string to oDg. o is the 15th letter, (15 mod 3) = 0, so it does not move. g is the 7th letter - (7 mod 3) = 1, so the string becomes goD.

hi*bye

• h is the 8th letter, move it 8 spots - hi*bye => i*hbye
• i is the 9th letter, move it 9 spots - i*hbye => *hbiye
• b is the 2nd letter, move it 2 spots - *hbiye => *hiybe
• y is the 25th letter, move it 25 spots - *hiybe => *hibye
• e is the 5th letter, move it 5 spots - *hibye => *hibey

Non-letters don't need to be moved, but they still take up space.

• cat => tca
• F.U.N => .F.NU
• mississippi => msiisppssii
• Do we have to do a standalone program or a function is enough? Also, do we have to print the string? – Katenkyo Dec 14 '15 at 8:51
• What characters can appear in the input? Printable ASCII? Linefeeds? Any ASCII? Any Unicode? – Martin Ender Dec 14 '15 at 9:44
• Also a test case with repeated letters would be good. – Martin Ender Dec 14 '15 at 10:00
• @Martin Any ASCII. – geokavel Dec 14 '15 at 15:31
• @Katenkyo function is allowed. If you are using a function, then output is the return value. – geokavel Dec 14 '15 at 15:33

CJam, 4442 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. :)

• Pyth needs chop list on list. – isaacg Dec 14 '15 at 23:04
• @isaacg Nah, I'm sure it doesn't. ;) – Martin Ender Dec 14 '15 at 23:11
• Could you make it so it supports multi-line strings? – geokavel Dec 15 '15 at 0:01
• @geokavel Oh right, fixed. – Martin Ender Dec 15 '15 at 13:34
• The sith is pleased, Darth Büttner. – geokavel Dec 21 '15 at 3:14

Ruby 125 130 132 139 bytes

->q{a=q.chars.map{|c|[c,c=~/[a-z]/i&&c.ord%32]}
while i=a.index{|c,s|s}
c,s=a.delete_at i
a.insert (i+s)%q.size,[c]
end
a*''}


Online demo with tests: http://ideone.com/GYJm2u

The initial (ungolfed version): http://ideone.com/gTNvWY

Edit: Big thanks to manatwork for his suggestions!

Edit 2: fixed character count (I was initially counting CRLF line endings.)

• Just barely tested: c.upcase.ord-64c.ord%32. – manatwork Dec 14 '15 at 10:50
• @manatwork That works fine, thanks! – Cristian Lupascu Dec 14 '15 at 11:35
• Looking again… Wait! a.join??? Who are you and what have you done with w0lf? He would certainly write it as a*''. – manatwork Dec 14 '15 at 11:42
• @manatwork :) I was so upset about having a while ... end in my code that I forgot to do that. Thanks for noticing! – Cristian Lupascu Dec 14 '15 at 11:44
• can't you turn that while ... end into (...)while ...? – Martin Ender Dec 14 '15 at 12:21

Python 3, 278275273270260258249248243 238 bytes

I should really golf this down better, but here is my solution, with thanks to katenkyo for his help with the logic, and to Cyoce and Mego for their help with the golfing.

Edit: At last, I got it down to one comparison statement. WOO! (And yes, I could move that z=-z into a,m=m,a bit, but that doesn't save bytes and it muddled the code more than I thought was necessary)

Edit: Byte count was off.

def m(s):
l=len(s);r=range(l);p=[[i,s[i]]for i in r]
for i in r:
if s[i].isalpha():
a=p[i][0];p[i][0]=m=(a+ord(s[i])%32)%l;z=1
if a>m:a,m=m,a;z=-z
for j in r:p[j][0]-=z*(j!=i)*(a<=p[j][0]<=m)
return''.join(dict(p).values())


Ungolfed:

def move(string):
length = len(string)
places = [[i,string[i]]for i in range(length)]
for index in range(length):
char = string[index]
if char.isalpha():
a = places[index][0]
mov = (a + ord(char)%32) % length
places[index][0] = mov
for j in range(length):
k = places[j][0]
if a <= k <= mov and j!=index:
places[j][0]-=1
elif mov <= k <= a and j != index:
places[j][0]+=1
return''.join(dict(places).values())

• I *believe* that p[j][0] can be reduced by setting J=p[j]; at the beginning, then replacing instances of p[j][0] with P[0] – Cyoce Dec 17 '15 at 5:10
• @Cyoce I think the trouble is I need to edit p directly, and not a variable that had p[j] assigned to it. Also, if you look at my revision history, I did have a variable k = p[j][0] for a<=k<=m comparisons, but it turned out that dropping k was better because I saved more bytes on indents from the extra line to set k than I saved by using k. – Sherlock9 Dec 17 '15 at 5:58