Simulate reproduction in a population of oozes

Reproductive strategy of oozes can be summarized as follows:

o O 8 oo

A population of oozes at generation N is described by a string, for example:

ooooo88ooOoOo8Ooooo8OOoo8Oo8Oo8oo8oooooo8OOooO88o


To compute the population at generation N+1, take one character randomly, and replace it by the result of ooze evolution:

• If o, replace by O
• If O, replace by 8
• If 8, replace by oo

Generation 0 is the one-character string o. Your program or function should get a positive number N, and output the population of oozes at generation N. At each generation, each ooze should have the same chance (to within 1% accuracy) to evolve.

Test cases (actual results may vary due to randomness):

1 -> O
2 -> 8
3 -> oo
4 -> Oo
5 -> OO
10 -> OO8
20 -> oo8oooo
30 -> oOoooOO8O
100 -> 8o8oO8OOooOo8OOoo88oOO8OOO
200 -> 88OOo8oooo8OOoooOoOOO8O8OO88o8Oo8oooOooooooOo8OooO888
1000 -> 8Ooooo88OO8o8O8oooOOoOOO88oOoO88OOoO8OOOooOOo8OO88O8OO8OO8ooo8oOO8OoooO88OooooooooOo8oooOO8OO8Oo8Oooo8O88oooOooO8Oo8ooOOOOo8OooOo8OoooooooO8oOooooOooOO8oOO8ooo8Ooooo8ooo8ooOOooO8OOoO8oO8oo88ooo8oO8OO8o888oO8OOoO8o8ooO8O88OOOoO8oooO8OoOoOo88O88oo88ooo8oO8o8oOooOo


If your solution has running time of O(n), please specify it explicitly!

• Can an output be an array? – Corsaka Nov 20 '19 at 15:01
• Seems reasonable... – anatolyg Nov 20 '19 at 15:10
• does order matter? Can different stages be separated? – frank Nov 20 '19 at 18:29
• Of course the order matters! Otherwise, you could output a sorted string; it would be boring. – anatolyg Nov 20 '19 at 18:42
• I am interested in any such solution because I thought for some time whether it was possible, and couldn't come to a conclusion. I don't always understand solutions in bizarre languages, so I don't want to miss such a great solution if it appears. – anatolyg Nov 21 '19 at 21:39

Jelly, 21 17 bytes

1F‘JX$¦ḃ3Ʋ¡ị“oO8”  Try it online! A full program that takes n as its argument and implicitly outputs the state of the oozes after that many generations. Explanation 1 | Literal 1 Ʋ¡ | Repeat the following the number of times given by the argument F | - Flatten ‘$¦           | - Increment the item at the index given by:
J              |   - Sequence along the list
X             |   - Random number fron the sequence
ḃ3         | Convert to bijective base 3
ị“oO8” | Index into "oO8"

• "bijective base 3" - this is a great idea! – anatolyg Nov 24 '19 at 16:11

Python 3, 114 bytes

from random import*
def f(n,s=b'o'):k=randrange(len(s));return n and f(n-1,s[:k]+b'oO8o'[-~s[k]%3::3]+s[k+1:])or s


Try it online!

Interesting observation: the ascii values of oO8 are 111, 79, and 56, respectively. If you take these modulo 3, you get 0, 1 and 2.

Additional observation: you can validate the generation of your final string by substituting 'o' with 3, 'O' with 4 and '8' with 5; then taking the sum of your list and subtracting 3 from the total.

This works for all strings regardless of random variations, because each replacement in your string increases this sum by 1:

'o' (3) -> 'O'  (4)
'O' (4) -> '8'  (5)
'8' (5) -> 'oo' (3+3 = 6)


We then just need to correct for the offset of 3.

Validate it online!

• SECONDS ahead of me. :( great job! – Henry T Nov 20 '19 at 15:12
• Great observation on character codes! I'll use it in my answer, if I decide to make one. – anatolyg Nov 20 '19 at 15:49
• That observation saved me 14 bytes in my solution so thank you. – Malivil Nov 20 '19 at 16:10

APL (Dyalog Unicode), 4032 31 bytesSBCS

Full program. Prompts for N from stdin.

'o8O'[{∊⌷∘3(1 1)2@(?≢⍵)⊢⍵}⍣⎕,1]


Try it online!

'o8O'[] select the following characters from the string "oO8":

,1 the list [1]

⎕ prompt for N from stdin

{}⍣ apply the following anonymous lambda to [1], that N times:

⊢⍵ on the argument…

@()at position…

≢⍵ the length of the argument

? random index in that range

… apply the following tacit function:

⌷∘3(1 1)2 use the number to select from the list [3,[1 1],2]:

∊ϵnlist (flatten)

05AB1E, 26 bytes

'osFÐg<ÝΩ©è…oO8S„O8„ooª‡®ǝ


Try it online!

• Every generation, a single random element is evolved. – frank Nov 20 '19 at 21:36
• +1, and you're definitely not a moron - it's the little stuff like this that everyone gets wrong! – FryAmTheEggman Nov 20 '19 at 21:50
• @FryAmTheEggman you saved me 6 bytes, I'm thanking you honestly ;). – Magic Octopus Urn Nov 20 '19 at 21:53
• -1 by replacing Ðg<Ý ... ǝ with Dā ... <ǝ. Bummer that ǝ doesn't wrap around the index like all other indexing-builtins do.. Not sure if it's intentional or a bug. – Kevin Cruijssen Nov 21 '19 at 7:48

Pyth, 25 bytes

uXGJOlG@c"O 8 oo")C@GJQ\o


Try it online!

Written by @FryAmTheEggman, similar idea to my earlier post but much cleaner as it reduces instead of looping.

How it works

uXGJOlG@c"O 8 oo")C@GJQ\o
u                     Q\o - Reduce 'o' Q=input times
JOlG                   - With random number J in the range [0,length G),
where G is the previous list
XG                       - Replace the element in G at index J...
@c"O 8 oo")        - With an element of "O 8 oo" split by
whitespace, at index...
C@GJ    - ...of the codepoint of the element in G at index J


Pyth, 38 bytes

K\oJ["oo"\o\O\8)VQ=kOlK=KXKk@JhxJ@Kk;K


Try it online!

My original solution, less sophisticated.

How it works

K\oJ["oo"\o\O\8)VQ=kOlK=KXKk@JhxJ@Kk;K
K\o                                    - K = 'o', start
J["oo"\o\O\8)                       - J = ['oo', 'o', 'O', '8']
VQ                     - Loop Q=input times
=kOlK                - k = random number from 0..length of K
=K              - K equals...
XKk           - K with the element at index k replaced by...
@Jh        - The item in J at index+1 of...
xJ      - The index (in J) of...
@Kk   - The element of K at index k
;K - End loop, print K

• Good call, @FryAmTheEggman , reduce would be a better way to write this, I hadn't considered that. I also like the idea to use the codepoint of the character! Are you planning to post this as your own answer? – frank Nov 20 '19 at 21:30
• Ooo... you beautiful person. You're gonna make me find 2 more bytes, aren't you? – Magic Octopus Urn Nov 20 '19 at 22:03

Ruby, 79 77 73 72 61 bytes

->n,s=?o{n.times{s[w=rand(s.size)]=%w[O 8 oo][s[w].ord%3]};s}


Try it online!

-11 bytes thanks to Value Ink and Jitse

R, 10997 93 bytes

for(i in 1:scan())T=append(T,switch(T[j],"O",O=8,"8"=c("o","o")),j<-sample(sum(T>0),1))[-j];T


Try it online!

Output as an array.

Thanks to Robin Ryder for golfing out 6 bytes, for pointing out a bug in a previous version, and for golfing out an additional 4 bytes.

• I think this is equivalent in 103 bytes. – Robin Ryder Nov 21 '19 at 11:54
• @RobinRyder ahhh, of course. That can be golfed further down to 97. – Giuseppe Nov 21 '19 at 16:16
• @RobinRyder I am pretty sure I fixed it now by moving the assignment to i in the append instead of in the switch...No idea why that worked. – Giuseppe Nov 21 '19 at 17:54
• @RobinRyder I'm an idiot, I used i for the loop index and the index into o which is why it gave incorrect results. So on each pass it appends to after i = 1,2,3,4,5 -- which obviously goes beyond the end, hence why Oo goes to o8 instead of 8o. – Giuseppe Nov 21 '19 at 18:42
• 93 bytes by using T and changing the order of the arguments in switch. – Robin Ryder Nov 22 '19 at 12:42

Perl 6, 55 bytes

{('o',{S:p(rand*.comb)[.]=<O 8 oo>[$/.ord%3]}...*)[$_]}


Try it online!

Explanation

{                                                     }  # Anonymous block
,                                       ...*  # Infinite sequence
{                                     }  # Compute next item by
S  # replacing
:p(rand*.comb)  # starting at random position
[.]  # any character
=<O 8 oo>  # with "O", "8" or "oo"
[$/.ord%3] # depending on ASCII code mod 3 ( )[$_]  # nth item of sequence


J, 54 bytes

'oO8'{~(((1+2={)[(1"0@])}#(3|1+{)[]})~1?#)^:(]0:)


Try it online!

Wholly different approach to shave off 2 bytes

J, 56 bytes

'oO8'{~(({.,(i.@3(>:@i.{ ::0 0[){),>:@[}.])~1?#)^:(]0:)


Try it online!

Had an equal byte solution, but adjusted it slightly using Adam's idea of doing all the calculation in pure integers, and saving the translation-to-string step till last, which I felt looked a bit cleaner.

Perl 5, 79 bytes

sub f{$_[0]?do{$_=f(-1+pop);substr($_,rand length,1)=~y/oO8/O82/;s,2,oo,r}:"o"}  Try it online! sub f {$_[0]
? do{
$_ = f(-1+pop); substr($_,rand length,1) =~ y/oO8/O82/;
s,2,oo,r
}
: "o"
}

srand(7) and print "$_ -> ",f($_),"\n" for 1..40

• 60 bytes - TIO – Nahuel Fouilleul Nov 21 '19 at 10:44
• @NahuelFouilleul Good one, you took it to the next level! :) You could post this one as your own answer. – Kjetil S. Nov 21 '19 at 11:12
• just rewriting using iterative style for loop or more exactly eval x, because of $_ default variable also used as in loop – Nahuel Fouilleul Nov 21 '19 at 11:28 Charcoal, 38 bytes ⊞υoＦＮ«≡⊟υo⊞υO¦O⊞υ8Ｆ²⊞υo≔‽ＬυιＵＭυ§υ⁺λι»υ  Try it online! Link is to verbose version of code. Outputs an array, which is like a string but vertical (+1 byte to output the array horizontally). Explanation: ⊞υo  Start with a single o. ＦＮ«  Loop the given number of generations. ≡⊟υ  Remove the last character and switch on it. o⊞υO  If it's an o then push an O. O⊞υ8  If it's an O then push an 8. Ｆ²⊞υo  Otherwise push o twice. ≔‽ＬυιＵＭυ§υ⁺λι  Rotate the array by a random amount. This causes the oozes to be evolved in a random order. »υ  Finally output the resulting array. Japt, 3231 29 bytes _gZÊö _Ä %3ª1ö2Ãc}g[]á)Ëg"oO8  Try it  _ // function taking array gZÊö // replace a random element with : _Ä %3 // element incremented mod 3 ª1ö2Ã // or [0,0] c} // flattens g[]á) // run the function input times with initial value of array, return last element Ëg"oO8 // convert values to o O 8  Thanks to @Embodiment of Ignorance for reminding me I don't need = at beginning • You don't need the leading equal sign – Embodiment of Ignorance Nov 21 '19 at 5:23 Python 2, 104 bytes from random import* l=['o'] exec"i=randrange(len(l));l[i:i+1]='oO8o'[-~ord(l[i])%3::3];"*input() print l  Try it online! Golfing Jitse's solution using the mutability of lists. Java (JDK), 138 bytes n->{var s=new StringBuffer("o");for(int i;n-->0;s.replace(i*=Math.random(),i+1,"O,8,oo".split(",")[s.charAt(i)%3]))i=s.length();return s;}  Try it online! Credits • -2 bytes by using int i; and doing the i=s.length() before the s.replace. – Kevin Cruijssen Nov 21 '19 at 9:50 • Using string split saved 2 bytes in my C# implementation so thanks for the inspiration =) – Malivil Nov 21 '19 at 13:19 C# (Visual C# Interactive Compiler), 172158150149144137130 128 bytes a=>{dynamic r=new Random(),g="o",i;while(a-->0)g=g.Remove(i=r.Next(g.Length),1).Insert(i,"O,8,oo".Split(',')[g[i]%3]);return g;}  Try it online! -14 bytes thanks to @Jitse's observation about character modulus -8 bytes by renaming a variable I forgot about -1 byte by switching to a while loop -5 bytes by not saving the current character to a variable -7 bytes by switching to the interactive compiler -7 bytes by chaining declarations and inlining assignment thanks to @my pronoun is monicareinstate -2 bytes by changing to string split over character comparison thanks to @Olivier Grégoire for inspiration • 130 bytes, a few minor golfs: Try it online! – the default. Nov 21 '19 at 5:40 • Thanks! Using dynamic to chain the declarations of different types is a great idea. I didn't see that in the tips thread, you should add it there. – Malivil Nov 21 '19 at 13:06 • Some of my C, umm... puke below may save you a few bytes as well. – Michael Dorgan Nov 21 '19 at 18:24 • Maybe, but I don't see any thing obvious at first glance. I'll have to look more in-depth later – Malivil Nov 21 '19 at 18:53 Zsh, 75 73 bytes s=o m=(oo 8 O) repeat$1 ((n=1+RANDOM%$#s))&&s[n]=$m[m[(i)$s[n]]-1] <<<$s


I want to focus here specifically:

$m[m[(i)$s[n]]-1]
m[        ]     # from array $m: (i) # get the index of$s[n]      #   the nth character in the string (as a pattern)
-1   # subtract one
$m[ ] # substitute the element at that index  Python 3.8, 91 bytes f=lambda n,s='o':n and f(n-1,s[:(k:=id(s)//7%len(s))]+'oO8o'[-~ord(s[k])%3::3]+s[k+1:])or s  Try it online! I'll be perfectly honest, I don't know for sure that this is valid randomness. It seems like it to me, but I'm not sure how I'd determine one way or the other - if someone could confirm or deny, that'd be much appreciated. Assuming validity, this is a modification of Jitse's answer that gets rid of the import. id(s) gives a different integer every time, but just taking it modulo len(s) gave results that were too consistent. Integer dividing the id by 7 seemed to do the trick, but again, I'm not sure how to verify. If this turns out not to be valid, I'll delete immediately. • I guess randomness here is subjective, especially when it involves dividing by a small magic number. Seems random enough for this simulation, but I am not sure! – anatolyg Nov 20 '19 at 20:19 • I believe this demonstrates a significant problem with this approach. The first string always has the same id so the progression is always the same. This eventually stops when the strings are longer but it seems very loose to me. I don't think there is a precise consensus on this, so perhaps it is best to ask on meta? – FryAmTheEggman Nov 20 '19 at 21:58 • @FryAmTheEggman If you got the id of a new dict {} or new list [] each time it'd gain one byte but be a new object every time, guaranteed – famous1622 Nov 21 '19 at 20:10 • @famous1622 Briefly testing that in the TIO link from my comment gives largely the same problem. I couldn't say why for sure, but it may be being optimised away, or being freed then reused immediately. – FryAmTheEggman Nov 21 '19 at 20:28 JavaScript (ES10), 91 bytes Similar to the ES6 version, but returns an array of characters. n=>(g=P=>n--?g(P.flatMap(c=>i--?c:'8O'[c|c>g]||['o','o'],i=Math.random()*P.length|0)):P)o  Try it online! JavaScript (ES6), 98 ... 94 93 bytes n=>(g=P=>n--?g(P.replace(/./g,c=>i--?c:'8O'[c|c>g]||'oo',i=Math.random()*P.length|0)):P)('o')  Try it online! Commented n => ( // n = number of generations g = P => // g is a recursive function taking the population string P n-- ? // decrement n; if it was not equal to 0: g( // do a recursive call: P.replace(/./g, c => // for each character c in P: i-- ? // if we haven't reached the requested position: c // leave c unchanged : // else: '8O'[ // replace with: c | c > g // 'O' is c != 8 and c is lowercase ] // '8' is c != 8 and c is uppercase || 'oo', // 'oo' if c = 8 i = Math.random() // compute a random position i within the current * P.length | 0 // population string ) // end of replace() ) // end of recursive call : // else: P // return the final population )('o') // initial call to g with P = 'o'  Red, 93 bytes func[n][s: copy"o"loop n[replace at s i: random length? s s/:i select[#"o"#"O"#"8":oo]s/:i]s]  Try it online! K (oK), 51 bytes {"oO8"x{{,/(x#t),((1;2;0 0)t@x),(x+1)_t}1?#t:x}/,0}  Try it online! No doubt this can be done much shorter / more elegant (using ammend?). I'm somewhat happy that it works at least. Retina, 29 27 bytes Ko "$+"{@T\oo\O80.
0
oo


Try it online! Edit: Saved 2 bytes when I realised how I could combine @ with T. Explanation:

Ko


Replace the input with the initial generation.

"\$+"{


Repeat the remaining stages for the given number of generations.

@T\oo\O80.


The O is a metacharacter and needs to be escaped. The o is also a metacharacter, so the first o is literal, while the second o represents O80 from the destination pattern. This therefore transliterates the characters oO8 to O80. The final . means that each character is treated as a separate match for the purposes of the @ operator, which chooses one match (i.e. character) at random to be transliterated.

0
oo


Fix up any 0 to oo. (This stage does not need to be conditional.)

Python 3, 142 bytes

from random import*
def f(i,x='o'):i-=1;s=randint(0,len(x)-1);o={'o':'O','O':'8','8':'oo'};x=x[:s]+o[x[s]]+x[s+1:];return x if i<1else f(i,x)


Icon, 85 bytes

procedure f(n)
s:="o"
1to n&s[p:=?*s]:=["O",8,"oo"][find(s[p],"oO8")]&\z
return s
end


Try it online!

C (gcc), 101 bytes 104 117 bytes

char*h,g[9999];l;f(a){*g='o';for(l=1;a--;(*h="O8o"[*h%3])-'o'?:bcopy(h,h+1,l++))h=g+rand()%l;g[l]=0;}


Try it online!

(Many thanks to ceilingcat for the awesome code golfing help and ideas!)

Ok, except for large generation count buffer overflow and the fact that I seeded random outside the code, this now works. Making g bigger could put the overflow size above what an int can hold if needed.

Commented

char *h,g[9999]         // Work pointer and a fixed size buffer.
l;                      // int length of string (number of slimes)
f(a){                   // a is generation
*g='o'                // Set first value.
for(l=1;              // Length is 1.
a--;                 // consume a generation
(*h="O8o"[*h%3])     // Use the mod 3 trick to index into the next symbol and assign the new slime character.
-'o'?:               // if we went back to 'o', grow the string
bcopy(h,h+1,l++))    // move all the values up in in array to length (including new 'o' symbol) Note: bcopy() can overlap src and dest.
h=g+rand()%l;       // for each loop, find which slime to evolve and point h to it.
g[l]=0;}              // NUL Terminate the g buffer to make it a string.

• I get satisfaction when I can get a C solution to be competitive with newer languages that have so many built-in functions :) – Michael Dorgan Nov 21 '19 at 18:44

C (gcc) (with -m32 compiler flag), 174 172 bytes

Thanks to ceilingcat for the suggestions.

Not competing (there are already shorter versions in C), but this uses a different method.

Instead of using a hard-coded output buffer, I use a linked list on the stack to hold each cell's state and traverse the list to locate cells to mutate. If the cell splits in two, the function calls itself to hold the new cell state and adds itself into the list chain. As the list is not necessarily linear on the stack, the last function prints the list of cells (intermediate callers set their list start to NULL after control returns to them to prevent unwanted output.)

g(i,j,s,p)int*s,*p;{int a[2]={},r;for(j++?*a=*p,*p=a:(s=a);i--;++p[1]>2?p[1]=0,g(i,j,s,a),s=i=0:0)for(r=rand(p=s)%j;r--;p=*p);for(;s;s=*s)printf(L"oO8"+s[1]);}f(i){g(i,0);}


Try it online!

Lua, 106101 110 bytes

Changed Code to function format + 9 bytes

e={"O",8,"oo"}for i=1,t do s=math.random(1,#o)o=o:sub(1,s-1)..e[o:sub(s,s):byte()%3+1]..o:sub(s+1)end return o


Try it online!

• This can be reduced to 101 bytes: e={"O",8,"oo"}for i=1,t do s=math.random(1,#o)o=o:sub(1,s-1)..e[o:sub(s,s):byte()%3+1]..o:sub(s+1)end By shifting all elements in the lookup table by one removing, the temporary variable l is also unnecessary. – bauen1 Nov 23 '19 at 20:39
• Good find @bauen1:) – LuaNoob Nov 24 '19 at 15:18

APL(NARS), chars 62, bytes 124

{⍵{⍺=0:⍵⋄(⍺-1)∇(⍵↑⍨k-1),(('oO'⍸k⊃⍵)⊃"O"'8'"oo"),⍵↓⍨k←?≢⍵},'o'}


test:

  h←{⍵{⍺=0:⍵⋄(⍺-1)∇(⍵↑⍨k-1),(('oO'⍸k⊃⍵)⊃"O"'8'"oo"),⍵↓⍨k←?≢⍵},'o'}
h 0
o
h 4
oO
h 10
8OO
h 100
8oOOO8ooOO8oOOoO8oOooooO8OO
h 200
8OO888Ooo8OO8oO88O8oo8oooooOo8ooO8OooOO88OoO8O8o8Oo


but it has problems if the argument is >500, the 73 chars code below has not these problems:

r←f w;k
r←,'o'
→0×⍳0>w-←1⋄r←(r↑⍨k-1),(('oO'⍸k⊃r)⊃"O"'8'"oo"),r↓⍨k←?≢r⋄→2

f 1000
O8oooo8OoooO8ooOO88oo88OOOoo8oOoO8888oo88ooOOOoooooo8oooOO8OooO8O8oOO8oOOOo88Oo
Ooooo88O8o8ooooo8OOooOO8O8OOOoo8OoO8Oo88ooOOoooO888O8o8OoOOoooOo8oooo8OoO
88Oooo8O8OoooooooOo88oo8oooooo8OOoO8oOO8Oo8oo8oO88oo8OOoOoOOoOoOoo8O88o8O
88OoO8OOoOo8Oo8ooo8OoooooooooOOooO8oo88