# Uncollapse digits

Given a string of English names of digits “collapsed” together, like this:

zeronineoneoneeighttwoseventhreesixfourtwofive


Split the string back into digits:

zero nine one one eight two seven three six four two five


# Rules

• The input is always a string. It always consists of one or more lowercase English digit names, collapsed together, and nothing else.

• The English digit names are zero one two three four five six seven eight nine.
• The output may be a list of strings, or a new string where the digits are delimited by non-alphabetic, non-empty strings. (Your output may also optionally have such strings at the beginning or end, and the delimiters need not be consistent. So even something like {{ zero0onetwo ); is a valid (if absurd) answer for zeroonetwo.)

• The shortest answer in bytes wins.

# Test cases

three -> three
eightsix -> eight six
fivefourseven -> five four seven
ninethreesixthree -> nine three six three
foursixeighttwofive -> four six eight two five
fivethreefivesixthreenineonesevenoneeight -> five three five six three nine one seven one eight
threesevensevensixninenineninefiveeighttwofiveeightsixthreeeight -> three seven seven six nine nine nine five eight two five eight six three eight
zeroonetwothreefourfivesixseveneightnine -> zero one two three four five six seven eight nine

• This is an excellent challenge! The task is extremely easy to understand and verify, but the right approach to use isn't very obvious. And choosing the right approach could make a huge difference in score. +1 :) Commented Nov 20, 2017 at 17:27
• After thinking this up, I remembered a similar, but more simplistic challenge on anarchy golf: yesno! It sparked some amazing C answers. I hope to see one of those soon :)
– lynn
Commented Nov 20, 2017 at 17:41
• I do not think my C answer qualifies as such, but hopefully it's a starting point for others with a more twisted sense of humor than myself. Commented Nov 20, 2017 at 20:29
• I'm pretty sure I've seen this same challenge, but where you're supposed to print the actual number. I'm almost certain it was also posted by, you, Lynn; but I've lost the link, hook me up with it? Commented Nov 20, 2017 at 22:21
• @MichaelDorgan (or any other C coders), you may want to have a look at the algorithm I used in my Befunge answer. A straight conversion of that to C got me a 104 byte solution, which I think beats all of the existing C answers. I'm willing to bet that could be improved upon by someone with more C golfing skills. Commented Nov 21, 2017 at 3:06

# Retina, 20 bytes

!..[eox]|[tse]?....


Try it online!

# C (gcc), 89 80 76 75 72 71 70 69 bytes

f(char*s){*s&&f(s+printf(" %.*s",""[(*s^s[2])%12],s)-1);}


Try it online!

(89) Credit to gastropner for the XOR hash.
(76) Credit to Toby Speight for the idea of using 1st and 3rd.
(75) Credit to Michael Dorgan for '0'48.
(72) Credit to Michael Dorgan and Lynn for literals with control characters.
(69) Credit to Lynn for x?y:0x&&y

f (char *s) {        /* K&R style implicit return type. s is the input. */
*s&&f(           /* Recurse while there is input. */
s+printf(    /* printf returns the number of characters emitted. */
" %.*s", /* Prefix each digit string with a space. Limit
* how many bytes from the string to print out. */
""
/* Magic hash table, where the value represents
* the length of the digit string. The string
* is logically equivalent to
* "\04\01\05\03\04\05\05\04\04\01\03\03" */
[(*s^s[2])%12],
/* The XOR hash (mod 12) */
s)       /* The current digit. */
-1);}    /* Subtract 1 for the space. */

# Python 2, 50 bytes

import re
re.compile('..[eox]|[tse]?....').findall


Try it online!

-3 thanks to Lynn.
-4 thanks to Uriel's answer's regex.

• Nice! import re;re.compile('…').findall should save a couple of bytes. I did expect this to turn into regex golf :)
– lynn
Commented Nov 20, 2017 at 17:11
• @Lynn Hold on, wait until I'm done! :-P EDIT: It's 3 bytes, actually. Commented Nov 20, 2017 at 17:12
• @Lynn Also, you should've turned this to code-golf regular-expression instead. ;) Commented Nov 20, 2017 at 17:24
• I’m holding out for a C answer, which will be very interesting!
– lynn
Commented Nov 20, 2017 at 17:33

# Befunge, 878581 76 bytes

<*"h"%*:"h"$_02g-v1$,*<v%*93,:_@#0:~
"@{&ruX;\"00^ !: _>_48^>+:"yp!"*+%02p0


Try it online!

Befunge doesn't have any string manipulation instructions, so what we do is create a kind of hash of the last three characters encountered, as we're processing them.

This hash is essentially a three digit, base-104 number. Every time a new character is read, we mod the hash with 1042 to get rid of the oldest character, multiply it by 104 to make space for the new character, then add the ASCII value of the new character mod 27 (to make sure it doesn't overflow).

For comparison purposes, we take this value mod 3817, write it into memory (thus truncating it to 8 bits), which results in smaller numbers that are easier for Befunge to handle. The hashes we then have to compare against are 0, 38, 59, 64, 88, 92, 114, 117, and 123. If it matches any of those, we know we've encountered a character sequence that marks the end of a number, so we output an additional space and reset the hash to zero.

If you're wondering why base 104, or why mod 3817, those values were carefully chosen so that the hash list we needed to compare against could be represented in as few bytes as possible.

• Quite honestly, this looks like bakemoji (ばけもじ) to me. Wow. The algorithm description is nice though and I will contemplate it. Commented Nov 21, 2017 at 18:27
• ^, I remember seeing the term as mojibake (もじばけ). How did you find those numbers (base 104, mod 3187), @JamesHolderness ? Commented Nov 22, 2017 at 22:31
• @Zacharý I wrote a little Python script that tested different base and mod combinations to find the ones that would produce the correct results when run against all the expected inputs. Once I knew which combinations worked, I ran the resulting hash outputs through a Befunge number generator to find out which produced the shortest code. Commented Nov 23, 2017 at 0:32

# Java (OpenJDK 8), 5546 43 bytes

Saving 9 bytes thanks to Forty3/FrownyFrog

Saving 3 bytes thanks to Titus

s->s.replaceAll("one|tw|th|f|z|s|.i"," $0")  Try it online! edit: Thank you for the welcome and explanation of lambdas! • Hi, welcome to PPCG! Great first answer, and it indeed works. Here is the TIO link for it. Lambdas can be created in multiple ways. Here is another TIO with some lambdas with added comments so you can see how to create them yourself. (I suggest copying it to Eclipse so you can see the highlighting of the code.) Also, Tips for golfing in Java and Tips for golfing in all languages might be interesting to read. Enjoy your stay! :) Commented Nov 21, 2017 at 10:55 • @KevinCruijssen thank you! I am honestly suprised that Java is shorter than JavaScript. Usually when I am reading challenges, JS is a lot shorter. Commented Nov 21, 2017 at 11:22 • JavaScript should be 2 bytes shorter (g regex suffix instead of All). – Neil Commented Nov 21, 2017 at 13:29 • @Neil it is longer here because it is using f=(s)=> instead of s->, which is 4 bytes shorter. Commented Nov 21, 2017 at 13:42 • @LucaH - per FrownyFrog's suggestion, you can reduce a few of your two-letter strings to single characters: z|f|s instead of ze|fo|fi|si|se/ Commented Nov 21, 2017 at 14:02 # C (gcc), 179159146139137116107103 102 bytes Edit 1: (Added suggestions from Mr. Xcoder - thanks! - My macro version was same size as yours, but I like yours better.) Edit 2: Changed char individual compares to calls to strchr() Edit 3: K&R's the var declarations (Eww!) Edit 4: When 1 macro is not enough... Edit 5: Redone with new algorithm suggested above. Thanks to James Holderness for this great idea! Edit 6: Removed 0 set as it seems to go there automatically - Master level code golf techniques used (commas, printf trick, etc.) - thanks gastropner! Edit 7: Use memchr and fixed a bug pointed out by James Holderness. Edit 7: Use && on final check to replace ? - thanks jxh. c,h;f(char*s){while(c=*s++)putchar(c),h=h%10816*104+c%27,memchr("&;@X\\ru{",h%3817,9)&&putchar(h=32);}  Try it online! Non-golfed (Which is still very golfy honestly...)  int c; int h; void f(char*s) { while(c=*s++) putchar(c), h=h%10816*104+c%27, memchr("&;@X\\ru{",h%3817,9)?putchar(h=32):1; }  Old, straight forward grep-esqe solution: #define p putchar #define q c=*s++ c,x;f(char*s){while(q){p(c);x=strchr("tse",c);p(q);p(q);if(!strchr("eox",c)){p(q);if(x)p(q);}p(' ');}}  Old, cleaner version. // Above code makes a macro of putchar() call. void f(char *s) { char c; while(c = *s++) { putchar(c); int x = strchr("tse", c); putchar(*s++); putchar(c=*s++); if(!strchr("eox", c)) { putchar(*s++); if(x) { putchar(*s++); } } putchar(' '); } }  Try it online! • We can macro the putchar and such for a few bytes, but in general, still thinking about a better algorithm if possible. Commented Nov 20, 2017 at 20:17 • 159 bytes by #defineing putchar and removing a pair of unnecessary brackets. Commented Nov 20, 2017 at 20:47 • A bit ugly, but 136 bytes by using #define p putchar( instead (note the open parenthesis). Commented Nov 21, 2017 at 9:11 • 109 bytes c,h=0;f(char*s){while(c=*s++)putchar(c),h=h%10816*104+c%27,c=h%3817,printf(" "+!(c&&strchr("&;@X\\ru{",c)));} Commented Nov 21, 2017 at 22:21 • Ah, the printf trick I saw below plus removal of a couple parenthesis and braces. Master level code golf enabled :) Commented Nov 21, 2017 at 22:47 # JavaScript, 66575244 41 bytes s=>s.replace(/one|t[wh]|.i|[fsz]/g,"$&")

Pretty naive, but it works.

Nice catch by FrownyFrog to use 2 chars .. except for "one" which a pure 2 char check might mess up zeronine. Edit: the single f and s were good catches by FrownyFrog that I overlooked my first two golfs.

Thanks, Neil, for the suggestion of an unnamed lambda and being able to use a single char for z gets down to 52.

Titus comes up with a smaller RegEx. I feel we are heading toward Uriel's regex eventually.

• Does it break if you use two characters and push 'on' to the end? Commented Nov 20, 2017 at 23:58
• I'm thinking z|tw|th|f|s|ei|ni|on Commented Nov 21, 2017 at 0:03
• @FrownyFrog o comes first so it is recognized first. Commented Nov 21, 2017 at 0:21
• on|t[wh]|.i|[fsz] (-4 bytes) Commented Nov 21, 2017 at 15:06
• @Titus - Unfortunately, the on| will match zeronine rendering zer onine Commented Nov 21, 2017 at 15:13

# Retina, 24 23 bytes

!..[eox]|[fnz]...|.{5}


Try it online! Edit: Saved 1 byte thanks to @FrownyFrog.

• would ..... -> .{5} work? Commented Nov 22, 2017 at 22:11

# C, 103 99 bytes

char*r="f.tzuonresn.xgv";f(char*s){*s&&f(s+printf("%.*s ",(strrchr(r,s[2])-strchr(r,*s))%10,s)-1);}


This works for any character encoding (including awkward ones like EBCDIC), because it doesn't use the numeric value of the input characters. Instead, it locates the first and third letters in a magic string. The distance between these indicates how many letters to advance with each print.

## Test program

#include <stdio.h>
int main(int argc, char **argv)
{
for (int i = 1;  i < argc;  ++i) {
f(argv[i]);
puts("");
}
}

• Some bytes can be saved by using recursion: tio.run/##XY/…
– jxh
Commented Nov 30, 2017 at 20:14
• 93 bytes Commented Dec 31, 2022 at 6:58

# J, 37 35 bytes

rplc'twthsiseeinionzef'(;LF&,)\~_2:


Try it online!

• Cool alternative solution! I tried f=:[:>'..[eox]|[tse]?....'&rxall and it worked in interpeter, but doesn't work in TIO. Commented Nov 21, 2017 at 10:21
• this is really clever, well done Commented Nov 21, 2017 at 14:13
• @GalenIvanov TIO has the latest release, it could be a regression in J. Commented Nov 21, 2017 at 15:50

# Jelly,  23  21 bytes

ḣ3OP%953%7%3+3ɓḣṄȧṫḊÇ


A full program printing line-feed separated output. Note: once it's done it repeatedly prints empty lines "forever" (until a huge recursion limit or a seg-fault)

Try it online! (TIO output is accumulated, a local implementation will print line by line)

### How?

Starting with a list of characters, the program repeatedly:

1. finds the length of the first word of the list of characters using some ordinal mathematics;
2. prints the word plus a linefeed; and
3. removes the word from the head of the list of characters

The length of the first word is decided by inspecting the first three characters of the current list of characters (necessarily part of the first word). The program converts these to ordinals, multiplies them together, modulos the result by 953, modulos that by seven, modulos that by three and adds three:

word   head3  ordinals       product  %953  %7  %3  +3 (=len(word))
zero   zer    [122,101,114]  1404708   939   1   1   4
two    two    [111,110,101]  1233210    28   0   0   3
one    one    [116,119,111]  1532244   773   3   0   3
three  thr    [116,104,114]  1375296   117   5   2   5
four   fou    [102,111,117]  1324674     4   4   1   4
five   fiv    [102,105,118]  1263780   102   4   1   4
six    six    [115,105,120]  1449000   440   6   0   3
seven  sev    [115,101,118]  1370570   156   2   2   5
eight  eig    [101,105,103]  1092315   177   2   2   5
nine   nin    [110,105,110]  1270500   151   4   1   4


ḣ3OP%953%7%3+3ɓḣṄȧṫḊÇ - Main link, list of characters           e.g. "fiveeight..."
ḣ3              - head to index three                                "fiv"
O             - ordinals                                           [102,105,118]
P            - product                                            1263780
%953        - modulo by 953                                      102
%7      - modulo by seven                                    4
%3    - modulo by three                                    1

ɓ - dyadic chain separation swapping arguments...
... ḣṄȧṫḊÇ ...
ḣ         - head to index                                        "five"
Ṅ        - print the result plus a line-feed and yield the result
ṫ      - tail from index                                      "eeight..."
ȧ       - and (non-vectorising)                                "eeight..."
Ḋ     - dequeue                                               "eight..."
Ç    - call the last link (Main*) as a monad with this as input
-       * since it's the only link and link indexing is modular.

• I am not sure whether this is allowed. (Seriously, what do you do when two greatly upvoted meta-answers say the opposite of each other?) Commented Nov 25, 2017 at 18:59
• The OP explicitly states "Your output may also optionally have such strings at the beginning or end" and this program actually prints as it goes, so the output is produced prior to any forced termination anyway. Commented Nov 25, 2017 at 19:01
• Sure, but I don't think OP considered an infinite end string. And the meta-question is explicitly about the case where the output is printed first. Commented Nov 25, 2017 at 19:02
• I think it fulfils the spirit of the requirement (if it, for example, printed infinite empty strings and then the words I might argue it did not) Commented Nov 25, 2017 at 19:04
• So, I guess that puts me into Martin's camp of "if it's a program and can justify..." :) Commented Nov 25, 2017 at 19:08

# Z80 Assembly, 46 45 bytes

; HL is the address of a zero-terminated input string
; DE is the address of the output buffer

Match5: ldi                                 ; copy remaining characters
Match4: ldi
Match3: ld a,32 : ld (de),a : inc de        ; and add space after a matched word.

Uncollapse:

ld a,(hl) : ldi : or a : ret z      ; copy first byte (finish if it was zero)
ex af,af'                           ; and save its value for later.

ldi : ld a,(hl) : ldi               ; copy second and third bytes

cp 'e' : jr z,Match3                ; is the third letter 'e' or 'o' or 'x'?
cp 'o' : jr z,Match3
cp 'x' : jr z,Match3

ex af,af'                           ; now look at the first letter

cp 'e' : jr z,Match5                ; is it 't' or 's' or 'e'?
sub 's' : jr z,Match5
dec a : jr z,Match5
jr Match4


(It was fun to adapt the Uriel's cool regex to a regex-unfriendly environment).

# C (gcc), 106 bytes104 102 bytes

-2 bytes thanks to @jxh -2 bytes thanks to ceilingcat

c;f(char*s){for(char*t=" $&=B*,29/?";*s;)for(c=4+(index(t,(*s^s[1])+35)-t)/4;c--;)putchar(c?*s++:32);}  Try it online! XOR is truly our greatest ally. • Like the s++ trick. Nice hash. Commented Nov 22, 2017 at 0:11 • s[1] will be shorter. – jxh Commented Nov 22, 2017 at 8:28 • @jxh Nice one! Updated. Commented Nov 22, 2017 at 8:44 # Retina, 28 bytes t[ewh]|[zfs]|(ni|o)ne|ei$&


Try it online!

# Pyth, 35 27 23 bytes

Saved a lot of bytes by porting Uriel's approach.

:Q"..[eox]|[tse]?...."1


# Pip, 27 bytes

aR[zfs]|one|[ent][iwh]s._


Takes input as a command-line argument. Try it online!

Simple regex replacement, inserts a space before each match of [zfs]|one|[ent][iwh].

Jumping on the bandwagon of stealing borrowing Uriel's regex gives 23 bytes (with -s flag):

a@..[eox]|[tse]?....


# C 168 ,145,144,141 bytes

EDIT: Tried init 'i' to 1 like so

a,b;main(i)

To get rid of leading whitespace,
but it breaks on input starting with three, seven or eight

141

#define s|a%1000==
a,i;main(b){for(;~scanf("%c",&b);printf(" %c"+!!i,b),a|=b%32<<5*i++)if(i>4|a%100==83 s 138 s 116 s 814 s 662 s 478)a=i=0;}


Try it online

144

a,i;main(b){for(;~(b=getchar());printf(" %c"+!!i,b),a=a*21+b-100,++i)if(i>4|a==204488|a==5062|a==7466|a==23744|a==21106|a==6740|a==95026)a=i=0;}


Try it online

168

i,a;main(b){for(;~scanf("%c",&b);printf(" %c"+!!i,b),a|=b<<8*i++)if(i>4|a==1869768058|a==6647407|a==7305076|a==1920298854|a==1702259046|a==7891315|a==1701734766)a=i=0;}


Try it online!

Ungolfed

i,a;main(b){
for(;~scanf("%c",&b); // for every char of input
printf(" %c"+!!i,b), // print whitespace if i==0 , + char
a|=b<<8*i++ // add char to a for test
)
if(
i>4| // three seven eight
a==1869768058|      // zero
a==6647407|        // one
a==7305076|       // two
a==1920298854|   //four
a==1702259046|  //five
a==7891315|    //six
a==1701734766 //nine
) a=i=0; //reset i and a
}


int constants gets unnecessary large by shifting a<<8
but in case you can compare to strings somehow it should be the most natural

146 Using string comparison

#define s|a==*(int*)
a,b;main(i){for(;~(b=getchar());printf(" %c"+!!i,b),a|=b<<8*i++)if(i>4 s"zero"s"one"s"two"s"four"s"five"s"six"s"nine")a=i=0;}


Using String comparison

Obfuscated

#define F(x)if(scanf(#x+B,&A)>0){printf(#x,&A);continue;}
B;A;main(i){for(;i;){B=1;F(\40e%4s)F(\40th%3s)F(\40se%3s)F(\40o%2s)B=2;F(\40tw%1s)F(\40si%1s)B=1;F(\40%4s)i=0;}}

• 136 bytes Commented Dec 31, 2022 at 9:44

# Python 3, no regex,  83 68 65  63 bytes

-15 thanks to Lynn (refactor into a single function)
-3 more thanks to Lynn (avoid indexing into a list with more arithmetic)
...leading to another save of 2 bytes (avoiding parenthesis with negative modulos) :)

def f(s):h=ord(s[0])*ord(s[1])%83%-7%-3+5;print(s[:h]);f(s[h:])


A function which prints the words separated by newlines and then raises an IndexError.

Try it online! (suppresses the exceptions to allow multiple runs within the test-suite)

• I'm revisiting this a lot later and realizing this could be 68 bytes: def f(s):h=[4,5,3][ord(s[0])*ord(s[1])%83%7%3];print(s[:h]);f(s[h:])
– lynn
Commented Apr 8, 2018 at 6:34
• Oh wow, h(s) and h(s) how did I not notice?! Thanks Lynn! Commented Apr 9, 2018 at 7:11
• I'm not sure how I keep coming back to this question and noticing new things, but h=(ord(s[0])*ord(s[1])%83%7+1)%3+3 is 65 bytes! :)
– lynn
Commented Jun 22, 2018 at 16:59
• Heh, thanks Lynn, that allowed two more bytes to be golfed off too! Commented Jun 22, 2018 at 17:31

# Jelly, 44 bytes

Ṛ¹Ƥz⁶ZUwÐ€“¢¤Ƙƒ⁺6j¹;Ċ-ḶṃżṃgɼṘƑUẏ{»Ḳ¤$€Ẏḟ1Ṭœṗ  Try it online! • Sorry man, you've been outgolfed twice. Commented Nov 20, 2017 at 21:53 Quite long one. You are welcome to golf it down. # R, 109 bytes function(x)for(i in utf8ToInt(x)){F=F+i;cat(intToUtf8(i),if(F%in%c(322,340,346,426,444,448,529,536,545))F=0)}  Try it online! • Any way to use unicode characters instead of digits? Commented Nov 21, 2017 at 21:37 • Nice application of intToUtf8! 90 bytes would be possible by using a different approach using regexp: function(x,p=paste,z=p("(",p(c("zero",broman::numbers),collapse="|"),")"))gsub(z,"\\1 ",x) Commented Apr 9, 2018 at 11:49 # Haskell, 81 bytes f[c]=[c] f(h:t)=[' '|s<-words"z one tw th f s ei ni",and$zipWith(==)s$h:t]++h:f t  Try it online! Explanation: f(h:t)= h:f t -- recurse over input string [' '|s<- ]++ -- and add a space for each string s words"z one tw th f s ei ni" -- from the list ["z","one","tw","th","f","s","ei","ni"] ,and$zipWith(==)s$h:t -- which is a prefix of the current string  # Python 3 (no regex), 85 bytes i=3 while i<len(s): if s[i-3:i]in'ineiveroneghtwoureesixven':s=s[:i]+' '+s[i:] i+=1  Try it online! • Welcome to PPCG! Commented Nov 23, 2017 at 19:10 • It's nice, but a full program must include the code to take input. Commented Nov 25, 2017 at 15:24 • So, as a full program 104 bytes. However you can save 4 by using while s[i:] and then you can get that down to 93 bytes by submitting a recursive lambda (functions only need to return the output rather than print it themselves). Commented Nov 25, 2017 at 19:13 # Excel, 181 bytes =SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(A1,"z"," z"),"on"," on"),"tw"," tw"),"th"," th"),"f"," f"),"s"," s"),"ei"," ei"),"ni"," ni")  Places a space in front of: z, on, tw, th, f, s, ei, ni # Jelly, 40 39 bytes “¢¤Ƙƒ⁺6j¹;Ċ-ḶṃżṃgɼṘƑUẏ{»Ḳe€€@ŒṖẠ€TḢịŒṖK  Try it online! ## How it works “¢¤Ƙƒ⁺6j¹;Ċ-ḶṃżṃgɼṘƑUẏ{»Ḳe€€@ŒṖẠ€TḢịŒṖK “¢¤Ƙƒ⁺6j¹;Ċ-ḶṃżṃgɼṘƑUẏ{» = the compressed string of the digit names Ḳ = split at spaces e€€@ŒṖ = check whether each member of each partition of the argument is a digit. Ạ€ = A function that checks whether all values of an array are true, applied to each element. T = Finds the index of each truthy element Ḣ = Grab the first element, since we have a singleton array ịŒṖ = The previous command gives us the index, partition that splits the input into digits. This undoes it and gives us the partition. K = Join the array of digits with spaces  # QuadS, 21 20 bytes ..[eox]|[tse]?.... &  Try it online! This is a port of my retina answer. # APL (Dyalog Unicode), 25 bytes '..[eox]|[tse]?....'⎕S'&'  Try it online! # Scala 3, 45 bytes Use Regular Expression (scala.util.matching.Regex) Golfed version. Attempt This Online! """..[eox]|[tse]?....""".r.findAllIn(_).toSeq  Ungolfed version. Attempt This Online! import scala.util.matching.Regex object Main { def main(args: Array[String]): Unit = { val pattern: Regex = """..[eox]|[tse]?....""".r val input = "threesevensevensixninenineninefiveeighttwofiveeightsixthreeeight" val matches = pattern.findAllIn(input).toList println(matches.mkString(", ")) } }  # Jelly, 36 bytes œṣj⁶;$}
W;“€ɗİẒmṫṃ¦¦ạỊɦ⁼Fḷeṭḷa»s2¤ç/


Try it online!

Algorithm:

for x in ['ze', 'ni', 'on', 'tw', 'th', ...]:
replace x in input by space+x


I bet we can do even better.

# Mathematica, 125 bytes

(s=#;While[StringLength@s>2,t=1;a="";While[FreeQ[IntegerName/@0~Range~9,a],a=s~StringTake~t++];Print@a;s=StringDrop[s,t-1]])&


TIO outputs an error message about "CountryData"(???)
I don't know why this happens, but eveything works fine on Mathematica

# Perl 6,  42  30 bytes

*.comb(/<{(0..9).Str.uninames.lc.words}>/)


Test it

{m:g/..<[eox]>||<[tse]>?..../}
`

Test it