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? Commented Nov 20, 2019 at 15:01
• Seems reasonable... Commented Nov 20, 2019 at 15:10
• does order matter? Can different stages be separated? Commented Nov 20, 2019 at 18:29
• Of course the order matters! Otherwise, you could output a sorted string; it would be boring. Commented Nov 20, 2019 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. Commented Nov 21, 2019 at 21:39

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! Commented Nov 20, 2019 at 15:12
• Great observation on character codes! I'll use it in my answer, if I decide to make one. Commented Nov 20, 2019 at 15:49
• That observation saved me 14 bytes in my solution so thank you. Commented Nov 20, 2019 at 16:10

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

• Using the observation from Jitse's Python solution I got 61 bytes since it let me cut the sub call at the end. Commented Nov 21, 2019 at 0:24

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. Commented Nov 20, 2019 at 21:36
• +1, and you're definitely not a moron - it's the little stuff like this that everyone gets wrong! Commented Nov 20, 2019 at 21:50
• @FryAmTheEggman you saved me 6 bytes, I'm thanking you honestly ;). Commented Nov 20, 2019 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. Commented Nov 21, 2019 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? Commented Nov 20, 2019 at 21:30
• Ooo... you beautiful person. You're gonna make me find 2 more bytes, aren't you? Commented Nov 20, 2019 at 22:03

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. Commented Nov 21, 2019 at 11:54
• @RobinRyder ahhh, of course. That can be golfed further down to 97. Commented Nov 21, 2019 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. Commented Nov 21, 2019 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. Commented Nov 21, 2019 at 18:42
• 93 bytes by using T and changing the order of the arguments in switch. Commented Nov 22, 2019 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


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! Commented Nov 21, 2019 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. Commented Nov 21, 2019 at 13:06
• Some of my C, umm... puke below may save you a few bytes as well. Commented Nov 21, 2019 at 18:24
• Maybe, but I don't see any thing obvious at first glance. I'll have to look more in-depth later Commented Nov 21, 2019 at 18:53

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 Commented Nov 21, 2019 at 10:44
• @NahuelFouilleul Good one, you took it to the next level! :) You could post this one as your own answer. Commented Nov 21, 2019 at 11:12
• just rewriting using iterative style for loop or more exactly eval x, because of $_ default variable also used as in loop Commented Nov 21, 2019 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 Commented Nov 21, 2019 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. Commented Nov 21, 2019 at 9:50 • Using string split saved 2 bytes in my C# implementation so thanks for the inspiration =) Commented Nov 21, 2019 at 13:19 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  Jelly, 21 16 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.

Thanks to @cairdcoinheringaahing for saving a byte!

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! Commented Nov 24, 2019 at 16:11 • Trivial golf, but -1 byte by removing the trailing ” Commented Apr 16, 2021 at 20:24 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! Commented Nov 20, 2019 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? Commented Nov 20, 2019 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 Commented Nov 21, 2019 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. Commented Nov 21, 2019 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 :) Commented Nov 21, 2019 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. Commented Nov 23, 2019 at 20:39 • Good find @bauen1:) Commented Nov 24, 2019 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  Lexurgy, 306 bytes Uses oO% for ooze states. Since Lexurgy doesn't have a random feature, the program deterministically moves a pointer from right to left in the ooze string, incrementing the ooze at that pointer. Iteration stops when the generation counter hits 0. Note: This seems to work all the way up to 83 generations; 84 or more generations has the interpreter complain about how the propagation doesn't converge to an endpoint. Class u {o,O,%} Class d {\1,\2,\3,\4,\5,\6,\7,\8,\9,\0} p: *=>ol/_$
e propagate:
@d=>{\0,\1,\2,\3,\4,\5,\6,\7,\8,s\9}/_
@d s=>{\0,\1,\2,\3,\4,\5,\6,\7,\8,\9} *
then:
\0 =>*/$_ \0=>*/$ _
then:
@u=>{O, %, oo}/_ l//$ _ then: @u$1 l=>l $1 then: *=>l/ l @u* _$
l=>*/ _
s:
{s,l,}=>*


Ungolfed explanation:

Class ooze {o, O, %}
Class next {O, %, oo}

Class digit {\1,\2,\3,\4,\5,\6,\7,\8,\9,\0}
Class dec   {\0,\1,\2,\3,\4,\5,\6,\7,\8,s\9}
Class dec2  {\0,\1,\2,\3,\4,\5,\6,\7,\8,\9}

# setup: generate initial digit-ooze sep, o, and pointer
pre:
* => ol / _ $# [gens left][oozes][growth pointer] # while the input was changed last iteration... evolve-once propagate: # decrement the generation counter @digit => @dec / _  @digit s => @dec2 * # handle carries then: # remove leading 0s \0  => * /$ _
\0 => * / $_ then: # grow an ooze if it's right before the growth pointer # but not when there's no generations left @ooze => @next / _ l //$  _
then:
# move the growth pointer to the left
@ooze$1 l => l$1
then:
# move the growth pointer to the end if it's at the left side of the ooze string
* => l /  l @ooze* _ \$
l => * /  _
# remove extraneous characters
strip:
{s,l,} => *
`