g | x | w | all
Bytes Lang Time Link
065sed 4.2.2 E240724T163625ZJordan
134C gcc240821T034607ZConor O&
153tinylisp 2240819T194750ZDLosc
032Uiua240819T193907ZjanMakos
131Haskell240729T214810ZDPD-
096StackControl 1.1240805T191746ZШвеев Ал
102jq240805T114251ZGammaFun
086Python240724T043605ZMukundan
208GNU C240725T221756ZKnogger
092Zsh240724T174928ZGammaFun
040Uiua240724T170308ZjanMakos
120Google Sheets240724T111238Zz..
092Python240724T062044ZAlbert.L
03405AB1E240723T130521ZKevin Cr
193Setanta240723T223355Zbb94
050Charcoal240723T215243ZNeil
080JavaScript Node.js240723T100159Zl4m2
071Perl 5 p240723T093221Zovs
053Retina 0.8.2240723T094301ZNeil
137Python 3.8 prerelease240723T091424ZJitse

sed 4.2.2 -E, 65 bytes

The input string and sequence of moves should be separated by a tab. Code contains literal tab characters (which SE has replaced with spaces below, alas).

:
s/(.)?\|(.* )</|\1\2/
s/\|(.)?(.* )>/\1|\2/
s/.?\|(.*   )#/|\1/
t

Try it online!

C (gcc), 134 bytes

j;f(c,i,q)char*c,*i,*q;{for(q=strchr(c,'|');*i;i++)*i%2?q-c?strcpy(q-1,q),q--:7:*(q-=j=q-c|*i%3?1-*i%3:0)?j?*q^=q[j]^=*q^=q[j]:0:q--;}

Try it online! Utilizes UB, but if it works, it works™. Reusable function which takes two char* inputs, and outputs by modifying its first parameter. All char* are NULL-terminated. Uses K&R syntax to declare the function, which also declares (but does not accept for the purposes of this challenge) a third char* parameter.

Extra test cases generated using this script, which (arbitrarily) uses Jitse's Python answer as a ground truth.

Experience

I find golfing conditional code somewhat challenging, so I wanted to share some progress for the main conditional deciding whether or not we should move the cursor. I initially had:

j=1-*i%3;q==c&&j>0?j=0:1; // 25 bytes
j=*i%3;j=q==c&&!j?0:1-j;  // 24 bytes (flips j's sign, refactored following code)
j=*i%3;j=q-c||j?1-j:0;    // 22 bytes
j=*i%3;j=q-c|j?1-j:0;     // 21 bytes (we don't need short circuiting)
j=q-c|*i%3?1-*i%3:0;      // 20 bytes (cheaper to reuse *i%3)

This last line also allows us to inline *(q-=j) that we had to use when we had multiple statements to *(q-=j=...).

Readable and commented version

j; // temp integer used to represent cursor movement
f(c, i, q)
    char *c, // the text to operate on
         *i, // cursor instructions
         *q; // dummy temp buffer
{
    // q is a pointer to our cursor
    for(q = strchr(c, '|'); *i; i++) {
        // decide between '#' and '<>'
        // '<' = 60    '>' = 62    '#' = 35
        // 60 % 2: 0   62 % 2: 0   35 % 2: 1
        if(*i % 2) {
            // delete
            if(q - c) {
                // only delete if our cursor is not at the start
                strcpy(q - 1, q); //shift characters after cursor left
                q--; // update the cursor's position to reflect shift
            }
        }
        else {
            // move
            // '<' = 60   '>' = 62
            // 60 % 3: 0  62 % 3: 2
            if(q - c | *i % 3) {
                // if we are at the start (q - c), only execute '>' moves
                // if we are not at the start, execute any cursor moves*
                j = 1 - *i % 3;
            }
            else {
                // otherwise, the cursor will not move
                j = 0;
            }
            // move the cursor pointer accordingly
            q -= j;
            if(*q) {
                // *we check to see if the cursor result points at a null byte
                if(j) {
                    // only swap if we actually moved, since the XOR trick
                    // zeroes *q if *q and q[j] are the same address
                    
                    // if *q is not null, we execute the cursor movement
                    // XOR swap trick thanks to Karl Napf
                    // https://codegolf.stackexchange.com/a/111495/31957
                    *q ^= q[j] ^= *q ^= q[j];
                    // even though j is -1 or 1, we can use q[j] since
                    //   q[j] <=> *(q + j)
                    // thus, q[-1] is the char to the left of the cursor
                }
            }
            else {
                // *q is null, we've moved too far right, and must undo,
                // lest our null-terminated string be ruined
                q--;
            }
        }
    }
}

tinylisp 2, 153 bytes

(d G(\(A M B)(: R(=(h M)62)(? M(G(? R(,(] 1 B)A)(t A))(t M)(? R(t B)(,(](=(h M)60)A)B)))(,(~ A)(,"|"B
(\(S M)(: I(first-index S 124)(G(~(] I S))M(t([ I S

The first line defines a helper function; the second line is an anonymous function that takes the starting string and the string of moves and returns the result string. Try It Online!

Ungolfed/explanation

The main function splits the string into two halves at the | (ASCII 124) and passes them with the move-string to the helper function, reversing the left half so the business end is in front:

(def apply-moves
 (lambda (string moves)
  (let pipe-index (first-index string 124)
   (_apply-moves
    (reverse (take pipe-index string))
    (tail (drop pipe-index string))
    moves))))

The helper function recurses while there are moves remaining:

When the move-string is empty, reverse the left half and concatenate it to | and the right half.

(def _apply-moves
 (lambda (left right moves)
  (if moves
   (_apply-moves
    (if (= (head moves) 62)
     (concat (take 1 right) left)
     (tail left))
    (if (= (head moves) 62)
     (tail right)
     (if (= (head moves) 60)
      (concat (take 1 left) right)
      right))
    (tail moves))
   (concat
    (reverse left)
    (concat "|" right)))))

Uiua, 32 bytes

∧(⍜⊙⊜□⍚⨬⋅@|⇌±⊙⊸⦷⟜⍥⇌⊙"\W|")⊗⊙"#>"

Try it online!

I am not editing my previous answer because this solution is radically different.

∧(⍜⊙⊜□⍚⨬⋅@|⇌±⊙⊸⦷⟜⍥⇌⊙"\W|")⊗⊙"#>"
                          
                          ⊗⊙"#>" # enumerate the instructions
∧(                       ) # for each instruction:
                   ⊙"\W|" # make a pattern string with `|`
                          # and wildcard (like `*` in regex)
                ⟜⍥⇌ # reverse the pattern if instruction is `>`
             ⊙⊸⦷ # find all matches of the pattern
  ⍜⊙⊜□⍚          # operate on each match
       ⨬    ± # if instruction is `#`
        ⋅@| # delete preceding character
           ⇌ # else move the cursor

Haskell, 133 131 bytes

f=(intercalate"|".).foldl h.splitOn"|"
h[x,y:z]'>'=[x++[y],z]
h x@[[],_]_=x
h[x,y]'<'=h[x,last x:y]'#'
h[x,y]'#'=[init x,y]
h x _=x

Try it online!

  1. The string is split at | into two strings
  2. For each move the last character of the first string and / or the first of the second are modified (helper function h)
  3. The two strings are joined with a | in the mid

StackControl 1.1, 96 characters

So basicly i unpack string on to stack and move on it directly, a lot of space get taked to prevent cursor go of the edge of the stack

[0 0R 0 0⟧←←←:"|"⊗⍃⇆("|"≠)⊚⍄R⇆(→⇆)⟲(:"<"=(,:#→¿←)(:">"=(,→:#←¿)(:"#"=(,:#,?)?)⁇)⁇)∴"|"]→⟦⇆⊍⌦⌦⌫⌫W

jq, 102 bytes

reduce.m[]as$m(.s;{">":sub("\\|(?<c>.)";.c+"|"),"<":sub("(?<c>.)\\|";"|"+.c),"#":sub(".\\|";"|")}[$m])

Try it online!

Input as {"s":"the| current buffer","m":[">","<","#","#"]}. The TIO link splits the motions in the header for testing convenience.

On each motion, compute each possible change as values in a JSON object, then select the correct result.

reduce .m[] as $m (  # Repeat for each motion, where the motion is bound to $m
    .s;              # Initial input is the string
    {                # Replace current input with...
      ">": sub("\\|(?<c>.)";.c+"|"),
      "<": sub("(?<c>.)\\|";"|"+.c),
      "#": sub(".\\|";"|")
     }[$m]           # ...value at key $m
)

If the motions need to be passed as a single string, then +5 bytes by using reduce(.m/"")[]as $m instead.

Python, 100 96 87 86 bytes

-4 bytes thanks to @xnor
-9 bytes thanks to @no comment

def f(s,o):
 for i in o:s[(j+i%5or 1)-1:0]=s.pop(j:=s.index('|'))[i%2>0<j==s.pop(j-1)]

Attempt This Online!

Modifies the input in-place

GNU C, 214 208 bytes

char*c(char*s,char*i){char*r=malloc(strlen(s)),*z,o,t;r=strcpy(r,s);for(;*i;++i){z=strchr(r,'|');o=(*i==60&&z-r)?-1:(*i==62&&z[1])?1:0;t=*z;*z=z[o];z[o]=t;if(*i==35&&z-r)memmove(z-1,z,strlen(z)+1);}return r;}

Try it on programiz.com

Zsh, 103 92 bytes

Saved 11 bytes using r[1]= to remove the first character of $r and l[-1]= to remove the last character of $l.

IFS=\|
read l r
for m;case $m {\<)r=$l[-1]$r l[-1]=;;\>)l+=$r[1] r[1]=;;*)l[-1]=;}
<<<$l\|$r

Try it online! Try it online!

Takes string on stdin and motions as argv.

IFS=\|    # set internal field separator for reading string
read l r  # read string from stdin, splitting on $IFS into $l and $r
for m;    # motions as separate arguments
  case $m {
    \<)
      r=$l[-1]$r l[-1]= ;;
    \>)
      l+=$r[1] r[1]= ;;
    *)     # catchall, rather than escaping \#
      l[-1]=
  }
<<<$l\|$r

Uiua, 40 bytes

⍜⊙↻⊂@|∧⊃(+-⊸¬=@>|⍜⊙↻↘=@#⊙-⊙1):⊙:⊙▽⊃⊗⊸≠@|

Try it out!

Explanation

⊙▽⊃⊗⊸≠@|      # Get the (0 indexed) position of the cursor `|`
              # and remove it from the string
:⊙:           # Rearrange items before looping
∧⊃(           # For each character in the moves string do: 
  +-⊸¬=@>     # Add 1 to the cursor position if the move is `>`
              # otherwise subtract 1
| ⍜⊙↻↘=@#⊙-⊙1 # If the move is `#` backspace remove the character
              # 1 index before the cursor position 
)             # End loop
⍜⊙↻⊂@|        # Insert the cursor back into the string at cursor position

Google Sheets, 120 bytes

Expects the string in A2 and the moves in B2:

=REDUCE(A2,SEQUENCE(LEN(B2)),LAMBDA(a,i,REGEXREPLACE(a,"(.?)\|(.?)",SWITCH(MID(B2,i,1),">","$1$2|","<","|$1$2","|$2")))) 

enter image description here

Python, 92 bytes

-1 thanks to @xnor

lambda s,c:[s:=re.sub((2*"((\.|))")[i<"="::2],r"\2\1"[:ord(i)&6],s)for i in c][-1]
import re

Attempt This Online!

Python, 93 bytes

lambda s,c:[s:=re.sub((2*"((\.|))")[i<"="::2],r"\2"+r"\1"*(i>"#"),s)for i in c][-1]
import re

Attempt This Online!

Inspired by other regex based answers.

05AB1E, 35 34 bytes

Çvā<VÐ'|kDy61.S+‚YÃUy₆›iXèë'|æ}RXǝ

Inputs in the order \$moves,string\$.

Try it online or verify all test cases or see all intermediate steps by adding a trailing =.

Explanation:

Ç                 # Convert the first (implicit) input-moves to a list of codepoint-integers
 v                # Pop and loop over each codepoint `y`:
  ā               #  Push a list in the range [1,length] (without popping)
                  #  (using the implicit second input-string in the first iteration)
   <              #  Decrease it to 0-based range [0,length)
    V             #  Pop and store it in variable `Y`
  Ð               #  Triplicate the current string
   '|k           '#  Pop one, and get the (0-based) index of "|"
      D           #  Duplicate this index
       y61.S      #  Compare the current codepoint `y` with 61
                  #  (1 if >61 and -1 if <61: ">" is 1 and "<"/"#" are -1)
            +     #  Add that to the duplicated index
             ‚    #  Pair the two together
              YÃ  #  Only keep indices that are in range based on `Y`
                U #  Pop and store this pair (or singleton) in variable `X`
  y₆›i            #  If `y` is an arrow (codepoint > 36):
      Xè          #   Get the character(s) of the string at index/indices `X`
     ë            #  Else:
      '|æ        '#   Push pair ["","|"] (powerset of "|")
     }R           #  After the if-else statement: Reverse the pair
       Xǝ         #  Insert it/them at index/indices `X` back into the string
                  # (after the loop, the result is output implicitly)

Setanta, 193 bytes

gniomh(s,m){s=roinn@s("|")t:=s[1]s=s[0]le i idir(0,fad@m){i=m[i]u:=fad@s ma i=="<"&u{t=s[-1]+t s=cuid@s(0,u-1)}ma i==">"&fad@t{s+=t[0]t=cuid@t(1,fad@t)}ma i=="#" s=cuid@s(0,u-1)}toradh s+"|"+t}

Try on try-setanta.ie

Charcoal, 50 bytes

≔⪪S¹θF⁺×<⌕⮌θ|⁺#S≡ι<«¿θ⊞υ⊟θ»>«¿υ⊞θ⊟υ»≔∧θ⊟θηFθι|Wυ⊟υ

Try it online! Link is to verbose version of code. Explanation:

≔⪪S¹θ

Input the text string and split it into a list of characters. At this point the program's cursor is still at the end of the string.

F⁺×<⌕⮌θ|⁺#S≡ι

Find the number of characters after the |, and prefix cursor commands to move the cursor back to the | and delete it to the command string, then loop over the resulting commands, switching over each character.

<«¿θ⊞υ⊟θ»

For a < try to move a character from the text string to the predefined empty list.

>«¿υ⊞θ⊟υ»

For a > try to move a character back to the text string.

≔∧θ⊟θη

Otherwise just try to delete a character from the text string.

Fθι|Wυ⊟υ

Reconstitute the string from the lists.

JavaScript (Node.js), 80 bytes

g=(x,[c,...d])=>c?g(x.replace(/(.?)\|(.?)/,c<g?'|$2':c>'='?'$1$2|':'|$1$2'),d):x

Try it online!

Perl 5 -p, 71 bytes

%C=qw(< (.)\|/|$1/ > \|(.)/$1|/ # .\|/|/);eval join";s/",@C{<>=~/^|./g}

Try it online!

same length:

%C=qw(< s/()(.)\| > s/\|(.) # s/.\|);$"='/$1|$2/;';eval"@C{<>=~/$|./g}"

Try it online!

Retina 0.8.2, 53 bytes

+`\|(.?)(.*	)>|(.?)\|(.*	)<|.?\|(.*	)#
$1|$2$3$4$5
	

Try it online! Link includes test cases. Takes input separated by tabs (normally would have used newlines but that makes writing a test suite harder). Explanation: The replacement stage simply shuffles the | around depending on the next operation, with the + prefix repeating until no more operations remain, then finally the tab is deleted.

Python 3.8 (pre-release), 137 bytes

f=lambda s,n=0,*m:n and f([(x:=s[:(c:=s.find('|'))and~-c])+'|'+s[c-1:c]+s[c+1:],0,s[:c]+s[c+1:c+2]+'|'+s[c+2:],x+s[c:]][ord(n)%4],*m)or s

Try it online!