12
\$\begingroup\$

Your task is to write a program or function which:

  • When run for the first time, outputs its source code.
  • On subsequent executions, it should output what it output previously, but with one random character change (defined below). It does not have to be a uniformly random change, but every possible change should have a nonzero chance of occurring.

    After the first execution, your program will not necessarily be a quine anymore; the output will have changed (and the program is free to modify itself as well).

For example, if your quine was ABCD, repeatedly running it might print:

ABCD
A!CD
j!CD
j!CjD

Specifications

  • A character change is either:

    • The insertion of a random character,
    • The deletion of a random character, or
    • A replacement of a character with a new random character. Note that the new character is allowed to be the same as the one it replaces, in which case no change will be made.

    Of course, deleting or replacing a character from an empty string is not a valid change.

  • Despite this being tagged , the rules against reading your source code do not apply.

You can use any character set as long as it includes the characters used in your source code.

\$\endgroup\$
  • 1
    \$\begingroup\$ What characters does each character refer to? \$\endgroup\$ – Dennis Feb 6 '17 at 6:50
  • 2
    \$\begingroup\$ How often does this have to work? Clearly it can't be arbitrarily often or otherwise every possible program as longer or longer than the original one has to be a solution to the challenge. \$\endgroup\$ – Martin Ender Feb 6 '17 at 7:29
  • 1
    \$\begingroup\$ Can the character be added anywhere, or just at the end? \$\endgroup\$ – Conor O'Brien Nov 13 '17 at 13:38
  • 1
    \$\begingroup\$ @ConorO'Brien Anywhere. \$\endgroup\$ – Esolanging Fruit Nov 13 '17 at 16:02
  • 1
    \$\begingroup\$ How many iterations does it have to work? \$\endgroup\$ – dylnan May 17 '18 at 14:06
7
\$\begingroup\$

Python 3, 288 270 224 212 195 196 194 180 178168 bytes

f=__file__
m=open(f).read()
x=m	
print(end=x)
h=hash
k=h(f)
n=k%2
a=h(m)%-~len(x)
x=x[:a]+(not(k%3)*x)*chr(k%127)+x[a+n:]
open(f,'w').write(m.replace("\t",";x=%r\t"%x))

Try it online!

After printing the file source code on the first iteration, we append an extra line to set x to the new source code, rather than m.

Explanation:

f=__file__    #Open and read the source code
m=open(f).read()

x=m       #Set x to the source code for the first iteration
x="..."
...
x="..."   #Set x to the latest iteration
          #On the last iteration there's a tab character to mark progress
print(end=x)    #Print the previous iteration's text

#Modify the text
h=hash
k=h(f)            #Generate a random number to use
n=k%2             #Whether the character will be inserted or changed/deleted
a=h(m)%-~len(x) #The index of the change
                         #Add 1 to the range to append new characters, and to avoid mod by 0 in the case of an empty string
x=x[:a]+(not(k%3)*x)*chr(k%127)+x[a+n:]    #Make the change

open(f,'w').write(m.replace("\t",";x=%r\t"%x))   #Modify the source code, adding the new iteration of the source code

Assuming hash returns a uniformly random number, there is about 1/6 chance of inserting a new character, 1/6 chance of changing an existing character and 2/6 chance of deleting a character. What's the remaining 2/6 chance you ask? Why, it does nothing at all 2/6 of the time!

(Here's a validation program adapted from mbomb007's answer's. Try it online!)

\$\endgroup\$
  • \$\begingroup\$ I think f=__file__ would help in the first step too. \$\endgroup\$ – Ørjan Johansen May 18 '18 at 5:58
4
\$\begingroup\$

Python 3, 205 195 bytes

s='print(end=x);h=hash;k=h(x);n=k%2;a=h(s)%-~len(x);x=x[:a]+(not(k%3)*x)*chr(k%127)+x[a+n:];open(__file__,"w").write("s=%r;x=%r;exec(s)"%(s,x))';x='s=%r;x=%r;x=x%%(s,x);exec(s)';x=x%(s,x);exec(s)

Try it online!

Wanted to try a version that doesn't read the source code. Turned out not a bad as I thought, and it's only 30 or so bytes behind the version that does. The explanation for how it works is mostly the same as the other answer, but it initialises x differently since it can't just read the source code.

\$\endgroup\$
4
\$\begingroup\$

Python 2, 779 801 bytes

Though the challenge was edited to show that reading your source is allowed, I was already creating my solution without that. So, to show that it's possible, I finished it. No reading of the source file:

s='s=%r;print s%%s\nfrom random import*;L=4;f=open(__file__,"wa"[L>5]);R=randint\nf.write("\\n".join((s%%s).split("\\n")[1:5:2]).replace("4",`map(ord,s%%s)`))\nif L>5:exec\'b=[];h=%%d\\nwhile~-h:b+=[h%%%%1000];h/=1000\\nwhile b:r,p,n=b[-3:];b=b[:-3];L=[L[:p]+L[p+1:],L[:p]+[r]+L[p+n:]][n<2if L else 1]\\nprint"".join(map(chr,L))\'%%1\n\nn=R(0,2);p=R(0,len(L if L>5else s%%s));r=R(0,255);f.write("%%03d"*3%%(n,p,r))';print s%s
from random import*;L=4;f=open(__file__,"wa"[L>5]);R=randint
f.write("\n".join((s%s).split("\n")[1:5:2]).replace("4",`map(ord,s%s)`))
if L>5:exec'b=[];h=%d\nwhile~-h:b+=[h%%1000];h/=1000\nwhile b:r,p,n=b[-3:];b=b[:-3];L=[L[:p]+L[p+1:],L[:p]+[r]+L[p+n:]][n<2if L else 1]\nprint"".join(map(chr,L))'%1

n=R(0,2);p=R(0,len(L if L>5else s%s));r=R(0,255);f.write("%03d"*3%(n,p,r))


Try it online! (Note that this won't modify the source. You have to run it locally for that to work)

To show that the transformations work, here is a test program (currently set up to always pick 100 for r, and it prints the result for every combination of n and p for the initial list.)



Explanation:

s='s=%r;print s%%s...';print s%s...

The first line is your classic quine, but a lot longer to account for what comes after.

from random import*;L=4;f=open(__file__,"wa"[L>5]);R=randint

Import for random integers. L will become of list of ordinals of the source code, but initially it is an integer not used anywhere else in the source to allow for a string replacement. Open the file to write the new source. On later runs, it will open to append instead.

f.write("\n".join((s%s).split("\n")[1:5:2]).replace("4",`map(ord,s%s)`))

Remove the first and third lines of code. Replace the 4 above with the list of ordinals.

if L>5:exec'b=[];h=%d\nwhile~-h:b+=[h%%1000];h/=1000\nwhile b:r,p,n=b[-3:];b=b[:-3];L=[L[:p]+L[p+1:],L[:p]+[r]+L[p+n:]][n<2if L else 1]\nprint"".join(map(chr,L))'%1

n=R(0,2);p=R(0,len(L if L>5else s%s));r=R(0,255);f.write("%03d"*3%(n,p,r))

In pieces:

  • if L>5: - Skips this line on first execution. Later, L will be a list, and this will run. I'll explain the exec last, because it's not run the first time.

  • n - A random number 0-2. This determines which modification occurs (0 = insert, 1 = replace, 2 = delete).

  • p - A random position in the list that the modification will occur at.

  • r - A random number to insert or replace in the list

  • f.write("%03d"*3%(n,p,r)) - Append the 3 randoms to the end of the source file. Every run, this will be adding on to an integer that encodes all the changes to the initial source that have occurred.

  • exec'b=[];h=%d...'%1... - Get the random numbers (found after %1 on later runs), apply the changes to the list, and print.

  • while~-h:b+=[h%%1000];h/=1000 - Build a list of the randoms generated so far, accounting for the leading 1, which prevents problems with leading zeros.

  • while b:r,p,n=b[-3:];b=b[:-3] - Assign the randoms for this iteration.

  • L=[L[:p]+L[p+1:],L[:p]+[r]+L[p+n:]][n<2if L else 1] - (0 = insert, 1 = replace, 2 = delete)

  • print"".join(map(chr,L)) - Print the modified source.

\$\endgroup\$
  • \$\begingroup\$ Does this sometimes delete a non-existent character from the end of the string? Since p can be the length of the string. Also, what's the behaviour with an empty string? \$\endgroup\$ – Jo King May 19 '18 at 23:07
  • \$\begingroup\$ @JoKing I added a test program. Every possible character change can happen. It just shows that every position may be selected for an insert, replace, or delete, and that it handles an empty list. tio.run/##LYoxDsMgDEVnOAUjCAZgRO0NuIHloUOaRIocy6JDT08dpdt/… \$\endgroup\$ – mbomb007 May 20 '18 at 18:07
  • \$\begingroup\$ I don't think that no change is valid, though I've asked the OP. The question does say Of course, deleting or replacing a character from an empty string is not a valid change \$\endgroup\$ – Jo King May 20 '18 at 21:37
  • \$\begingroup\$ I've asked Esolanging Fruit, and they say that no change is valid, but not for an empty string. \$\endgroup\$ – Jo King May 20 '18 at 23:44
  • 1
    \$\begingroup\$ @JoKing Should be fixed. \$\endgroup\$ – mbomb007 May 21 '18 at 0:29
1
\$\begingroup\$

Java 10, 370 bytes

String s;v->{if(s==null){s="String s;v->{if(s==null){s=%c%s%1$c;s=s.format(s,34,s);}else{int r=s.length();r*=Math.random();char c=127;c*=Math.random();s=s.substring(0,r)+(c%%3<2?c:%1$c%1$c)+s.substring(r+(c%%3>0?1:0));}}";s=s.format(s,34,s);}else{int r=s.length();r*=Math.random();char c=127;c*=Math.random();s=s.substring(0,r)+(c%3<2?c:"")+s.substring(r+(c%3>0?1:0));}}

Try it online.

Explanation:

String s;               // Class-level String variable to store the modifying source code
v->{                    // Method without parameter nor return-type
  if(s==null){          //  If this is the first execution of this function:
    s="String s;v->{if(s==null){s=%c%s%1$c;s=s.format(s,34,s);}else{int r=s.length();r*=Math.random();char c=127;c*=Math.random();s=s.substring(0,r)+(c%%3<2?c:%1$c%1$c)+s.substring(r+(c%%3>0?1:0));}}";
                        //   Set `s` to the unformatted source-code
    s=s.format(s,34,s);}//   And then to the formatted source-code
else{                   //  For any following executions of this function:
  int r=s.length();r*=Math.random();
                        //   Random index in range [0, length_of_modified_source_code)
  char c=127;c*=Math.random();
                        //   Random ASCII character in unicode range [0, 127)
  s=                    //   Replace the current String `s` with:
    s.substring(0,r)    //    The first [0, `r`) characters of the modified source code `s`
    +(c%3<2?            //    If the random option is 0 or 1:
           c:"")        //     Append the random character
        +s.substring(r  //    Append the rest of the modified source code `s`, but:
          +(c%3>0?      //     If the random option is 1 or 2:
             1:0));}}   //      Skip the first character of this second part

General explanation:

-part:

  • The String s contains the unformatted source code.
  • %s is used to input this String into itself with the s.format(...).
  • %c, %1$c and the 34 are used to format the double-quotes.
  • (%% is used to format the modulo-%).
  • s.format(s,34,s) puts it all together.

Here a basic Java quine program.

Challenge part:

  • String s; is the source code we'll modify on class-level.
  • int r=s.length();r*=Math.random(); is used to select a random index of the source code in the range [0, length_of_modified_source_code).
  • char c=127;c*=Math.random(); is used to select a random ASCII character (including unprintables) in the unicode range [0, 126].
  • c%3 is used to select a random option of either 0, 1 or 2. Option 0 will add the random character before index r; option 1 will replace the character at index r with the random character; and option 2 will remove the character at index r.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.