# [Clean]

 - `1 < n <= 23` in 28 seconds on TIO (17 seconds locally)
 - `n = 24 (2311294134347173535961967837989)` in 25 seconds locally
 - `n = 25 (23112941343471735359619678378979)` in 134 seconds locally (22 seconds is GC)

This uses a similar approach to Arnauld's JS solution based on recursive permutation rejection, using a specialized tree-set to gain a lot of speed.

For every prime that needs to fit in the number:

1. check if the prime is a sub-string of another prime, and if so, remove it
2. sort the current list of prime sub-strings, join it, and add it to the balanced tree set
3. check if any primes fit on the front of any other ones, and if so, join them

Then, for each pair of sub-strings that we joined, prepend that joined pair to the list of sub-strings and recuse.

Once no more sub-strings can be joined to any other sub-strings on any arm of our recursion, we use the already-ordered tree set to quickly find the lowest number containing the substrings.

*I believe that there is still significant optimization to be made in determining which numbers to attempt merges with using this approach, and that that is probably the best avenue for me to pursue*  
*Using Integers instead of Strings (not Ints) may also be a good idea if memory is an issue*

<pre><code>module main
import StdEnv,StdOverloadedList,_SystemEnumStrict
import Data.List,Data.Func,Data.Maybe
import Text

// adapted from Data.Set to work with a single specific type, and persist uniqueness
:: Set a = Tip | Bin !Int !a !.(Set a) !.(Set a)

delta :== 4
ratio :== 2

:: NumberType :== String

makeSetType e = e//{len=size e,str=e}

:: SetType :== NumberType//= {len :: Int, str :: String}

toNumberType = toString

//uSingleton :: !SetType -&gt; Set
uSingleton x :== (Bin 1 x Tip Tip)

// adapted from Data.Set to work with a single specific type, and persist uniqueness
uFindMin :: !.(Set .a) -&gt; .a
uFindMin (Bin _ x Tip _) = x
uFindMin (Bin _ _ l _)   = uFindMin l

uSize set :== case set of
&#9;Tip = (0, Tip)
&#9;s=:(Bin sz _ _ _) = (sz, s)

uInsertM :: (a a -&gt; .Bool) -&gt; (a u:(Set a) -&gt; v:(.Bool, w:(Set a))), [v u &lt;= w]
uInsertM cmp = uInsertM`
where
&#9;//uInsertM` :: !.a !.(Set .a) -&gt; .(.Bool, .(Set .a))
&#9;uInsertM` x Tip = (False, uSingleton x)
&#9;uInsertM` x set=:(Bin _ y l r)
&#9;&#9;| cmp x y//sx &lt; sy || sx == sy &amp;&amp; x &lt; y
&#9;&#9;&#9;# (t, l) = uInsertM` x l
&#9;&#9;&#9;= (t, uBalanceL y l r)
&#9;&#9;&#9;//= (t, if(t)(\y` l` r` = Bin sz y` l` r`) uBalanceL y l r)
&#9;&#9;| cmp y x//sx &gt; sy || sx == sy &amp;&amp; x &gt; y
&#9;&#9;&#9;# (t, r) = uInsertM` x r
&#9;&#9;&#9;= (t, uBalanceR y l r)
&#9;&#9;&#9;//= (t, if(t)(\y` l` r` = Bin sz y` l` r`) uBalanceR y l r)
&#9;&#9;| otherwise = (True, set)

// adapted from Data.Set to work with a single specific type, and persist uniqueness
uBalanceL :: .a u:(Set .a) v:(Set .a) -&gt; w:(Set .a), [v u &lt;= w]
//!a !.(Set a) !.(Set a) -&gt; .(Set a)
uBalanceL x Tip Tip
&#9;= Bin 1 x Tip Tip
uBalanceL x l=:(Bin _ _ Tip Tip) Tip
&#9;= Bin 2 x l Tip
uBalanceL x l=:(Bin _ lx Tip (Bin _ lrx _ _)) Tip
&#9;= Bin 3 lrx (Bin 1 lx Tip Tip) (Bin 1 x Tip Tip)
uBalanceL x l=:(Bin _ lx ll=:(Bin _ _ _ _) Tip) Tip
&#9;= Bin 3 lx ll (Bin 1 x Tip Tip)
uBalanceL x l=:(Bin ls lx ll=:(Bin lls _ _ _) lr=:(Bin lrs lrx lrl lrr)) Tip
&#9;| lrs &lt; ratio*lls
&#9;&#9;= Bin (1+ls) lx ll (Bin (1+lrs) x lr Tip)
&#9;# (lrls, lrl) = uSize lrl
&#9;# (lrrs, lrr) = uSize lrr
&#9;| otherwise
&#9;&#9;= Bin (1+ls) lrx (Bin (1+lls+lrls) lx ll lrl) (Bin (1+lrrs) x lrr Tip)
uBalanceL x Tip r=:(Bin rs _ _ _)
&#9;= Bin (1+rs) x Tip r
uBalanceL x l=:(Bin ls lx ll lr) r=:(Bin rs _ _ _)
&#9;| ls &gt; delta*rs
&#9;&#9;= uBalanceL` ll lr
&#9;| otherwise
&#9;&#9;= Bin (1+ls+rs) x l r
where
&#9;uBalanceL` ll=:(Bin lls _ _ _) lr=:(Bin lrs lrx lrl lrr)
&#9;&#9;| lrs &lt; ratio*lls
&#9;&#9;&#9;= Bin (1+ls+rs) lx ll (Bin (1+rs+lrs) x lr r)
&#9;&#9;# (lrls, lrl) = uSize lrl
&#9;&#9;# (lrrs, lrr) = uSize lrr
&#9;&#9;| otherwise
&#9;&#9;&#9;= Bin (1+ls+rs) lrx (Bin (1+lls+lrls) lx ll lrl) (Bin (1+rs+lrrs) x lrr r)

// adapted from Data.Set to work with a single specific type, and persist uniqueness
uBalanceR :: .a u:(Set .a) v:(Set .a) -&gt; w:(Set .a), [v u &lt;= w]
uBalanceR x Tip Tip
&#9;= Bin 1 x Tip Tip
uBalanceR x Tip r=:(Bin _ _ Tip Tip)
&#9;= Bin 2 x Tip r
uBalanceR x Tip r=:(Bin _ rx Tip rr=:(Bin _ _ _ _))
&#9;= Bin 3 rx (Bin 1 x Tip Tip) rr
uBalanceR x Tip r=:(Bin _ rx (Bin _ rlx _ _) Tip)
&#9;= Bin 3 rlx (Bin 1 x Tip Tip) (Bin 1 rx Tip Tip)
uBalanceR x Tip r=:(Bin rs rx rl=:(Bin rls rlx rll rlr) rr=:(Bin rrs _ _ _))
&#9;| rls &lt; ratio*rrs
&#9;&#9;= Bin (1+rs) rx (Bin (1+rls) x Tip rl) rr
&#9;# (rlls, rll) = uSize rll
&#9;# (rlrs, rlr) = uSize rlr
&#9;| otherwise
&#9;&#9;= Bin (1+rs) rlx (Bin (1+rlls) x Tip rll) (Bin (1+rrs+rlrs) rx rlr rr)
uBalanceR x l=:(Bin ls _ _ _) Tip
&#9;= Bin (1+ls) x l Tip
uBalanceR x l=:(Bin ls _ _ _) r=:(Bin rs rx rl rr)
&#9;| rs &gt; delta*ls
&#9;&#9;= uBalanceR` rl rr
&#9;| otherwise
&#9;&#9;= Bin (1+ls+rs) x l r
where
&#9;uBalanceR` rl=:(Bin rls rlx rll rlr) rr=:(Bin rrs _ _ _)
&#9;&#9;| rls &lt; ratio*rrs
&#9;&#9;&#9;= Bin (1+ls+rs) rx (Bin (1+ls+rls) x l rl) rr
&#9;&#9;# (rlls, rll) = uSize rll
&#9;&#9;# (rlrs, rlr) = uSize rlr
&#9;&#9;| otherwise
&#9;&#9;&#9;= Bin (1+ls+rs) rlx (Bin (1+ls+rlls) x l rll) (Bin (1+rrs+rlrs) rx rlr rr)
&#9;&#9;&#9;
primes :: [Int]
primes =: [2: [i \\ i &lt;- [3, 5..] | let
&#9;&#9;checks :: .[#Int!]
&#9;&#9;checks = TakeWhile (\n = i &gt;= n*n) primes
&#9;in All (\n = i rem n &lt;&gt; 0) checks]]

//primePrefixes :: [[NumberType]]
primePrefixes :== Tl (Scan removeOverlap [|] [toNumberType p \\ p &lt;- primes])

removeOverlap :: !.[NumberType] !.NumberType -&gt; [NumberType]
removeOverlap subs nsub = insertSorted subs
where
&#9;insertSorted :: !.[NumberType] -&gt; [NumberType]
&#9;insertSorted [|] = [|nsub]
&#9;insertSorted [|h: t]
&#9;&#9;|indexOf h nsub &lt;&gt; -1
&#9;&#9;&#9;= insertSorted t
&#9;&#9;| nsub &gt; h
&#9;&#9;&#9;= [|h: insertSorted t]
&#9;&#9;| otherwise
&#9;&#9;&#9;= [|nsub, h: Filter (\s = indexOf s nsub == -1) t]

getMergeCandidate :: !.NumberType !.NumberType -&gt; .Maybe NumberType
getMergeCandidate a b
&#9;| a == b = Nothing
&#9;| otherwise
&#9;&#9;= last_prefix max_len
where
&#9;sa = size a - 1
&#9;max_len = min sa (size b - 1)
&#9;last_prefix :: !.Int -&gt; .Maybe NumberType
&#9;last_prefix 0 = Nothing
&#9;last_prefix n
&#9;&#9;| b%(0,n-1)== a%(n,sa)
&#9;&#9;&#9;= Just (a%(0,n-1) + b)
&#9;&#9;| otherwise
&#9;&#9;&#9;= last_prefix (dec n)

mergeString :: !.NumberType !.NumberType -&gt; .NumberType
mergeString a b = first_prefix (max (size a - size b) 0)
where
&#9;sa = size a - 1
&#9;first_prefix :: !.Int -&gt; .NumberType
&#9;first_prefix n
&#9;&#9;| b%(0,sa-n) == a%(n,sa)
&#9;&#9;&#9;= a%(0,n-1) +++. b
&#9;&#9;| otherwise
&#9;&#9;&#9;= first_prefix (inc n)

&#9;&#9;&#9;
//uFilterSt :: !(a -&gt; .b -&gt; .(Bool,.b)) -&gt; .[a] -&gt; .b -&gt; .(.[a],.b)
uFilterSt fn :== uFilterSt`
where
&#9;uFilterSt` :: !.[a] !.b -&gt; .(.[a], .b)
&#9;uFilterSt` [|] s = ([|], s)
&#9;uFilterSt` [|h:t] s
&#9;&#9;# (iff, s) = fn h s
&#9;&#9;| not iff
&#9;&#9;&#9;= uFilterSt` t s
&#9;&#9;# (t, s) = uFilterSt` t s
&#9;&#9;| otherwise
&#9;&#9;&#9;= ([|h:t], s)
&#9;
&#9;
:: CombType :== NumberType
// todo: keep track of merges that we make independent of the resulting whole number
mapCandidatePermsSt :: !.[.[NumberType]] !u:(Set .NumberType) -&gt; v:(Set NumberType), [u &lt;= v]
mapCandidatePermsSt [ ] returnSet = returnSet
mapCandidatePermsSt [h:t] returnSet
&#9;#! (mem, returnSet) = uInsertM minFinder h` returnSet
&#9;| mem
&#9;&#9;= mapCandidatePermsSt t returnSet
&#9;| otherwise
&#9;&#9;# merges = [removeOverlap h y \\ x &lt;- h, (Just y) &lt;- Map (getMergeCandidate x) h]
&#9;&#9;# returnSet = mapCandidatePermsSt merges returnSet
&#9;&#9;= mapCandidatePermsSt t returnSet
&#9;&#9;//= mapCandidatePermsSt (merges ++| t) returnSet
where
&#9;h` = foldl mergeString "" h
&#9;dropper e s
&#9;&#9;# (mem, s) = uInsertM (&lt;) e s
&#9;&#9;= (not mem, s)
&#9;
&#9;&#9;
containmentNumbersSt = [ uFindMin (mapCandidatePermsSt [p] Tip) \\ p &lt;- primePrefixes]&#32;

minFinder :== (\a b = let sa = size a; sb = size b in if(sa == sb) (a &lt; b) (sa &lt; sb))

Start = [(i, ' ',n , '\n') \\ i &lt;- [1..] &amp; n &lt;- containmentNumbersSt]
</code></pre>

[Try it online!][TIO-jp7kdwt8]

[Clean]: https://github.com/Ourous/curated-clean-linux
[TIO-jp7kdwt8]: https://tio.run/##tVhtb9u2Fv5s/YqTFWulxrH7svuhvnGA220FOjRbkQTYB8dIaJuOhVCUR9KJ3Xl//eaecyhZlKWm3S6KIq7I88rnHD4SOVVS6IeHLJ@tlIRMpDpKs2VuHJy72c/6rov//XYnjcrFTM4@pNZ1r8431snsZ73Kzp1Jp660@Ek40WMVfnq30lP/dCo2E1lqXci1i6J@H8RMLJ2cwdzkmbc9lw5cDve5uYX71C1AgE31DSZml3KaztMpuM1SdkHoGSylsRgLVjr9YyW1tDYaDIBcCBjCRbqELbxNNRy81w4OBBz0YhYm1VMUzaRyAgbDIfwQGeHSnJ9fReTq11U2keYCA/IkrVXfRFEmbiWa87zESLLf/1NJPbTpJ5zoWmeG8q@oyGVnXTnr94dABoAamFoX0IKevX@0dHkQeYiAlJH7/dU5w@Fytj4oAxydUKwokK45aEzrf4kDQgP/km@E@@pdqmenqU/Kg9tDnDGtnqiEnM1Vkc1VgktbN4RXoEgEKNyJVEQrQ3At@qVlTYX1g3wedcgZrvRF1y@wY4cD9mU/sTuOE9tPCDOufvVeW2ncKSUaC1wmpfg2zxUnizOrQdkkOL4bxCzswn05nSRdGN3BCo6HcD@u/E0zyqIcXkf3C2lk1MGKlVMeG7GPTxlhN4tLqGw8VriAd0JZxD@s8J4i4lGs/Ao2iKJBeWfLia1h0@/bNRyD3cB2C/iIKOLz06dAsxvU7DyBGHtRJeEyUKpINmTZ6q1QQk/lh8p/h5qZZOk8dkl8ubkGdQ3mGp0UNShnkjZzn94G1pzeSWt6J2F6Zj8900zv7P9L7yxML3dYx/vU0j6ML8wKS4A4f7NttEMIe6W3a0ZqlbtB2DX3u1GtG/v9dprzjVZQXhVkRwtRx@MRMEVNTe0a62rHJKHdK9J5xEp5t@XIrHlf1ly85umCr1RFWC0U9tkYKsyTd/5@oq@92lc6VbbmVeG48KtMOWcsJ66Mwj9TrmnLgmPgN8pzNIw6RQbxy0NlkzANmjE4RU4KCsNeR4e2S2654Zn9cFCIDItMKDJR2K2NcCW2NFb2kLyXSXCMKpMyFdPEhcAqF25KLKIqkjdlrUfxRO9Jm6MtaZwAv5KfG4/Zzs@1N3xkmUV83Lsl@9aM/0YVees3K9gIVq@isUEh2ckjdXyskHtLbIb92mpyRlVBzbfmrbN/yFuV/VdR0tleL4a0FFJSvRObVqaYMHu0kVR0UXFSQEnmC07LJ7WuWChwqdp8FjOmhZLOmlsP1UzZ0Fh89mmw@oZ31k5xt7d4c5Fi2dDG1CiJeiRoKu6nIqji9VK7YgBsV/yt2hUHhciwyISiz@9VDqfCeGHAsIGxg40qsjO0rUwdl4BaKsqP6uS3/25qt9tHl0MRahUlqTolnV17tX9ESWz8NyrIrNCsYCNYSA22LKTalfGxOj5WyC9RUlhNjlsF/lI50Vu0NGkmLbHHCE9E43I8xPEr/Evh8hJSOD6C0esu/KvXG@PRDr@C0Xa6kNNbtuyNnqDtwbiaxEMgHtV@X6RIZPGlxnEKJ0PQz3UCPkLUwcT@QxxeiI3MQMPxCbxIwHsZj4k0Wf2jkfN0XeQ5qs5o4yLhSo6frxfo9HwqNLnM7yQfocUSRtsxjGoHvCUtbkmL8zmNkaXrNnxwCOPhMHCAvBoK94ztamJB4y@tj7@bz/EQjvRPgrIra4JmuP0IdX1a0hB/KUhTthiAo5ps8TAn17/NYeGzQYyPXvpWqlk4bjZWOYGFV2Avda1xW0/6HLqA2u9S5aTBulpetw9dAjHE0An5iG6kO5XmRv6Ib7Z0Jpz0qw/Q3Yfa32UEx/kWHwImRAqCIhHuv2KedIBvEIUS1l0tuW0gE@srJXVZEkuXGHylgIdUQKQKOc5mdHQReKgl6YSkuItCV7wGuvVozbem@iJMLxRoBnjyPR6sNaKFKxHfx7prReKx/mWF3wCxKOVwCJOkrSahz3gmp6CxvzPCy99qfBnwIPXQTjC089QE/hGjAhYCzeOT4F7@LKg18xpsIWA1rQAYK46QShrQBKAcHvaoF5qw1PNOtceFuLDfX/nmPXecUewvKSb@EMeXBb1J4s90IzEOhTQmYVR5mGtmo93E7maimik2vCBaCR0BeQr1aKPTdorxga9T6sLFwKHcv0fS@Zw0qEAad7z1mzp3eBCfewACU1daucKmIWvAF/twPgv8h0v4Mc8mLbdt9MXr8lk@gFspl@CMmN5CPgduJQtuIRzc08XnrWSeWEr80XS1hDJ8@0m7Uo767X6R43tEs@MoE8vddv8oTWaLYvVGNeZESMtv4Wq2vFqi6WAWv4j5e/hu3Op9BGNMxq2MJrth9dyuzcWodDpPDnB3yKxbzYU3KUQpdNuGjLm4Ds22iFPGVNUWxdVVwxo9KQFGVq6/kBawoTfemt54iy7ETCSbhIanKI6bdLpOYDFmnyEAbQkVMYOsviZzviVq04oLf4eHW0C8KpNiDy3oImmeq5mCkJm@@45eXDOTL/GsBLLsbobf1mCPj5NCji1N26PQoZ7uRNNcO5HqDNvR9wnlhHhWN6Nxa@2XY3@mqH1XlB8nY0D63VWbL4kvPZXiBxUEBPlvsJNyMMGtQXdoll9pFjkVWemYuJWmjmkG2evcCUMJOtpLr17DKE678AyedTXg/5f6WVJ9x72kj7in9K11BG3rHD88/Hc6V@LGPhy9//Dw00aLLJ36wUcl3Dw32cPR5OFo8fDmzZvsfw "Clean – Try It Online"

Save to `main.icl` and compile with: `clm -fusion -b -IL Dynamics -IL StdEnv -IL Platform main`

This produces a file `a.out` which should be run as `a.out -h <heap_size>M -s <stack_size>M`, where `<heap_size> + <stack_size>` is the memory that will be used by the program in megabytes.  
(I generally set the stack to 50MB, but I rarely have programs use even that much)