# Triangulating Text

Write a program or function that takes in a string guaranteed to only contain printable ASCII characters except for space, and to be a positive triangular number (1, 3, 6, 10, 15, ...) in length.

Print or return the same string, but shaped into a triangle using spaces. Some examples will best show what I mean:

If the input is R then the output will be

R

If the input is cat then the output will be

c
a t

If the input is monk3y then the output will be

m
o n
k 3 y

If the input is meanIngfu1 then the output will be

m
e a
n I n
g f u 1

If the input is ^/\/|\/[]\ then the output will be

^
/ \
/ | \
/ [ ] \

If the input is

Thisrunofcharactersismeanttohavealengththatcanbeexpressedasatriangularnumber.Diditwork?Youtellme,Ican'tcountverywell,ok?

then the output will be

T
h i
s r u
n o f c
h a r a c
t e r s i s
m e a n t t o
h a v e a l e n
g t h t h a t c a
n b e e x p r e s s
e d a s a t r i a n g
u l a r n u m b e r . D
i d i t w o r k ? Y o u t
e l l m e , I c a n ' t c o
u n t v e r y w e l l , o k ?

Basically, newlines are inserted between the substrings of triangular length, spaces are added between all characters, and each line is indented with spaces to fit the triangle shape.

A single trailing newline and lines with trailing spaces are optionally allowed, but otherwise your output should exactly match these examples. The last line of the triangle should not have leading spaces.

The shortest code in bytes wins.

• Is there an absolute max the length of the string can be? Nov 30 '15 at 6:33
• @geokavel It should work for any length of string your language can normally handle. Nov 30 '15 at 6:41
• Here's a Christmas tree for anyone who hasn't put theirs' up yet. */\/|\/|o\/|o|\/o|o|\/||o|o\/o|||o|\/o||o|||\/||o|||o|\/|o|||o||o\ Nov 30 '15 at 20:30
• potentially related Dec 2 '15 at 0:13

## Python, 81 bytes

def f(s,p=''):
i=-int(len(2*s)**.5)
if s:f(s[:i],p+' ');print p+' '.join(s[i:])

A recursive function. Goes from the end of s, chopping off and printing characters. The number of characters to take is computed from the length of s. The function is set up to prints in reverse order of the recursive calls, which terminate when s is empty and then resolve back up the line. Each layer, the prefix p has an extra space added.

In Python 3, the if can be done via short circuiting, though this doesn't seem to save chars:

def f(s,p=''):i=-int(len(2*s)**.5);s and[f(s[:i],p+' '),print(p+' '.join(s[i:]))]

An equally-long alternative with inequality chaining:

def f(s,p=''):i=-int(len(2*s)**.5);''<s!=f(s[:i],p+' ')!=print(p+' '.join(s[i:]))

Both print and f return None, which is hard to use.

• This is quite clever. By chopping off the string one row at a time, you still end up with a triangular length string to compute the number of leading spaces with.
– xsot
Nov 30 '15 at 9:54

# Pyth, 22 bytes

jua+L\ GjdHfTczsM._UzY

Try it online: Demonstration or Test Suite

### Explanation:

jua+L\ GjdHfTczsM._UzY   implicit: z = input string
Uz    create the list [0, 1, ..., len(z)-1]
._      all prefixes of this list: [[0], [0,1], [0,1,2], ...]
sM        sum up each sublist: [0, 1, 3, 6, 10, ...]
cz          split z at these indices
fT            remove all the unnecessary empty strings
this gives us the list of strings of the triangle
u                   Y   reduce this list, with the initial value G = []
+L\ G                    prepend a space to each string in G
jdH                 join the current string with spaces
a                         and append it to G
j                        print each string on a separate line

## Retina, 10810294878264 63 bytes

Thanks to Sp3000 for making me pursue my original approach, which brought the byte count from 108 down to 82.

Massive thanks to Kobi who found a much more elegant solution, which allowed me to save another 19 bytes on top of that.

S_(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
.
$0 m+^(?=( *)\S.*\n\1) <space> Where <space> represents a single space character (which would otherwise be stripped by SE). For counting purposes, each line goes in a separate file and \n should be replaced with an actual linefeed character. For convenience, you can run the code as is from a single file with the -s flag. Try it online. ### Explanation Well... as usual I can't give a full introduction to balancing groups here. For a primer see my Stack Overflow answer. S_(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+) The first stage is a Split stage, which splits the input into lines of increasing length. The _ indicates that empty chunks should be omitted from the splitting (which only affects the end, because there will be a match in the last position). The regex itself is entirely contained in a look-around so it won't match any characters, but only positions. This part is based on Kobi's solution with some additional golfitude I found myself. Note that lookbehinds are matched right-to-left in .NET, so the following explanation should best be read bottom to top. I've also inserted another \G in the explanation for clarity, although that isn't necessary for the pattern to work. (?<= ^ # And we ensure that we can reach the beginning of the stack by doing so. # The first time this is possible will be exactly when tri(m-1) == tri(n-1), # i.e. when m == n. Exactly what we want! (?<-1>.)* # Now we keep matching individual characters while popping from group <1>. \G # We've now matched m characters, while pushing i-1 captures for each i # between 1 and m, inclusive. That is, group <1> contains tri(m-1) captures. (?: (?<= \G # The \G anchor matches at the position of the last match. (.)* # ...push one capture onto group <1> for each character between here # here and the last match. ) # Then we use a lookahead to... . # In each iteration we match a single character. )+ # This group matches all the characters up to the last match (or the beginning # of the string). Call that number m. ) # If the previous match was at position tri(n-1) then we want this match # to happen exactly n characters later. I'm still admiring Kobi's work here. This is even more elegant than the prime testing regex. :) Let's move on to the next stage: .$0

Simple: insert a space after every non-linefeed character.

m+^(?=( *)\S.*\n\1)
<space>

This last stage indents all the lines correctly to form the triangle. The m is just the usual multiline mode to make ^ match the beginning of a line. The + tells Retina to repeat this stage until the string stops changing (which, in this case means that the regex no longer matches).

^      # Match the beginning of a line.
(?=    # A lookahead which checks if the matched line needs another space.
( *) # Capture the indent on the current line.
\S   # Match a non-space character to ensure we've got the entire indent.
.*\n # Match the remainder of the line, as well as the linefeed.
\1   # Check that the next line has at least the same indent as this one.
)

So this matches the beginning of any line which doesn't have a bigger indent than the next. In any such position we insert a space. This process terminates, once the lines are arranged in a neat triangle, because that's the minimal layout where each line has a bigger indent than the next.

• @_@ What the heck does it do? Dec 1 '15 at 7:30
• @n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ Now with 100% more amazingness, courtesy of Kobi. Dec 1 '15 at 20:13

# Candy, 6759 57 bytes

&iZ1-=yZ1+Z*2/>{0g}0=z@1i&{|.}bYR(" ";=)ZR(=a&{;}" ";)"\n";Y1-=ya1j

&1-8*1+r1-2/=y@1i&{|.}bYR(" ";=)ZR(=a&{;}" ";)"\n";Y1-=ya1j

&8*7-r1-2/=y@1i&{|.}bYR(" ";=)ZR(=a&{;}" ";)"\n";Y1-=ya1j

or:

&
8 *
7 - r
1 - 2 /
= y @ 1 i
& { | . } b
Y R ( "   " ;
= ) Z R ( = a &
{ ; } "   " ; ) "
\ n " ; Y 1 - = y a
1 j

long form:

stackSz
digit8    # Y = (sqrt((numCh - 1) * 8 + 1) - 1) / 2   using pythagorean
mult      # Y = (sqrt(numCh * 8 - 7) - 1) / 2  equivalent but shorter
digit7
sub
root
digit1
sub
digit2
div
popA
YGetsA
label digit1
incrZ
stackSz   # bail if we're out of letters
if
else
retSub
endif
stack2
pushY     # print the leading spaces (" " x Y)
range1
while
" " printChr
popA
endwhile
pushZ
range1      # output this row of characters (Z of them)
while
popA
stack1
stackSz
if
printChr    # bail on unbalanced tree
endif
" " printChr
endwhile
"\n" printChr
pushY
digit1
sub
popA
YGetsA
stack1
digit1 jumpSub   # loop using recursion
• Yea, I felt Christmas-y. Dec 3 '15 at 9:06

## CJam, 27 26 bytes

Thanks to Sp3000 for saving 1 byte.

Lq{' @f+_,)@/(S*N+a@\+\s}h

Surprisingly close to Pyth, let's see if this can be golfed...

Test it here.

### Explanation

L        e# Push an empty array to build up the lines in.
{        e# While the top of the stack is truthy (non-empty)...
' @f+  e#   Prepend a space to each line we already have.
_,)    e#   Get the number of lines we already have and increment.
@/     e#   Split the input into chunks of that size.
(S*    e#   Pull off the first chunk (the next line) and join with spaces.
N+     e#   Append a linefeed.
a@\+   e#   Append it to our list of lines.
\s     e#   Pull up the other chunks of the input and join them back into one string.
}h
• Why doesn't it work if I change ' to S??? Nov 30 '15 at 17:06
• @geokavel Because S is a string, not a character, so f will map over that string instead of the list of lines. Nov 30 '15 at 17:07
• That was my guess. Do you have any idea as to the rationale for making S a string? Nov 30 '15 at 17:12
• @geokavel No I don't. Nov 30 '15 at 17:13

# Ruby, 8477 73 bytes

->v{1.upto(n=v.size**0.5*1.4){|i|puts" "*(n-i)+v[i*(i-1)/2,i].chars*" "}}

77 bytes

->v{0.upto(n=(v.size*2)**0.5-1){|i|puts" "*(n-i)+v[i*(i+1)/2,i+1].chars*" "}}

Reduced few more bytes by removing variable r as suggested by steveverrill.

84 bytes

->v{n=(v.size*2)**0.5-1;0.upto(n){|i|puts" "*(n-i)+v[(r=i*(i+1)/2)..r+i].chars*" "}}

Ungolfed:

->v {
1.upto(n=v.size**0.5*1.4) { |i|
puts" "*(n-i)+v[i*(i-1)/2,i].chars*" "
}
}

First calculating triangular number from the input string

n=v.size**0.5*1.4

i.e. for example input string size is 120 and our triangular number n, will be 15.

puts" "*(n-i)+v[i*(i-1)/2,i].chars*" "

In the above line, it prints spaces followed by series of string which are fetched from the input string using following pattern

[[0,0],[1,2],[3,5],[6,9]]

Usage:

f=->v{1.upto(n=v.size**0.5*1.4){|i|puts" "*(n-i)+v[i*(i-1)/2,i].chars*" "}}
f["Thisrunofcharactersismeanttohavealengththatcanbeexpressesasatriangularnumber.Diditwork?Youtellme,Ican'tcountverywell,ok?"]
T
h i
s r u
n o f c
h a r a c
t e r s i s
m e a n t t o
h a v e a l e n
g t h t h a t c a
n b e e x p r e s s
e s a s a t r i a n g
u l a r n u m b e r . D
i d i t w o r k ? Y o u t
e l l m e , I c a n ' t c o
u n t v e r y w e l l , o k ?
• Wow, our approaches are very similar, but we seem to have complementary golfing knowledge. I didn't know upto doesn't require an integer argument, (times most certainly does.) I've incorporated some of your syntax into a revision of my answer. The biggest tip I have for you is, you don't need that variable r. Just use a , instead of .. and the number after the comma is the total number of elements to return, rather than the end of the range. Nov 30 '15 at 21:23
• True. Thanks for the tip i'm updating my answer right away :) Dec 1 '15 at 6:58

# Pyth, 27 bytes

Js.IsSGlzWz+*-J=hZdjd<~>zZZ

z = input()
Z = 0
d = ' '
sSG                        G -> tri(G)
.I   lz                      Find the (float) input whose output is len(z).
s                             Convert to int.
J                              Save as J.
Wz                    while z:
=hZ             Z += 1
*-J  Zd            Generate J-Z spaces.
~>zZ     Remove the first Z characters from z.
<    Z    Generate those first Z characters.
jd          Join on spaces.
+                   Add the two together and print.

Test Suite

An interesting approach - imperative, and uses .I. Probably golfable.

# C, 138136 134 bytes

Takes a string as an input :

j,r,k,a;f(char*s){j=strlen(s);r=k=sqrt(1+8*j)/2;for(;r--;printf("\n")){for(j=r;j--;)printf(" ");for(j=k-r;j--;)printf("%c ",s[a++]);}}
• You seem to have beat JavaScript with C by 1 byte so far :D Nov 30 '15 at 23:51
• @MarkKCowan yes, apparently. I hope I make it even smaller! :) Dec 1 '15 at 9:30
• @SahilArora - You can replace printf(" ") and printf("\n") with puts(" ") and puts("\n"). Each substitution will save you 2 bytes.:) Dec 2 '15 at 9:33
• @enhzflep I tried it already, it gave an ambiguous output! Dec 2 '15 at 9:45
• Oh. :( Works fine here on win7 with gcc 4.7.1 - I guess it's to do with the way that the printf output is flushed to stdout. +1 for beating Javascript. Dec 2 '15 at 9:56

# Ruby approach 2 rev 1, 76 bytes

->s{s=s.chars*' '
0.upto(w=s.size**0.5-1){|i|puts' '*(w-i)+s[i*i+i,i*2+2]}}

Optimized using syntax ideas from Vasu Adari's answer, plus a few twists of my own.

# Ruby approach 2 rev 0, 93 bytes

->s{s=s.chars.to_a.join(' ')
w=(s.size**0.5).to_i
w.times{|i|puts' '*(w-i-1)+s[i*i+i,i*2+2]}}

Completely different approach. First we add spaces in between the characters of the input. Then we print out the rows line by line.

# Ruby approach 1, 94 bytes

->s{n=-1;w=((s.size*2)**0.5).to_i
(w*w).times{|i|print i/w+i%w<w-1?'':s[n+=1],-i%w==1?$/:' '}} this ended up far longer than anticipated. w contains the number of printable characters in the bottom row, or equivalently, the number of lines. Every line contains w whitespace characters (the last of which is the newline) so the idea is to print these whitespace characters and insert the printable characters where necessary. ## Minkolang 0.14, 42 bytes (xid2;$I2*,)1-[i1+[" "o]lrx" "$ii-1-D$O].

Try it here.

### Explanation

(                Open while loop
x               Dump top of stack
i              Loop counter (i)
d2;           Duplicate and square
$I2* Length of input times two , Push (i^2) <= (length of input) ) Close for loop; pop top of stack and exit when it's 0 1-[ Open for loop that repeats sqrt(len(input))-1 times i1+[ Open for loop that repeats (loop counter + 1) times " "o Push a space then read in character from input ] Close for loop l Push 10 (newline) r Reverse stack x Dump top of stack " " Push a space$i             Push the max iterations of for loop
i-           Subtract loop counter
1-         Subtract 1
D        Pop n and duplicate top of stack n times
$O Output whole stack as characters ]. Close for loop and stop. • Such a perfect byte count! good job! Nov 30 '15 at 7:31 • @TanMath but 42 is not a triangular number! Nov 30 '15 at 21:16 # Python 2, 88 85 bytes s=t=raw_input() i=1 while s:print' '*int(len(t*2)**.5-i)+' '.join(s[:i]);s=s[i:];i+=1 Thanks xnor for saving 3 bytes. • Doesn't shortening s mess up the number-of-spaces calculation? – xnor Nov 30 '15 at 9:07 • Oh right. I removed a temporary variable before submitting but didn't realise that it invalidated the code. – xsot Nov 30 '15 at 9:11 • What if you do like before but save a back-up S=s=raw_input()? – xnor Nov 30 '15 at 9:14 • Good suggestion. I think there's probably a shorter overall strategy though. – xsot Nov 30 '15 at 9:17 • Crossed out 88 looks funny Nov 30 '15 at 18:17 # CJam, 50 Bytes q:QQ,1>{,{),:+}%:RQ,#:IR2ew<{~Q<>:LS*L,I+(Se[N}%}& Try it here. Explanation q:QQ,1>{ e# Only proceed if string length > 1, otherwise just print. ,{),:}%:R e# Generates a list of sums from 0 to k, where k goes from 0 to the length of the string [0,1,3,6,10,15,21,...] Q,#:I e# Find the index of the length of the string in the list R2ew< e# Make a list that looks like [[0,1],[1,3],[3,6],...,[?,n] ]where n is the length of the string {~Q<>:L e# Use that list to get substrings of the string using the pairs as start and end indices S* e# Put spaces between the substrings L,I+(Se[N e# (Length of the substring + Index of string length in sum array -1) is the length the line should be padded with spaces to. Add a new line at the end. %}& # JavaScript (ES6), 135 bytes w=>{r='';for(s=j=0;j<w.length;j+=s++);for(i=j=0;w[j+i];j+=++i)r+=Array(s-i-1).join +w.slice(j,i+j+1).split.join +'<br>';return r} De-golf + demo: function t(w) { r = ''; for (s = j = 0; j < w.length; j += s++); for (i = j = 0; w[j + i]; j += ++i) r += Array(s - i - 1).join  + w.slice(j, i + j + 1).split.join  + '<br>'; return r; } document.write('<pre>' + t(prompt())); • What's the goal of for (s = j = 0; j < w.length; j += s++);? Also, inside a <pre>, you can use \n instead of <br>. Also, you forgot to mention that it is ES6. Dec 1 '15 at 12:03 • The goal of the first loop is to count the length of the last line, so as to indent each line properly. Dec 1 '15 at 12:29 # Java, 258 194 Golfed: String f(String a){String r="";int t=(((int)Math.sqrt(8*a.length()+1))-1)/2-1;int i=0,n=0;while(n++<=t){for(int s=-1;s<t-n;++s)r+=" ";for(int j=0;j<n;++j)r+=a.charAt(i++)+" ";r+="\n";}return r;} Ungolfed: public class TriangulatingText { public static void main(String[] a) { // @formatter:off String[] testData = new String[] { "R", "cat", "monk3y", "meanIngfu1", "^/\\/|\\/[]\\", "Thisrunofcharactersismeanttohavealengththatcanbeexpressedasatriangularnumber.Diditwork?Youtellme,Ican'tcountverywell,ok?", }; // @formatter:on for (String data : testData) { System.out.println("f(\"" + data + "\")"); System.out.println(new TriangulatingText().f(data)); } } // Begin golf String f(String a) { String r = ""; int t = (((int) Math.sqrt(8 * a.length() + 1)) - 1) / 2 - 1; int i = 0, n = 0; while (n++ <= t) { for (int s = -1; s < t - n; ++s) r += " "; for (int j = 0; j < n; ++j) r += a.charAt(i++) + " "; r += "\n"; } return r; } // End golf } Program output: f("R") R f("cat") c a t f("monk3y") m o n k 3 y f("meanIngfu1") m e a n I n g f u 1 f("^/\/|\/[]\") ^ / \ / | \ / [ ] \ f("Thisrunofcharactersismeanttohavealengththatcanbeexpressedasatriangularnumber.Diditwork?Youtellme,Ican'tcountverywell,ok?") T h i s r u n o f c h a r a c t e r s i s m e a n t t o h a v e a l e n g t h t h a t c a n b e e x p r e s s e d a s a t r i a n g u l a r n u m b e r . D i d i t w o r k ? Y o u t e l l m e , I c a n ' t c o u n t v e r y w e l l , o k ? • You could probably statically import System.out to save some bytes. Dec 11 '15 at 17:43 • import static System.out; is 25 bytes, and System. is 7 bytes. It is used three times, and 21 < 25 so it would actually increase the size by 4 bytes. Good lead, though, static imports can save space and not everyone knows about them. – user18932 Dec 11 '15 at 18:22 • I was going through old answers when I found this one: "write a program or function" which I didn't realize at first. Stripping out the class stuff saved space. I made it into a proper function and found a few more bytes to shave off. – user18932 Aug 9 '16 at 21:38 # JavaScript (ES6), 106 bytes a=>(y=z=0,(f=p=>p?" ".repeat(--p)+a.split.slice(y,y+=++z).join + +f(p):"")(Math.sqrt(2*a.length)|0)) Uses recursion instead of a for loop to build the string. To find the length of the longest row, use the formula for the nth triangular number T_n is T_n = (n^2 + n)/2. Given n and solving for T_n using the quadratic formula, we have: 1/2 * n^2 + 1/2 * n - T_n = 0 a = 1/2, b = 1/2, c = -T_n -1/2 + sqrt(1/2^2 - 4*1/2*-T_n) ------------------------------- = sqrt(1/4 + 2*T_n) - 1/2 2*1/2 It turns out that after flooring, adding 1/4 inside the square root doesn't change the result, hence the formula for the longest row is Math.sqrt(2*a.length)|0. # TeaScript, 44 Bytes r(m=$s(2*xn)|0)ßp.R(m-i)+x·.S(v,v+=Æw)jø+§)µ

This uses the same method as my JavaScript answer, but is a lot shorter.

r(m=$s(2*xn)|0)m(#p.R(m-i)+xs.S(v,v+=++w)j + )j # Powershell, 69 bytes ($args|% t*y|?{$r+="$_ ";++$p-gt$l}|%{$r;rv r,p;$l++})|%{' '*--$l+$_}

Less golfed test script:

$f = { ($args|% t*y|?{  # test predicate for each char in a argument string
$r+="$_ "   # add current char to the result string
++$p-gt$l   # return predicate value: current char posision is greater then line num
}|%{            # if predicate is True
$r # push the result string to a pipe rv r,p # Remove-Variable r,p. This variables will be undefined after it.$l++        # increment line number
}

)|%{                # new loop after processing all characters and calculating $l ' '*--$l+$_ # add spaces to the start of lines } # and push a result to a pipe } @( ,("R", "R ") ,("cat", " c ", "a t ") ,("monk3y", " m ", " o n ", "k 3 y ") ,("meanIngfu1", " m ", " e a ", " n I n ", "g f u 1 ") ,("^/\/|\/[]\", " ^ ", " / \ ", " / | \ ", "/ [ ] \ ") ,("Thisrunofcharactersismeanttohavealengththatcanbeexpressedasatriangularnumber.Diditwork?Youtellme,Ican'tcountverywell,ok?", " T ", " h i ", " s r u ", " n o f c ", " h a r a c ", " t e r s i s ", " m e a n t t o ", " h a v e a l e n ", " g t h t h a t c a ", " n b e e x p r e s s ", " e d a s a t r i a n g ", " u l a r n u m b e r . D ", " i d i t w o r k ? Y o u t ", " e l l m e , I c a n ' t c o ", "u n t v e r y w e l l , o k ? ") ,("*/\/|\/|o\/|o|\/o|o|\/||o|o\/o|||o|\/o||o|||\/||o|||o|\/|o|||o||o\", " * ", " / \ ", " / | \ ", " / | o \ ", " / | o | \ ", " / o | o | \ ", " / | | o | o \ ", " / o | | | o | \ ", " / o | | o | | | \ ", " / | | o | | | o | \ ", "/ | o | | | o | | o \ ") ) | % {$s,$expected =$_
$result = &$f $s "$result"-eq"$expected"$result
}

Output:

True
R
True
c
a t
True
m
o n
k 3 y
True
m
e a
n I n
g f u 1
True
^
/ \
/ | \
/ [ ] \
True
T
h i
s r u
n o f c
h a r a c
t e r s i s
m e a n t t o
h a v e a l e n
g t h t h a t c a
n b e e x p r e s s
e d a s a t r i a n g
u l a r n u m b e r . D
i d i t w o r k ? Y o u t
e l l m e , I c a n ' t c o
u n t v e r y w e l l , o k ?
True
*
/ \
/ | \
/ | o \
/ | o | \
/ o | o | \
/ | | o | o \
/ o | | | o | \
/ o | | o | | | \
/ | | o | | | o | \
/ | o | | | o | | o \

## C#, 202

string r(string s,List<string> o,int i=1){o=o.Select(p=>" "+p).ToList();o.Add(String.Join(" ",s.Substring(0,i).ToCharArray()));return s.Length==i?String.Join("\n",o):r(s.Substring(i,s.Length-i),o,i+1);}

I don't know if this is legal in code-golf but, does passing a list in the function count? I can't find a way to recurse this without a List< string > declared outside the function so I put it as a parameter.

Usage:

r("1",new List<string>());
r("123", new List<string>());
r("123456", new List<string>());
r("Thisrunofcharactersismeanttohavealengththatcanbeexpressedasatriangularnumber.Diditwork?Youtellme,Icanstcountverywell,ok?",new List<string>());

# C, 102 bytes

i,j;main(n,s){for(n=sqrt(strlen(gets(s))*2);j<n;printf("%*.1s",i>1?2:i*(n-j),i++>j?i=!++j,"\n":s++));}

# Bash + sed, 87

for((;i<${#1};i+=j));{ a+=(${1:i:++j})
}
printf %${j}s\\n${a[@]}|sed 's/\S/ &/g;s/.//'

# R, 142 bytes

Pretty sure I can get this down more. Still working on that though. I feel like I'm missing an easy recursion -- but I haven't been able to shorten it right.

f=function(a){n=nchar(a);l=which(cumsum(1:n)==n);w=strsplit(a,c())[[1]];for(i in 1:l){cat(rep(" ",l-i),sep="");cat(w[1:i],"\n");w=w[-(1:i)]}}

ungolfed

f=function(a){
n = nchar(a)                 #number of characters
l= which(cumsum(1:n)==n)     #which triangle number
w= strsplit(a,c())[[1]]      #Splits string into vector of characters
for (i in 1:l) {
cat(rep(" ",l-i),sep="") #preceeding spaces
cat(w[1:i],"\n")         #Letters
w=w[-(1:i)]              #Shifts removes letters (simplifies indexing)
}
}

ā£õKεSðý}.c

Try it online!

# Vyxalj, 24 bytes

{:|&›:¥Ẏ⅛¥ȯ}¾ƛfṄ¾LnL-ð*p

Try it Online!