17
\$\begingroup\$

This question already has an answer here:

In a programming language of your choice, write a full program that, when run, prints a positive integer N and then modifies its own source file such that the next time it is run it will print N+1.

For example, this (ungolfed) Python 3 program satisfies this behavior:

N = 1
print(N)
with open(__file__, 'r+') as f:
    N = int(f.readline()[4:]) + 1
    code = 'N = ' + str(N) + '\n' + f.read()
    f.seek(0)
    f.write(code)

Right away it declares and prints N, then it opens itself and rewrites its first line so the declared value of N is incremented. Then it copies the rest of its code verbatim and writes the new code to the same file, resulting in a program that will print N+1 when run again.

If the file were named incr.py, running it on the command line would look something like this:

C:\somepath> python incr.py
1
C:\somepath> python incr.py
2
C:\somepath> python incr.py
3
C:\somepath> python incr.py
4

At this point, incr.py would read:

N = 5
print(N)
with open(__file__, 'r+') as f:
    N = int(f.readline()[4:]) + 1
    code = 'N = ' + str(N) + '\n' + f.read()
    f.seek(0)
    f.write(code)

Your program should behave essentially the same as this Python example, though of course your program structure may be very different, even changing as N increases. (You don't need to define N as a plain integer on the first line, for example.)

Give the program that prints 1 in your answer. Programs for all higher N can then be inferred. The shortest 1-printing program in bytes wins.

Notes

  • The output should be the sole number N in decimal (or your language's most natural base), followed optionally by a trailing newline.

  • Your program must actually rewrite its own source code file, not simply output its modified source like a quine.

  • The file name and/or path of the source file may be hardcoded into your program (e.g. 'incr.py' could have replaced __file__ in the Python example) but the program's behavior should not depend on them. If the file is moved or renamed it should be easy enough to rewrite the program accordingly.

  • The source file's name and path should not change during execution.

  • Temporary files may be made (to copy code to or whatever) but they should be removed before the main program ends. Specifically, the only side effects of running your program should be:

    1. Printing N to stdout.

    2. Changing the source file so it will print N+1 when next run.

\$\endgroup\$

marked as duplicate by AdmBorkBork, Adnan code-golf Aug 30 '16 at 13:33

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • \$\begingroup\$ Related (but that one was a pop con so not a duplicate) \$\endgroup\$ – trichoplax Aug 30 '16 at 1:09
  • 1
    \$\begingroup\$ In Excel it's just 5 bytes by setting maximum iterations in Enable Iterative Calculation to 1 and then just enter = A1 + 1 in cell A1. \$\endgroup\$ – Anastasiya-Romanova 秀 Aug 30 '16 at 2:43
  • \$\begingroup\$ @matsjoyce No. Did you try the example? It does work beyond 9. \$\endgroup\$ – Calvin's Hobbies Aug 30 '16 at 7:13
  • 3
    \$\begingroup\$ Helka, the spec also says "modifies its own source file such that the next time it is run" (my emphasis). If your intention is that only interpreted languages may be used, you should state that explicitly. If you intend to allow compiled languages as well, the wording is a bit unclear. \$\endgroup\$ – Peter Taylor Aug 30 '16 at 7:38
  • 1
    \$\begingroup\$ @PeterTaylor That's why I didn't post my answer :D \$\endgroup\$ – Anastasiya-Romanova 秀 Aug 30 '16 at 7:54

15 Answers 15

22
\$\begingroup\$

bash (+ wc + id), 16 bytes

wc -l<$0;id>>$0
 

Counts its lines, then appends a line of garbage to itself.

Needs a trailing newline.

Crashes on line 2 with a syntax error, which thankfully prints to STDERR and exits.

After three invocations, the script looks like this for me:

wc -l<$0;id>>$0
uid=1000(lynn) gid=1000(lynn) groups=1000(lynn),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
uid=1000(lynn) gid=1000(lynn) groups=1000(lynn),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
uid=1000(lynn) gid=1000(lynn) groups=1000(lynn),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
 
\$\endgroup\$
  • 5
    \$\begingroup\$ Now that's style! \$\endgroup\$ – GreenAsJade Aug 30 '16 at 9:16
  • 2
    \$\begingroup\$ Gave +1. I also want to mention that since there are no quotes around $0, the source filename must not contain spaces and other specific characters. \$\endgroup\$ – seshoumara Aug 30 '16 at 11:30
13
\$\begingroup\$

Python 2, 31 bytes

open(*'aa').write('+1');print 1

Simply appends +1 to the source file upon execution. This program assumes that the source file is named as a. It can be easily modified to work with an arbitrary filename at 38 bytes as follows:

open(__file__,'a').write('+1');print 1

Additionally, a 37 bytes alternative exists though it isn't valid due to the 'no temporary files' rule:

f=open(*'aa')
print>>f
print f.tell()

This program appends a newline to the file a which is assumed not to exist before the first execution of the program. Then, the program prints the file size of a which is simply f.tell() since the file object is now pointing to the end of the file after appending.

\$\endgroup\$
  • \$\begingroup\$ if you know file is stored in 'a', why use __file__ at all? \$\endgroup\$ – Maltysen Aug 30 '16 at 6:18
  • \$\begingroup\$ @Maltysen I'm not assuming the filename of the source file. 'a' opens the file for appending. \$\endgroup\$ – xsot Aug 30 '16 at 6:22
  • \$\begingroup\$ Though you are allowed to name the file a.py or just a for a few byte savings. \$\endgroup\$ – Calvin's Hobbies Aug 30 '16 at 7:39
  • \$\begingroup\$ @HelkaHomba Ah, I see. I assumed it wasn't allowed. Unfortunately, this means my 37 bytes program is now completely outclassed. \$\endgroup\$ – xsot Aug 30 '16 at 8:00
6
\$\begingroup\$

Perl (+shell), 30 bytes

self.pl:

#!/usr/bin/perl
print 1;`$^X -pi -es/1/1+1/ $0`

Works as shown, but replace ^X by the literal control byte for the claimed score. This does lead to a warning on recent perl versions though.

Because the filename is passed through the shell it should not contain any special characters.

A pure perl version weights in at 32 bytes (or 33 if you add a -X option to suppress the -i warning)

#!/usr/bin/perl -pi
INIT{print 1;@ARGV=$0}s/1/1+1/

If you don't mind calling with perl -M5.010 in both version 2 more bytes can be removed by using say instead of print

\$\endgroup\$
5
\$\begingroup\$

Pyth - 7 bytes

Very simple, save as o.txt.

.wk)l"1

(btw to change filename, replace the paren for the .w with a string, like: .wk"file.pyth"l"1

\$\endgroup\$
  • 1
    \$\begingroup\$ ...what is it doing? \$\endgroup\$ – Francesco Dondi Aug 30 '16 at 9:33
  • 1
    \$\begingroup\$ @FrancescoDondi "1 is the string literal "1", and l gives length, so this implicitly prints the length of the string, giving 1 on the first execution. .w writes its argument to o.txt with a single trailing newline; since k is the empty string, this increases the length of the string by 1 each time, incrementing the output. \$\endgroup\$ – TheBikingViking Aug 30 '16 at 10:53
5
\$\begingroup\$

QBasic (QB64), 47 bytes

OPEN"C:\p.bas"FOR APPEND AS 1:?LOF(1)\2-22:?#1,

Adjust the path and filename as desired. Note: you must create the file C:\p.bas with an external text-editor (I used Notepad++) and make sure it does not contain a trailing newline. The QB64 editor adds one when you save the file, which throws off the math and the bytecount. (I assume actual QBasic would do the same, not to mention the auto-formatting.)

Here's an ungolfed version (which does expect a trailing newline):

OPEN "C:\p.bas" FOR APPEND AS 1
PRINT LOF(1) \ 2 - 32
PRINT #1,

How it works

The program opens its own code file in APPEND mode. It then uses LOF to get the length of the current version in bytes. To begin with, this is 47, so we calculate LOF(1)\2-22 (where \ is integer division) and print the resulting 1.

Finally, we want to write some no-op to the end of the file. The shortest way to do this turns out to be writing nothing, with a trailing newline automatically added. Since this is Windows (or DOS), the newline is \r\n, adding two bytes and necessitating the division by 2 earlier in the code.

(Always CLOSE your files when you're done with them, kids. Unless you're golfing.)

\$\endgroup\$
3
\$\begingroup\$

V, 15 bytes

:e z
jñ:wñck0

Or, this readable version:

:e <C-r>z
j<C-a>ñ:wñck0

This will not work on the online interpreter, since it disallows file access.

Here is a hexdump:

00000000: 3a65 2012 7a0a 6a01 f13a 77f1 636b 30    :e .z.j..:w.ck

Explanation:

:e                   "Edit the following file:
   <C-r>z            "Insert the full path to the source file
j                    "Move down a line
 <C-a>               "And increment the first number on this line
      ñ:wñ           "Save this file. Surrounding it with 'ñ' is a fun little hack that allows this to appear on the same line
          ck         "Change this line and the line above us
            0        "To '0' (or whatever number we're on)
\$\endgroup\$
  • \$\begingroup\$ Does :e % work in V? (Not that it would change the byte count; I'm just curious.) \$\endgroup\$ – Jordan Aug 30 '16 at 3:18
  • \$\begingroup\$ @Jordan Good question! That will not work because % is the current file being edited, which in V is the file used to give input. @z is the path to the source file. \$\endgroup\$ – DJMcMayhem Aug 30 '16 at 3:28
3
\$\begingroup\$

PowerShell, 69/60 57

Further golfed and regex corrected based on feedback from TimmyD and Matt

($a=1);$a++;(gc($b=$PSCommandPath))-replace"\d+",$a|sc $b

69 for completely path and filename agnostic

($a=1)|oh;$a++;$b=$PSCommandPath;((gc $b)-replace"\d",$a)|Out-File $b

60 bytes if I can specify a fixed filename

($a=1)|oh;$a++;(gc .\a.ps1)-replace"\d",$a)|Out-File .\a.ps1

Explanation:

Sets $a to 1, returns it. Then it increments the variable, finds its own invocation path, brings that into memory and uses regex to replace digits with the value in $a, then writes to disk.

\$\endgroup\$
  • 1
    \$\begingroup\$ Would this not fail is a became double digits? Also why not use sc instead of out-file \$\endgroup\$ – Matt Aug 30 '16 at 12:27
  • 1
    \$\begingroup\$ The second example would fail on the second pass since there is a digit in the file name. \$\endgroup\$ – Matt Aug 30 '16 at 12:46
  • 1
    \$\begingroup\$ Welcome to PPCG! Matt has it right -- the digit-replace needs to be thought out a little better. Also, a few additional golfs such as eliminating the |oh (things left on the pipeline are implicitly printed), moving the $b assignment into the gc, and using sc instead of out-file gets you down to 57 -- ($a=1);$a++;(gc($b=$PSCommandPath))-replace'\d+',$a|sc $b \$\endgroup\$ – AdmBorkBork Aug 30 '16 at 12:56
  • \$\begingroup\$ Ah, thanks. I definitely forgot the plus in the regex, that was silly. I left the define of $a in the pipeline and it didn't print when I was writing it, probably because I hadn't yet put the parens around it, same for why i did the define of $b that way, I knew I could get it to return the value as well as defining but I couldn't remember how. Completely goofed on forgetting SC \$\endgroup\$ – Chirishman Aug 30 '16 at 17:42
3
\$\begingroup\$

Javascript (Node.js), 92 bytes

s=require('fs')
f=s.readFileSync(l=__filename)
console.log(f.length-91)
s.writeFile(l,f+';')

Prints out the length of the file minus 91, and adds a semicolon (which is ignored by the interpreter) to the final line of the file each time it is run

\$\endgroup\$
  • 1
    \$\begingroup\$ You can save 2 bytes by moving the first line into the readFileSync call (and changing 93 to 91 of course). \$\endgroup\$ – Matthew Crumley Aug 30 '16 at 14:45
2
\$\begingroup\$

C#, 124 bytes

namespace System.IO{class C{static void Main(){Console.Write(File.ReadAllLines("p").Length);File.AppendAllText("p","\n");}}}

Usage: Source file needs to be called p. If using Visual Studio, make sure the Build Action is set to Compile (not default for non-cs files). Also, under project properties ensure the the Output Path is the current directory (ie- empty text box) (again not a default setting).

How it works: It opens the source file and outputs the line count. Then it appends an empty line to the source file.

Pretty print version:

namespace System.IO // Put in System.IO namespace so no usings needed.
{
    class C
    {
        static void Main()
        {
            Console.Write(File.ReadAllLines("p").Length); // Write line count/run count
            File.AppendAllText("p","\n"); // Increment line count/run count
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

PHP, 38 bytes

There are so many ways in PHP to do that ... this one´s the shortest I could find:

<?=stat(n)[7]-37;fputs(fopen(n,a),1);#

print file size (initially 38) -37, append a character (1 in this case) to the comment at the end.

  • save with file name n, call with php n
  • make sure that the file contains no trailing newline
  • fclose is called implicitly when the script finishes.
  • if you pick another filename, replace 37 with the new file size -1.
  • if filename gets longer than 5 characters (including eventual quotation marks), use $n=filename for the first occurence, $n for the second.
  • if filename gets longer than 7 characters, use __FILE__ instead of the literal file name.
\$\endgroup\$
2
\$\begingroup\$

Dyalog APL, 21 bytes

Define f as the following:

(⎕FX⊢2-⍨⍴)'⍝',⍨⎕NR'f'

⎕NR'f' the Nested Representation of f; on the first run, this is {f, (⎕FX⊢2-⍨⍴)'⍝',⍨⎕NR'f'}

'⍝',⍨ append a comment symbol, giving {f, (⎕FX⊢2-⍨⍴)'⍝',⍨⎕NR'f', }

(...) apply the following function:

⎕FX Fix definition of f

then

2-⍨ subtract two from

the length of the argument (3 on the first run)

Example run:

      f
1
      f
2
      f
3
\$\endgroup\$
2
\$\begingroup\$

Batch, 70 55 bytes

@cmd/cset/a-1>>%0
@call:l
@echo %n:~1%
:l
@set/an=0

Ensure that the file does not end in a newline. Use the full file name to invoke the file (e.g. incr.bat). Works by appending -1 (cmd/cset/a does not output a trailing newline) to the end of the file on each run (including the first!) so that n is the negation of the desired result, then just strips off the leading minus sign when printing n.

Edit: Saved 8 bytes by removing an exit/b which had no visible effect. Saved 7 bytes by switching from @echo off to @ prefixes on each line.

\$\endgroup\$
1
\$\begingroup\$

R, 45 bytes

f=function(){body(f)[[3]]<<-body(f)[[3]]+1;1}

Not sure if a function counts as a "full program" but calling f() will return 1,2,... within the same R session. It also satisfies the self-modifying criterion.

It is necessary to use the double-harpoon operator <<- or else f will just make a local copy of itself and modify that instead. <<- forces that to happen at the top level.

\$\endgroup\$
0
\$\begingroup\$

JavaScript (browser) (ES6), 84 bytes

s=document.querySelector('script');f=_=>eval(s.innerHTML);n=0;s.innerHTML+='n++;n;';

Run by calling f(). Note that this is reset on page load.

I assume that modifying the <script> tag counts as rewriting the source code.

\$\endgroup\$
0
\$\begingroup\$

PowerShell 55 53 31 Bytes

"$(gc($p='.\1.ps1'))+1"|sc $p;1

Read in the file then just add +1 to the end of the file. Powershell will evaluate that as addition so for the fifth pass the file would have ....;1+1+1+1+1 which evaluates to five!

Old version using saved integer in a variable.

($a=1);$a++;(gc .\1.ps1)-replace'=\d+',"=$a"|sc .\1.ps1

Since we can hardcode a name it saves spaces over the script determining its own name. Printer the current value of $a. Increase $a by one. Using regex, replace the digits that follow an equal sign with the now updated value. Then write back to the original file.

If I change the way I locate the first number I can get down to 53. Using a newline I change my regex to look for numbers at the end of a line. This is why I have to a semicolon to the last line to get that omitted.

$a=1
($a++);(gc .\1.ps1)-replace'\d+$',$a|sc .\1.ps1;
\$\endgroup\$

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