You are given a string [A-Za-z|]+
that contains exactly one occurrence of |
denoting the current position of the cursor and a sequence of moves [><#]+
where:
>
: moves the cursor one position to the right<
: moves the cursor one position to the left#
: moves the cursor one position to the left, deletes the character to its right and shifts back any text after (simulates a backspace)
If the cursor is the first character of the string, moving left (<
) or deleting (#
) should keep the string unchanged. Similarly, if the cursor is the last character of the string, moving right (>
) should keep the string unchanged.
Your goal is to simulate the sequence of moves on the provided string.
Test cases
String | Moves | Expected result |
---|---|---|
This is a test| | #<<<<<######> | Tha| tes |
Curs|or end test | >>>>>>>>>>>>><> | Cursor end test| |
Cursor s|tart test | <<<<<<<<<<<>< | |Cursor start test |
Delete all| | ##############><<< | | |
| | >><##> | | |
Steps for the 1st test case
- Input:
This is a test|
- Moves:
#<<<<<######>
Steps:
Move | String |
---|---|
This is a test| (Input) | |
(#) | This is a tes| |
(<) | This is a te|s |
(<) | This is a t|es |
(<) | This is a |tes |
(<) | This is a| tes |
(<) | This is |a tes |
(#) | This is|a tes |
(#) | This i|a tes |
(#) | This |a tes |
(#) | This|a tes |
(#) | Thi|a tes |
(#) | Th|a tes |
(>) | Tha| tes (Output) |
If you want to see the intermediate steps for the other test cases you can check this spreadsheet. If you want to run the simulation on your own inputs, you can make a copy of the spreadsheet and change the corresponding fields in the 'simulator' tab.
This is code-golf so shortest answer in bytes wins.
20 Answers 20
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.
Perl 5 -p
, 71 bytes
%C=qw(< (.)\|/|1ドル/ > \|(.)/1ドル|/ # .\|/|/);eval join";s/",@C{<>=~/^|./g}
same length:
%C=qw(< s/()(.)\| > s/\|(.) # s/.\|);$"='/1ドル|2ドル/;';eval"@C{<>=~/$|./g}"
-
1\$\begingroup\$ Nice approach! I tried to do this myself and ended up with a pretty similar solution for 63: dom111.github.io/code-sandbox/… Main changes are using a list instead of a hash and using
cmp
to get0
,1
or-1
from"<"
and usinggetc
with awhile!eof
\$\endgroup\$Dom Hastings– Dom Hastings2024年07月23日 13:30:21 +00:00Commented Jul 23, 2024 at 13:30 -
1\$\begingroup\$ Worth noting that a port of @Neil's answer comes in at 57: dom111.github.io/code-sandbox/… \$\endgroup\$Dom Hastings– Dom Hastings2024年07月23日 13:30:54 +00:00Commented Jul 23, 2024 at 13:30
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
-
1\$\begingroup\$ 106 with regex \$\endgroup\$no comment– no comment2024年07月23日 23:05:59 +00:00Commented Jul 23, 2024 at 23:05
-
-
\$\begingroup\$ Nice suggestions! I'll refrain from posting a regex submission since Albert.Lang has a nice one now. \$\endgroup\$Jitse– Jitse2024年07月24日 09:04:58 +00:00Commented Jul 24, 2024 at 9:04
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
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
Inspired by other regex based answers.
-
1\$\begingroup\$ That's a crazy cyclic
[::2]
! For the second arg, looks liker"2円1円"[:ord(i)&6]
saves a byte \$\endgroup\$xnor– xnor2024年07月24日 08:45:53 +00:00Commented Jul 24, 2024 at 8:45
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ドル"))))
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
JavaScript (Node.js), 80 bytes
g=(x,[c,...d])=>c?g(x.replace(/(.?)\|(.?)/,c<g?'|2ドル':c>'='?'1ドル2ドル|':'|1ドル2ドル'),d):x
Uiua, 40 bytes
⍜⊙↻⊂@|∧⊃(+-⊸¬=@>|⍜⊙↻↘=@#⊙-⊙1):⊙:⊙▽⊃⊗⊸≠@|
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
-
\$\begingroup\$ Welcome to Code Golf, and nice first answer! \$\endgroup\$emanresu A– emanresu A2024年07月26日 02:00:56 +00:00Commented Jul 26, 2024 at 2:00
GNU C, (削除) 214 (削除ここまで) 208 bytes
- -6 bytes by replacing all
*(x+y)
byx[y]
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;}
-
\$\begingroup\$ Welcome to Code Golf, and nice first answer! Be sure to check out our Tips for golfing in C page for ways you can golf your program! It looks like this is 214 bytes, since you don't have to include the trailing newline. \$\endgroup\$emanresu A– emanresu A2024年07月25日 22:23:00 +00:00Commented Jul 25, 2024 at 22:23
-
\$\begingroup\$ Thanks, I'll check it out right away! \$\endgroup\$Knogger– Knogger2024年07月25日 22:28:17 +00:00Commented Jul 25, 2024 at 22:28
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)]
Modifies the input in-place
-
\$\begingroup\$ It looks like you can replace the
and
with==
\$\endgroup\$xnor– xnor2024年07月24日 08:31:54 +00:00Commented Jul 24, 2024 at 8:31 -
\$\begingroup\$ Or better, do
i%2>0<j==
\$\endgroup\$xnor– xnor2024年07月24日 08:37:31 +00:00Commented Jul 24, 2024 at 8:37 -
\$\begingroup\$ 90:
def f(s,o): for i in o:s.pop(j:=s.index('|'));i%2>0<j==s.pop(j-1);s[max(0,j+i%5-1):0]='|'
\$\endgroup\$no comment– no comment2024年07月24日 15:17:17 +00:00Commented Jul 24, 2024 at 15:17 -
1\$\begingroup\$ 87:
def f(s,o): for i in o:s[max(0,j+i%5-1):0]=s.pop(j:=s.index('|'))[i%2>0<j==s.pop(j-1)]
\$\endgroup\$no comment– no comment2024年07月24日 15:27:57 +00:00Commented Jul 24, 2024 at 15:27
Haskell, (削除) 133 (削除ここまで) 131 bytes
- -2 bytes thanks to @DLosc
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
- The string is split at | into two strings
- For each move the last character of the first string and / or the first of the second are modified (helper function h)
- The two strings are joined with a | in the mid
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
Charcoal, 50 bytes
×ばつ<⌕⮌θ|+#S≡ι<«¿θ⊞υ⊟θ»>«¿υ⊞θ⊟υ»≔∧θ⊟θηFθι|Wυ⊟υ
Try it online! Link is to verbose version of code. Explanation:
≔⪪S1θ
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.
×ばつ<⌕⮌θ|+#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.
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}
05AB1E, (削除) 35 (削除ここまで) 34 bytes
Çvā<VÐ'|kDy61.S+‚YÃUy6›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`
y6›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)
jq, 102 bytes
reduce.m[]as$m(.s;{">":sub("\\|(?<c>.)";.c+"|"),"<":sub("(?<c>.)\\|";"|"+.c),"#":sub(".\\|";"|")}[$m])
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.
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:
- On a
>
(ASCII 62), concatenate the first character from the right half (if any) to the left half; otherwise, take the tail of the left half. - On a
>
, take the tail of the right half; otherwise, on a<
(ASCII 60), concatenate the first character from the left half (if any) to the right half; otherwise, leave the right half unchanged. - Take the tail of the move-string.
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)))))
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
Uiua, 32 bytes
∧(⍜⊙⊜□しろいしかく⍚⨬⋅@|⇌±⊙⊸⦷⟜⍥⇌⊙"\W|")⊗⊙"#>"
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
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 worksTM. 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--;
}
}
}
}
-
\$\begingroup\$ 130 bytes \$\endgroup\$ceilingcat– ceilingcat2024年08月21日 06:33:08 +00:00Commented Aug 21, 2024 at 6:33
-
\$\begingroup\$ @ceilingcat nice stuff!! I'm not sure why
strcpy(q,q--)
didn't work for me in testing, I know I tried it - but it seems that the+1
after that is superfluous? That would give 128 assuming I'm not missing anything \$\endgroup\$Conor O'Brien– Conor O'Brien2024年08月21日 06:42:07 +00:00Commented Aug 21, 2024 at 6:42
[A-Za-z ]
, may we choose a different character to represent the cursor to avoid the|
choking RegEx based solutions? \$\endgroup\$