Shifty Eyes Shifting I's

Perl, 59 56 54 bytes

Includes +1 for -p

Run with the input on STDIN, e.g. perl -p shifty.pl <<< ">_> <_< >_< <_>"

shifty.pl:

s%^|<|(>)%y/iI/Ii/or$_=Ii;$1?s/i |i$/ i/:s/ i/i /%reg

Explanation

The control string alternates instructions for i and I and the rule is the same for both of them if you formulate them as:

  • < Move left if there is a space to the left
  • > Move right if there is a space or end of string to the right

So I'm going to swap i and I in the target string at each step so I only need to apply the rule to one letter ever. This is the y/iI/Ii/

I will walk the control string looking for < and > using a substitution which is usually the shortest way in perl to process something character by character. To avoid having to write $var =~ I want the control string in the perl default variable $_. And I also want an easy way to distinguish < from >. All this can be accomplished using

s%<|(>)%  code using $1 to distinguish < from > %eg

The target string I also want to manipulate using substitutions and for the same reason I want that in $_ too. $_ being two things at once seems impossible.

However I can have my cake and eat it too because the $_ inside the body of a substitution does not have to remain the same as the $_ being substituded. Once perl started substituting a string this string will not change even if you change the variable the string originally came from. So you can do something like:

s%<|(>)% change $_ here without disturbing the running substitution %eg

I want to replace the original $_ by the initial "Ii" only the very first time the substitution body gets executed (otherwise I keep resetting the target string). This replacement however also has to happen for an empty control string, so even for the empty control string the body needs to be executed at least once. To make sure the substition runs an extra time at the start of the control string (even for empty control strings) I change the substitution to:

s%^|<|(>)% change $_ here without disturbing the running substitution %eg

I will run the y/iI/Ii/ as the first thing inside the substitution code. While $_ is still the control string this won't yet contain any Ii, so if the transliteration indicates nothing was changed that is my trigger initialize $_:

y/iI/Ii/or$_=Ii

Now I can implement the actual moving of the letters. Since I start with a swap all moves should be done on i, not I. If $1 is set move i to the right:

s/i |i$/ i/

If $1 is not set move i to the left

s/ i/i /

Notice that at the start of the control string when I match ^ $1 will not be set, so it tries to move i to the left on the initial string Ii. This won't work because there is no space there, so the intial string remains undisturbed (this is why I put the () around > instead of <)

Only one problem remains: at the end of the outer substitution $_ is set to result of the outer substitution regardless of what you did to $_ inside the substitution body. So the target string with the proper placement of i and I gets lost. In older perls this would be a fatal flaw. More recent perls however have the r modifier which means "make a copy of the original string, do your substitution on that and return the resulting string (instead of number of matches)". When I use that here the result is that the modified command string gets discarded while the original $_ is not disturbed by perl and left after the substitution. However the disturbing I do on $_ is still done after perl left $_ alone. So at the end $_ will be the proper target string.

The -p option makes sure the original string is in $_ and also prints the final $_.


LittleLua - 178 Bytes

r()l=sw(I)o=1 D='.'f q=1,#l do i l[q]:s(1,1)=='>'t i z+1~=o t z=z+1 e else i z-1>0 t z=z-1 e e i l[q]:s(3)=='>'t o=o+1 else i o-1~=z t o=o-1 e e e p(D:r(z).."I"..D:r(o-z-1)..'i')

Straight forward implementation.

Ungolfed:

r()                             --call for input
l=sw(I)                         --Split input by spaces
o=1                             --Hold i position (z holds I position)
D='.'                           --Redundant character
f q=1,#l do                     --Foreach table entry
    i l[q]:s(1,1)=='>' t        --If the first eye points right
        i z+1~=o t z=z+1 e      --Verify no collision and move the I
    else
        i z-1>0 t z=z-1 e       --If it points left.. .same...
    e                           --yatta yatta...
    i l[q]:s(3)=='>' t
        o=o+1
    else
        i o-1~=z t o=o-1 e
    e
e
p(D:r(z).."I"..D:r(o-z-1)..'i')--String repeats to print correct characters.

What is LittleLua?

LittleLua is a work in progress to try to level the playing fields between my language of choice for these challenges and esoteric languages that often have extremely powerful built-ins.

LittleLua is a Lua 5.3.6 interpreter with an additional module (LittleLua.Lua), as well as function and module names shrunk. These changes will expand over the next day or two, until I'm happy, but as it stands several of the largest changes between LittleLua and a the standard Lua interpreter are:

Functions and modules are shrunk:

io.read() -> r() (Value stored in built in variable "I")
string -> s
string.sub -> s.s or stringvalue:s
etc.

Built in variables

LittleLua has several built in variables to shrink some tasks:

z=0
o=10
h1="Hello, World!"
h2="Hello, World"
h3="hello, world"
h4=hello, world!"
etc.

Built in Functions

Currently a depressingly small list, but here it is:

d(N) -> Prints NxN identity matrix
sw(str) -> Splits string at spaces and returns table of results
sc(str) -> Splits string at commas and returns table of results
sd(str) -> Removes white space from a string (not including tabs)
ss(str,n) -> Swap N characters from beginning and end of string
sr(str,n) -> Swap N characters from beginning and end of string retaining order
sd(str) -> Split string into array of characters
co(ta) -> Concatenate table with no delimiter
co(ta, delim) -> Concatenate table with delimiter: delim

Retina, 101 86

$
¶Ii
(`^¶

s`^>(.*)I( )?
$1$2I
s`^<(.*?)( )?I
$1I$2
s`^_>(.*)i
$1 i
s`^_<(.*?) ?i
$1i

Try it online

Saved 15 bytes thanks to daavko!

Takes input separated by newlines and outputs with the eyes separated by spaces.

Explanation:

I will explain stage by stage as usual. All of these stages are in Retina's Replace mode. That means the first line is a regular expression and the second line is a replacement string.

$
¶Ii

Add the initial Ii to the end of the input.

(`^¶

The backtick separates the stage from the options. The option character ( indicates that this stage is the start of a loop of stages to be executed repeatedly in order until a full cycle is completed without changing the input. Since this open parenthesis is never closed, all of the remaining stages are a part of this loop.

The actual stage is very simple, if the first character of the string is a newline then delete it. This is just to help make handling the empty input easier, otherwise it'd be golfier to add it on to the two last stages.

s`^>(.*)I( )?
$1$2I

Here, the option s causes the Regex metacharacter . to match newlines. This stage causes a leading > to match the I followed by an optional space. Then it replaces that match with the stuff after the >, followed by the optional space (so the empty string if the space couldn't be matched), and then the I.

s`^<(.*?)( )?I
$1I$2

This stage is very similar to the previous one, only the optional space is before the I, and the order and eye are reversed.

s`^_>(.*)i
$1 i

The handling of i is actually often simpler, because we don't have to worry about optionally adding or removing as i can always move right. For the i cases we match away the underscore as well as the greater/less than sign, but otherwise do similar logic. This one adds a space before the i.

s`^_<(.*?) ?i
$1i

Again similar to the above, but it deletes the character before the i if that character is a space, otherwise it only removes the emoticon.