g | x | w | all
Bytes Lang Time Link
1825Vyxal240320T124248Zlyxal
02105AB1E240411T103132ZKevin Cr
167Python240319T090756ZNicola S
130Python240320T141342Zlynn
036Uiua240319T113058Znoodle p
287R 4.3.2240318T140441Zint 21h
040APL Dyalog Unicode240318T164618Zakamayu
181Matlab240319T004335ZXenox
093Perl 5 + pF240318T124818ZDom Hast
nanAPL+WIN240318T162207ZGraham
151JavaScript Node.js240318T135847ZArnauld
062J240319T032021ZJonah
023Jelly240318T191317ZJonathan
028Vyxal240318T193122Zmath sca
025Charcoal240318T183338ZNeil
053K ngn/k240318T153351Zovs
164JavaScript Node.js240318T094014Zl4m2

Vyxal, 146 bitsv2, 18.25 bytes

RƛṫøA‹$ṫ"$꘍kzJ;⁽Ṙẇf§

Try it Online!

Bitstring:

01100011010100001110000111101010000100100111011100100011100110011010010111100111000011110101010100011001011010111000101101001001001101011011110010

20 bytes unencoded. Thanks to @emanresu for the golfing suggestion which I somehow didn't notice for 7 months.

First code golf in a while. Feels good to be back doing the golf.

Explained (old)

ƛḣ$øA‹$ḣṘ"$꘍;⁽ṘẆnṘ:"j§­⁡​‎‎⁡⁠⁡‏⁠‎⁡⁠⁤⁡‏‏​⁡⁠⁡‌⁢​‎‎⁡⁠⁢‏⁠‎⁡⁠⁣‏‏​⁡⁠⁡‌⁣​‎‎⁡⁠⁤‏⁠‎⁡⁠⁢⁡‏⁠‎⁡⁠⁢⁢‏‏​⁡⁠⁡‌⁤​‎‎⁡⁠⁢⁣‏⁠‎⁡⁠⁢⁤‏⁠‎⁡⁠⁣⁡‏‏​⁡⁠⁡‌⁢⁡​‎‎⁡⁠⁣⁢‏‏​⁡⁠⁡‌⁢⁢​‎‎⁡⁠⁣⁣‏⁠‎⁡⁠⁣⁤‏‏​⁡⁠⁡‌⁢⁣​‎‏​⁢⁠⁡‌⁢⁤​‎‎⁡⁠⁤⁢‏⁠‎⁡⁠⁤⁣‏⁠‎⁡⁠⁤⁤‏‏​⁡⁠⁡‌⁣⁡​‎‎⁡⁠⁢⁡⁡‏⁠‎⁡⁠⁢⁡⁢‏⁠‎⁡⁠⁢⁡⁣‏⁠‎⁡⁠⁢⁡⁤‏‏​⁡⁠⁡‌⁣⁢​‎‎⁡⁠⁢⁢⁡‏‏​⁡⁠⁡‌⁣⁣​‎‎⁡⁠⁢⁢⁢‏‏​⁡⁠⁡‌­
ƛ           ;           # ‎⁡To each string S in the input:
 ḣ$                     # ‎⁢  Push S[1:], S[0]
   øA‹                  # ‎⁣  and calculate the 0 based index of S[0] in the alphabet
      $ḣṘ               # ‎⁤  Push S[1:][0], S[1:][1::-1]
         "              # ‎⁢⁡  and pair into a list. The first item is the bit that's coming out of the stem, the second is the spike.
          $꘍            # ‎⁢⁢  and prepend (number of spaces calculated earlier) to each string in that pair
# ‎⁢⁣This generates a list of what'll be in each column. The spaces at the front are how far down the cactus the characters will be.
             ⁽ṘẆ        # ‎⁢⁤Reverse the order of the first item so it's [spike, stem]
                nṘ:"    # ‎⁣⁡["zyx...cba", "zyx...cba"]
                    j   # ‎⁣⁢Join the list of lists of stem and spike on ^. By some miracle, this also flattens the list.
                     §  # ‎⁣⁣Transpose, and join on newlines
💎

Created with the help of Luminespire.

05AB1E, 21 bytes

εćUć‚AXkúAª}`R«€SζJ»R

Inputs as a pair in reversed order.

Try it online.

Explanation:

ε           # Map over both strings:
 ć          #  Extract head; pop and push remainder-string and first character
  U         #  Pop this first character and store it in variable `X`
   ć        #  Extract head again
    ‚       #  Pair the remainder-string and first char together
     AXk    #  Get the 0-based index of character `X` in the lowercase alphabet
        ú   #  Pad that many leading spaces to both strings in the pair
         Aª #  Append a lowercase alphabet to this pair
}`          # After the map: pop and push both triplets separated to the stack
  R         # Reverse the second/top triplet
   «        # Merge the two triplets together
    €S      # Convert each string to a list of characters
      ζ     # Zip/transpose; swapping rows/columns,
            # with space as filler character for unequal length rows
       J    # Join all inner character lists back together to strings
        »   # Join these strings by newlines
         R  # Reverse the entire multiline string
            # (after which the result is output implicitly)

I originally started with an approach that uses the Canvas builtin to draw everything at once, but this ended up roughly three times as long (57 bytes):

Aû¹н©¡¨®ý¹ð'aAûIн©¡¨®ýIJ₂₂A¹нk-3¹gÍ0Y3₂₂®-3²gÍ)ć•ĀçR5•SRΛ

Try it online.

Python, 167 bytes

lambda l,r,o=lambda s:" "*(ord(s[0])-97):[("{:99s}"*6).format(o(l)+l[2:],o(l)+l[1],*["abcdefghijklmnopqrstuvwxyz"]*2,o(r)+r[1],o(r)+r[2:])[98-i::99]for i in range(99)]

Attempt This Online!


Old version (function with print rather than lambda: I wasn't too sure about I/O conventions) with explanation:

Python, 197 194 192 185 178 175 174 bytes

o=lambda s:" "*(ord(s[0])-97)
def f(l,r):[print(("{:99s}"*6).format(o(l)+l[2:],o(l)+l[1],*["abcdefghijklmnopqrstuvwxyz"]*2,o(r)+r[1],o(r)+r[2:])[98-i::99])for i in range(99)]

Attempt This Online!

How?

o=lambda s:" "*(ord(s[0])-97)              # Function that, given an input word,
                                           # returns as many whitespaces as the
                                           # index of the first char in unicode,
                                           # after "a" (ord("a") == 97), i.e.
                                           # how much the branch should be raised
                                           # from the ground.

def f(l,r):
                                           # Cactus built horizontally by rows:

  ( o(l)+l[2:],                            # * padding + letters 3+ of the word
    o(l)+l[1],                             # * padding + second letter
    *["abcdefghijklmnopqrstuvwxyz"]*2,     # * whole alphabet twice (ugly but I
                                           #   can't golf it further as strings)
    o(r)+r[1],                             # * padding + second (right) 
    o(r)+r[2:]  )                          # * padding + letters 3+ (right)
#
# └──────┬──────┘
#        └──────────────┐
#                       ╽
  ("{:99s}"*6).format( ... )               # Concatenation of 6 strings, each
#                                          # right-padded to 99 characters.
# └───────────┬────────────┘
#        ┌────┘
#        ╽
[print( ... [98-i::99])for i in range(99)] # A way of wrapping the string at 99
                                           # and printing it transposed.
```

Python, 130 bytes

lambda a,b:[*zip(*g(a),*g(b)[::-1])][::-1]
s=" "*102
g=lambda a:[(p:=s[65:ord(a[0])])+a[2:]+s,p+a[1]+s,"%c"*26%(*range(65,91),)+s]

A lambda returning a matrix (list of tuples) of characters.

Attempt This Online!

Uiua, 40 38 36 bytes

⇌⍉⬚@ (⊂⇌∩(⊂+@a⇡26≡⊂:⊟°⊂:¤↯:@ -@a°⊂))

Try it

38 → 36 bytes thanks to Joao-3 :)

Explanation

⬚@ (…)

Uiua won’t join together arrays of different sizes, so this sets a fill value of a space for the following function:

⇌⍉

Rotate the array to the correct orientation, and it’s done!

R 4.3.2, 287 bytes

Certainly, there's room for optimization...

Edit

THE final version.

I have tried to replace writeLines with cat("\n"), that removed 2 bytes of the code, but the output is slightly different (an empty line over the cactus):

g=\(x,y){a=rev(letters);s=substr;b=nchar;`!`=\(c)which(a==s(c,1,1));`?`=\(y)((24+b(y)-!y)%/%26)*(b(y)-2-!y);k=max(?x,?y);r=l=m=q=rep(" ",26+k);a=c(rep(" ",k),a);`?`=\(e)s(e,2,2);`/`=\(l,x){for(j in 3:b(x))l[3-j+!x]=s(x,j,j);l};m[!x]=?x;q[!y]=?x;l=l/x;r=r/y;cat(paste0("\n",l,m,a,a,q,r))}

Try this one online

Yet another R, 231 bytes

This is a stripped down version - without the cactus headspace calculation. The height of output window is 99 lines.

\(x,y){a=rev(letters);s=substr;b=nchar;`!`=\(c)which(a==s(c,1,1));r=l=m=q=rep(" ",99);a=c(rep(" ",73),a);`?`=\(e)s(e,2,2);`/`=\(l,x){for(j in 3:b(x))l[3-j+!x]=s(x,j,j);l};m[!x]=?x;q[!y]=?x;l=l/x;r=r/y;cat(paste0("\n",l,m,a,a,q,r))}

Attempt This Online!

Test (the first method):

    > f("yoghurt","antidisestablishmentarianism")
t     
r     
u     
h zz m
goyy s
  xx i
  ww n
  vv a
  uu i
  tt r
  ss a
  rr t
  qq n
  pp e
  oo m
  nn h
  mm s
  ll i
  kk l
  jj b
  ii a
  hh t
  gg s
  ff e
  ee s
  dd i
  cc d
  bb i
  aant

APL (Dyalog Unicode), 51 49 44 42 40 bytes SBCS

Assume ⎕io←0, take input as left and right arguments. 44 -> 42 thanks to @ovs, and 42 -> 40 thanks to @att.

⊖⍉∘↑⍤,∘⌽⍥{((a⍳⊃⍵)↓⍣¯1¨⌽0 1 1⊂⍵),⊂a←⎕C⎕A}

How?

The dfn transforms a string into its half of the cactus.

{((a⍳⊃⍵)↓⍣¯1¨⌽0 1 1⊂⍵),⊂a←⎕C⎕A}
                          a←⎕C⎕A  a is abcd...z, half of the middle stem.
              ⌽0 1 1⊂⍵            vertical and horizontal stems that are
         ↓⍣¯1¨                     aligned (using white spaces) with
  (a⍳⊃⍵)                           the first letter of ⍵ in a

With the dfn denoted as f, the answer is ⊖⍉∘↑⍤,∘⌽⍥f which is equivalent to {⊖⍉↑(f⍺),(⌽f⍵)}.

{⊖⍉↑(f⍺),(⌽f⍵)}
    (f⍺) (⌽f⍵)  two halves of the cactus
        ,       stick them together
 ⊖⍉↑            combine the columns

Try it on tryapl.org

Matlab, 181 bytes

Try it online! Input for first test case would be F(["cartoonish";"testing"])

I feel like Matlab isn't well suited for working with strings. But it was a great exercise in array indexing.

s=char(cellstr(input("")));C(1:26,3:4)=char([97:122;97:122]');for i=1:2;C(s(i,1)-96,5:6)=s(i,2:3);C(s(i,1)-95:s(i,1)+length(s(i,:))-99,6)=s(i,4:end)';C=flip(C,2);end;flip(flip(C),2)

Perl 5 + -pF, 93 bytes

This solution utilises ANSI escape sequences and necessitates a compatible terminal emulator. This solution uses a hard-coded row position of 29, but this can be trivially changed to increase the available space (at the cost of having to scroll to see the cactus) up to 99 without incurring any additional byte-cost. There is another version below that scales as needed.

$_=".[29;".(3+$|--).H.join$"="..[A",a..z;s/$F[0]\K/.[s@{[$|?"..$F[1]..":$F[1]]}@F[2..@F].[u/g

Try it online!

Explanation

For each input string, the input is automatically read, placed in $_ (implicit -n from -p) and split into characters in @F (-F).

$_ is then re-set to "\x1b[29;nH" (where n is either 3 or 4 depending on the alternating value of $|) which positions the cursor on the 29th row and 3rd (or 4th) column, and has the lowercase alphabet - joined on "\x08\x1b[A" which is backspace followed by the ANSI escape sequence for "up one row" (which is also stored in $") - concatenated to it.

s/// works on $_ by default, so the first char of the word ($F[0]) is s///ubstituted (keeping the start letter in place using \K which will insert the value of the substitution after the char) with the ANSI escape sequence for "save cursor position" (\x1b[s), the inline list (@{[...]}) consisting of the next letter of the word ($F[1], conditionally surrounded by double backspaces if $| is truthy) and then the interpolated sub-list of the remaining letters in the word from index 2 (automatically joined with $" which we set earlier to backspace and "up one row", finally followed by the ANSI escape sequence for "restore cursor position" (\x1b[u).

$_ is then implicitly printed (via -p).


Perl 5 + -p040lF -MList::Util+max, 127 bytes

This solution also utilises ANSI escape sequences and necessitates a compatible terminal emulator, but it automatically moves the cursor to the correct position.

$\.=".[25B"." "x--$|.join($"="..[A",a..z)=~s/$F[0]\K/.[s@{[$|?"..$F[1]..":$F[1]]}@F[2..@F].[u/gr;$;=max$;,-125+@F+ord}{$_="."x$

Try it online!

Explanation

The majority of this is the same as the previous solution, except the output is stored in $\ instead of $_ (which is implicitly output after every call to print, which happens automatically at the end of the program due to -p).

For each input word (implicit -n from -p, stored in $_, split into chars in @F via -F) the following happens:

$\ has "\x1b[25B" added which is the ANSI escape sequence for "move the cursor down 25 lines" and, conditionally (depending on the alternating value of --$|), a space (can't use $" here as that could is overwritten in the first loop), concatenated with the result of the alphabet joining and substitution which is the same as the previous version.

Additionally, $; is set to the max of either $; (\x1c by default) or -125+@F+ord (where @F used in scalar context is the length of the list and ord is the ordinal of the first char of the input $_).

Outside of the implicit while loop from -n (-p), $_ is set to one space, followed by $; (using the implicit ; added from -n - while <STDIN> { ... ;}) repetitions of "\x0c" (form feed).

$_ is then output automatically via -p, which also outputs $\.

APL+WIN, 154 or 76 72 bytes

Original. Prompts for first string followed by second. Index origin = 0:

x←⎕⋄y←⎕⋄a←⎕av[17+⍳26]⋄m←((∊⌈/26⌈¨(n←¯2+¨⍴¨s)+i←a⍳↑¨s←x y),6)⍴' '⋄m[(↑i)+⍳↑n;0]←2↓x⋄m[↑i;1]←↑1↓x⋄m[;3]←m[;2]←(↑⍴m)↑a⋄m[∊1↓i;4]←↑1↓y⋄m[(∊1↓i)+⍳∊1↓n;5]←2↓y⋄⊖m

Try it online! Thanks to Dyalog Classic

New approach. Prompts for the two strings as a nested vector:

l←∊26+⌈/⍴¨s←⎕⋄⍉⌽3⊖a⍪((∊2⍴¨-a⍳↑¨⌽s)⌽1⊖⊃l↑¨(1⌷¨s),2↓¨⌽s)⍪a←l↑a←⎕av[17+⍳26]

Try it online! Thanks to Dyalog Classic

JavaScript (Node.js), 151 bytes

Expects an array of two strings.

f=(a,y=i=[],c=e=Buffer([y>25?32:97+y]),b=a.map((w,k)=>w[0]==c?w[(i[k]=2)-k]+w[1+k]:(x=w[++i[k]])?e=k?" "+x:x+" ":"  "))=>++e?c:f(a,-~y)+`
`+b.join(c+c)

Try it online!

Commented

f = (                  // f is a recursive function taking:
  a,                   //   a[] = input
  y =                  //   y = vertical position
  i = [],              //   i[] = array of positions in words
  c =                  //   c = current middle character
  e = Buffer([         //   e = copy of c
    y > 25 ? 32        //     use a space if y > 25
           : 97 + y    //     otherwise, use 'a' ... 'z'
  ]),                  //
  b = a.map((w, k) =>  // for each word w at index k in a[]:
    w[0] == c ?        //   if the first letter of w is c:
      w[               //     append the 2nd or 3rd letter of w
        (i[k] = 2) - k //     and initialize i[k] to 2
      ] +              //
      w[1 + k]         //     append the other letter (3rd or 2nd)
    :                  //   else:
      (                //     try to increment i[k]
        x = w[++i[k]]  //     save w[i[k]] in x
      ) ?              //     if it's defined:
        e =            //       set e to a non-zero'ish value
          k ? " " + x  //         append a space followed by x
            : x + " "  //         or x followed by a space
      :                //     else:
        "  "           //       append 2 spaces
  )                    // end of map()
) => ++e               // if e is a space (i.e. we have y > 25
?                      // and both words have been fully written):
  c                    //   stop
:                      // else:
  f(a, -~y) +          //   append the result of a recursive call
  `\n` +               //   followed by a new line 
  b.join(c + c)        //   followed by b[] joined with c + c

J, 62 bytes

0|.@|:(,~|.)&((u:97+i.26)&([,](,"1(,@{.,:}.)@}.)~' '#~(i.{.)))

Attempt This Online!

Probably more bytes to shave off here but not seeing them now...

Function that takes the left branch as the right arg and the right branch as the left arg.

Draws each half in canonical, horizontal form with branch underneath:

 abcdefghijklmnopqrstuvwxyz
                   o
                   mebranch

Reverses the right half and prepends it.

Transposes and reverses.

Jelly,  25  23 bytes

ḢO_97⁶x;Ɱ,Ḣ$Øaṭ¹Ṛƭ)Ẏz⁶Ṛ

A monadic Link that accepts a pair of lists of characters and yields a list of lists of characters (lines).

Try it online! (The footer joins the lines with newline characters.)

How?

ḢO_97⁶x;Ɱ,Ḣ$Øaṭ¹Ṛƭ)Ẏz⁶Ṛ - Link: pair of lists of characters, Words
                  )     - for each {Word in Words}:
Ḣ                       -   pop the first character
 O_97                   -   ordinal {of that} minus 97 -> height
     ⁶x                 -   space character repeated {that many times}
           $            -   last two links as a monad - f(BeheadedWord)
          Ḣ             -     pop the first character -> second character of Word
         ,              -     pair {the rest of BeheadedWord} with {that}
        Ɱ               -   map across {these two WordParts} with:
       ;                -     concatenate {WordPart} to {RepeatedSpaces}
            Øaṭ         -   tack on the lower-case alphabet
                 ƭ      -   alternate between:
               ¹        -     do nothing (when processing the first Word)
                Ṛ       -     reverse (when processing the second Word)
                   Ẏ    - tighten
                    z⁶  - transpose with space character as filler
                      Ṛ - reverse

Vyxal, 28 bytes

vhøA‹ð*?vḢƛḣ$W;÷Ṙ"+n2εwYfvṘ§

Try it Online!

I'm very rusty, this can probably be done in about 25 bytes.

Explanation:

vhøA‹ð*?vḢƛḣ$W;÷Ṙ"+n2εwYfvṘ§­⁡​‎‎⁡⁠⁡‏⁠‎⁡⁠⁢‏⁠‎⁡⁠⁣‏⁠‎⁡⁠⁤‏⁠‎⁡⁠⁢⁡‏‏​⁡⁠⁡‌⁢​‎⁠‎⁡⁠⁢⁢‏⁠‎⁡⁠⁢⁣‏‏​⁡⁠⁡‌⁣​‎‎⁡⁠⁢⁤‏⁠‎⁡⁠⁣⁡‏⁠‎⁡⁠⁣⁢‏⁠‎⁡⁠⁣⁣‏⁠⁠‎⁡⁠⁤⁣‏‏​⁡⁠⁡‌⁤​‎⁠‎⁡⁠⁣⁤‏⁠‎⁡⁠⁤⁡‏⁠‎⁡⁠⁤⁢‏⁠‏​⁡⁠⁡‌⁢⁡​‎‎⁡⁠⁤⁤‏⁠‎⁡⁠⁢⁡⁡‏⁠‎⁡⁠⁢⁡⁢‏‏​⁡⁠⁡‌⁢⁢​‎‎⁡⁠⁢⁡⁣‏‏​⁡⁠⁡‌⁢⁣​‎‎⁡⁠⁢⁡⁤‏⁠‎⁡⁠⁢⁢⁡‏⁠‎⁡⁠⁢⁢⁢‏⁠‎⁡⁠⁢⁢⁣‏‏​⁡⁠⁡‌⁢⁤​‎‎⁡⁠⁢⁢⁤‏⁠‎⁡⁠⁢⁣⁡‏‏​⁡⁠⁡‌⁣⁡​‎‎⁡⁠⁢⁣⁢‏⁠‎⁡⁠⁢⁣⁣‏⁠‎⁡⁠⁢⁣⁤‏‏​⁡⁠⁡‌­
vhøA‹                         # ‎⁡For both words, push first item's position in alphabet
     ð*                       # ‎⁢Multiply with " "
       ?vḢƛ   ;               # ‎⁣Map through suffix of each word:
           ḣ$W                # ‎⁤  Extract head and rest, swap and wrap in list
               ÷Ṙ"            # ‎⁢⁡Reverse order of second item
                  +           # ‎⁢⁢Append spaces to each pair
                   n2εw       # ‎⁢⁣Push alphabet twice, wrap in list
                       Yf     # ‎⁢⁤Interleave with formatted words and flatten
                         vṘ§  # ‎⁣⁡Reverse each item, transpose and join by newlines
💎

Created with the help of Luminespire.

Charcoal, 25 bytes

↑E²βF²«‖SθJι±⌕β§θ⁰…θ²↑✂θ²

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

↑E²β

Output two copies of the alphabet vertically.

F²«

Loop over the two inputs.

Reflect the canvas (this is golfier than trying to output the two words in different directions).

Sθ

Input the next word.

Jι±⌕β§θ⁰

Jump to its starting letter.

…θ²

Output the first two letters of the word.

↑✂θ²

Output the rest of the word upwards.

K (ngn/k), 53 bytes

Takes a list of 2 strings and returns a character matrix.

{|+(|/#'x)$x,:|y}.{(""[!97!*x],/:|1 2_x),,`c$97+!26}'

Try it online!

JavaScript (Node.js), 164 bytes

f=(a,b,p=![i=10],q=0,c=i>35?S:i.toString(++i),s=(p?(a[++p]||S)+S:a[0]==c?a[p=2]+a[1]:S+S)+c+c+(q?S+(b[++q]||S):b[0]==c?b[1]+b[q=2]:S))=>1/s?S:f(a,b,p,q)+`
`+s;S=' '

Try it online!