14
\$\begingroup\$

Now, we all know most languages have very simple ways to "self-modify" code. However, what if you were to actually modify the code and edit parts of it...on disk?

Your goal is to make code that prints a number, then edits its own file to replace the number with the next one in the Fibonacci sequence like so:

$ ./program
1
$ ./program
1
$ ./program
2
$ ./program
3
$ ./program
5
[etc...]

Rules

  1. You may not store the number(s) "outside" of the code. No comments, no telling the script to exit, no EOF, etc.
  2. If your code works with any filename, subtract 2 from your byte amount and write $BYTESNOW ($ORIGINALBYTES - 2) in your title. (Filenames are assumed to be within the range of any alphanumeric file path.)
  3. Your code must write the output to the file on it's own, without any external piping assistance.
  4. Your code can start from one or zero. It doesn't matter.
\$\endgroup\$
6
  • 8
    \$\begingroup\$ Next time, please post your idea in the Sandbox instead and leave the post there for a few days to receive feedback. \$\endgroup\$ Commented Jan 22, 2017 at 1:50
  • 2
    \$\begingroup\$ Is it allowed to call the program by invoking the interpreter of the programming language (e.g. perl6 program), or does it have to include the shebang line so that it can be called as ./program? \$\endgroup\$
    – smls
    Commented Jan 22, 2017 at 11:37
  • 1
    \$\begingroup\$ Also, if we don't want to go for the -2 bytes bonus, can we choose a single-byte filename or does it have to be program, and can we assume it's located in the current working directory? \$\endgroup\$
    – smls
    Commented Jan 22, 2017 at 11:44
  • \$\begingroup\$ Can it be allowed to fail when large numbers begin implicitly converting to exponential notation? \$\endgroup\$ Commented Jan 22, 2017 at 18:40
  • \$\begingroup\$ Why only 2 bytes bonus? Most languages, Eg. Lua, have it easier just to do "a" instead of arg[0]. It doesn't seem worth it. \$\endgroup\$
    – ATaco
    Commented Jan 22, 2017 at 22:20

15 Answers 15

7
\$\begingroup\$

Bash, 52 47 (49-2) bytes

EDITS:

  • Saved 5 bytes, by starting with 1 instead of 0. Thanks @Leo !

Golfed

A=$[1+0]
echo $A
sed -ri "s/\w+\+(\w+)/\1+$A/" $0

Test

>for i in `seq 10`
> do
> ./fibo
> done
1
1
2
3
5
8
13
21
34
55
\$\endgroup\$
3
  • 2
    \$\begingroup\$ I think you could save 1 byte by starting from [1+0] instead of [-1+1] (see 4th rule of the challenge) \$\endgroup\$
    – Leo
    Commented Jan 22, 2017 at 11:57
  • 2
    \$\begingroup\$ Actually, that would make you save even more bytes by removing the -? from the regex. And since you're there, you could also remove the first capturing group :) \$\endgroup\$
    – Leo
    Commented Jan 22, 2017 at 12:01
  • \$\begingroup\$ @Leo That's a nice advice, thank you ! \$\endgroup\$
    – zeppelin
    Commented Jan 22, 2017 at 12:25
3
\$\begingroup\$

Batch, 81 bytes

@call:c
@set/az=x+y
@echo %x%
@echo>>%0 @set/ax=%z%,y=%x%
:c
@set/ax=0,y=1

Note: the trailing newline is significant. Requires the script to be invoked using its full name including extension. Output starts at 0.

Since Batch can't realistically edit a file, I just add extra lines to the end of the file, so eventually it will know which the next number to print is. The >>%0 placement saves a byte because I can't precede it with a digit.

\$\endgroup\$
5
  • \$\begingroup\$ -14 bytes, without forcing filename \$\endgroup\$
    – ScriptKidd
    Commented Apr 18, 2020 at 10:20
  • \$\begingroup\$ @HackingAddict1337 Ah, I don't remember that bonus. \$\endgroup\$
    – Neil
    Commented Apr 18, 2020 at 10:34
  • 1
    \$\begingroup\$ Also, my batch file now measures 79 bytes for some reason? \$\endgroup\$
    – Neil
    Commented Apr 18, 2020 at 10:37
  • \$\begingroup\$ I don't want to put my name to your weird @goto& behaviour, or even your use of an uninitialised variable, so feel free to post your solution as an answer. \$\endgroup\$
    – Neil
    Commented Apr 18, 2020 at 10:43
  • \$\begingroup\$ all right then (goto& == cmd/c) \$\endgroup\$
    – ScriptKidd
    Commented Apr 18, 2020 at 10:45
2
\$\begingroup\$

Python 2, 118 111 bytes ( 113 - 2 )

a,b=0,1;print a
f=open(__file__,'r+')
s=f.read()
s=s.replace(s[4:s.find(';')],`b`+','+`a+b`)
f.seek(0)
f.write(s)

It works with any valid filename. There is not much to explain here, the code itself is very verbose.

Thanks to FlipTack for reminding me, close() is not mandatory.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Can't you just use f=open(...) instead of the with statement? \$\endgroup\$
    – FlipTack
    Commented Jan 22, 2017 at 9:52
2
\$\begingroup\$

Python 3.6, 96 91 (93-2) bytes

a,b=0,1
f=open(__file__,"r+");next(f);f.write(f"a,b={b,a+b}\n{next(f)}{f.seek(0)}");print(b)

hardcoding the filename would save 5 bytes (88 bytes):

a,b=0,1
f=open("f","r+");next(f);f.write(f"a,b={b,a+b}\n{next(f)}{f.seek(0)}");print(b)

Saved some bytes thanks to @Artyer

\$\endgroup\$
1
  • 1
    \$\begingroup\$ How about this (88 bytes) a,b=0,1 f=open('f','r+');next(f);f.write(f'a,b={b,a+b}\n{next(f)}{f.seek(0)}');print(b)# \$\endgroup\$
    – Artyer
    Commented Jan 23, 2017 at 15:42
1
\$\begingroup\$

C, 142 bytes (144 - 2)

void main(int x,char**a){FILE*f=fopen(*a,"r+");fseek(f,27,0);char n=fgetc(f),m=fgetc(f);fseek(f,27,0);printf("%d\n",fputc(fputc(m,f)?n+m:1,f));}

It's pretty straight forward. First it reads then saves the two chars at position 0x1A in the header. I probably could've looked deeper to find a safer spot to save the data but it works for me on my machine running OSX, compiled with GCC 4.2ish and I doubt it's very portable. Also, since it's based off chars it overflows after the 13th iteration.

It gives the output:

1
1
2
3
5
8
13
21
34
55
\$\endgroup\$
0
1
\$\begingroup\$

Node.js, 152 137 bytes (139 - 2)

Separated with newlines for clarity, not part of byte count.

f=_=>require('fs').writeFileSync(__filename,
`f=${f};f()`.replace(/(\d[^,]*),(\d[^\)]*)/,
(m,a,b)=>`${b=+b},${+a+b}`),console.log((0,1)));
f()

Explanation:

f=_=>                          // define `f` as function with a single unused argument `_`
  require('fs').writeFileSync( // import the standard filesystem module and overwrite file
    __filename,                // string var containing path of file for current module
    `f=${f};f()`.replace(      // template string containing source of entire script
      /(\d[^,]*),(\d[^\)]*)/,  // regexp to match and group the numbers in this script
      (m,a,b)=>                // replace function with arguments match, group a, group b
        `${b=+b},${+a+b}`      // template string incrementing fibonacci numbers in place
    ),                         // end replace()
    console.log(               // prints to stdout, `undefined` passed to argument
      (0,1)                    // comma separated group returns value of last expression
    )                          // end console.log()
  )                            // end fs.writeFileSync()
;                              // end statement defining `f` as arrow function
f()                            // run function to modify script and print fibonacci number

Usage:

// assuming above script is stored in program.js
$ node program
1
$ node program
1
$ node program
2
$ node program
3
$ node program
5
...
\$\endgroup\$
0
1
\$\begingroup\$

bash + Unix utilities, 43 bytes (45-2)

dc -e9k5v1+2/z^5v/.5+0k1/p;sed -i s/z/z1+/ $0

The first time this is run, it uses dc to compute the 1st Fibonacci number via the Binet formula. Each call to sed modifies the program by changing the string passed to dc; this change tells dc to add an additional 1 to the exponent in the formula, which causes it to compute the next number in the Fibonacci sequence each time.

Test

> for k in {1..10}
> do
> ./fib
> done
1
1
2
3
5
8
13
21
34
55

To illustrate how it works, at this point, after the 55 is printed, the program has been modified to read:

dc -e9k5v1+2/z1+1+1+1+1+1+1+1+1+1+^5v/.5+0k1/p;sed -i s/z/z1+/ $0

so running it again yields

> ./fib
89

and the program now reads:

dc -e9k5v1+2/z1+1+1+1+1+1+1+1+1+1+1+^5v/.5+0k1/p;sed -i s/z/z1+/ $0
\$\endgroup\$
2
  • \$\begingroup\$ I like this ! Well done ! \$\endgroup\$
    – zeppelin
    Commented Jan 24, 2017 at 19:49
  • \$\begingroup\$ @zeppelin Thank you -- this avoids the issues with the previous version we had. \$\endgroup\$ Commented Jan 24, 2017 at 19:50
1
\$\begingroup\$

SmileBASIC 3, 99 bytes (101 -2)

-2 byte bonus because it works with any filename.

A=0B=1F$="TXT:"+PRGNAME$()S$=LOAD(F$)SAVE F$,SUBST$(S$,0,INSTR(S$,"F"),FORMAT$("A=%DB=%D",B,A+B))?A+B

This one does work, and it somehow ended up being the same size as my broken one!

\$\endgroup\$
5
  • \$\begingroup\$ It's much shorter if you don't do the bonus \$\endgroup\$
    – 12Me21
    Commented Feb 11, 2017 at 22:26
  • \$\begingroup\$ forcing a specific filename makes me feel like a freak. I'm beating half these answers anyway \$\endgroup\$
    – snail_
    Commented Feb 12, 2017 at 3:06
  • \$\begingroup\$ I think not turning off the LOAD dialog is much worse. \$\endgroup\$
    – 12Me21
    Commented Feb 12, 2017 at 3:15
  • \$\begingroup\$ It's actually shorter if you load it into slot 1 and use PRGEDIT commands to replace the first line (and add a linebreak after A=0B=1) And you also don't need A=0 the first time. \$\endgroup\$
    – 12Me21
    Commented Feb 12, 2017 at 3:33
  • \$\begingroup\$ Dang, I saw this one and immediately thought "SmileBASIC". I probably couldn't come close anyways. Nicely done! Btw I'm known as Mr_Mo on sbs \$\endgroup\$
    – raddish0
    Commented Apr 18, 2020 at 19:50
1
\$\begingroup\$

Batch, 66 - 2 = 64 bytes

Uses \r line terminators (not traditional \r\n)

@call:a
@set/az=x+y
@(set/p=,x=%z%,y=%x%)<%0>>%0
:a
@goto&set/ax=1

Any filenames are allowed, except for 0-9 (which isn't a .bat anyways)


Credits to:
Original answer by @Neil
@dbenham mentioning the weird behavior of GOTO :invalid_or_empty label (originally discovered by some Russians):

In summary, (GOTO) 2>NUL behaves like EXIT /B, except it allows execution of concatenated commands in the context of the caller!

Warning: will overflow if the number exceeds 2^31-1 (47), because batch integers are 32-bits.

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

R, 145 bytes (147 - 2)

a=c(1,1)
cat(a[1])
R=readLines(f<-sub("^.+=","",grep("^--f",commandArgs(F),v=T)))
cat(c(sprintf("a=c(%i,%i)",a[2],sum(a)),R[-1]),file=f,sep="\n")

(Has a trailing newline). It works with any valid filename.

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

Perl 6, 67 62 bytes (64 - 2)

say (1,1,*+*...*)[1];$*PROGRAM.&{.spurt: .slurp.&{S/\[<(\d+/{$/+1}/}}

say 0+1;$*PROGRAM.&{.spurt: .slurp.&{S/(\d+).(\d+)/$1+{$0+$1}/}}
\$\endgroup\$
0
\$\begingroup\$

Stacked, noncompeting, 65 (67 - 2) bytes

Some issues regarding file IO were fixed in the most recent series of commits. Thus, noncompeting.

2:>
:sum\tail...\stack:0#out repr LF+program LF split last+d0\write

Here's a link to the github.

Example execution

(I omitted the actual path for clarity.)

C:\
λ type permanently-self-modifying-code.stk
2:>
:sum\last\stack:0#out repr LF+program LF split last+d0\write
C:\
λ stacked permanently-self-modifying-code.stk
1

C:\
λ stacked permanently-self-modifying-code.stk
1

C:\
λ stacked permanently-self-modifying-code.stk
2

C:\
λ stacked permanently-self-modifying-code.stk
3

C:\
λ stacked permanently-self-modifying-code.stk
5

C:\
λ stacked permanently-self-modifying-code.stk
8

Explanation

How this works is by taking a pair of numbers to begin the sequence (2:> in this case is the integer range [0, 2), which is (0 1)), then performing the Fibonacci transformation on them, like so:

:sum\last\                     top of stack: (x y)
:              duplicate.             stack: ((x y) (x y))
 sum           sum of TOs.            stack: ((x y) x+y)
    \          swap order.            stack: (x+y (x y))
     last      obtain last element.   stack: (x+y y)
         \     swap order.            stack: (y x+y)

On each run, this transformation is executed on the top of stack. Then, the stack is pushed to the stack, duplicated, and its first member obtained (stack:0#). This item is then outputted, and is the desired Fibonacci number. repr then takes the representation of the stack and appends a newline. Then, the program is pushed to the stack, and split on newlines. Then, we take the last member (the last line), and append this to the aforementioned string. Finally, we push d0 (the file itself; think dollar sign 0 == $0.) and write to it.

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

Ruby, 68 bytes (70-2)

p$a=1+0
f=open$0,'r+'
s=f.read.sub /\d+.(\d+)/,"\\1+#$a"
f.seek 0
f<<s
\$\endgroup\$
0
\$\begingroup\$

Clojure, 209 204 195 bytes

0 1(let[u #(apply str %)a"./src/s.clj"p #(Long/parseLong(u %))l(fn[v](split-with #(Character/isDigit %)v))c(slurp a)[n[_ & r]](l c)[m r](l r)b(+(p n)(p m))](println b)(spit a(str(p m)" "b(u r))))

-5 bytes by switching to parse the numbers as a long instead of an integer, and removing a couple missed spaces.

-9 bytes by removing the space between the second number and (let...) (most expensive space ever!).

See the pregolfed code comments for a description.

Tested again, and it no longer throws unmatched bracket errors. It works up to 7540113804746346429, at which point it throws an integer overflow exception.

Also note, this assumes the source code is located at "./src/s.clj".

0 1 ; Starting numbers
(let [; The first 4 entires are shortcuts to functions and data that are used more than once
      u #(apply str %) ; Turns a list into a string
      a "./src/s.clj" ; Current location
      p #(Integer/parseInt (u %)) ; Integer parsing shortcut
      ; Used to split a string on digits to parse them out
      l (fn [v] (split-with #(Character/isDigit %) v))
      src (slurp a) ; Get the source
      [n [_ & r]] (l src) ; Use deconstructuring to grab the first number
      [m r] (l r) ; Same as above, grabbing the second number
      n' (+ (p n) (p m)) ; Parse the 2 numbers, and add them
      ; Put everything back together, only this time with the new numbers
      k (str (p m) " " n' (u r))]
  (println n') ; Print the new number
  (spit a k)) ; Overwrite the old source
\$\endgroup\$
0
\$\begingroup\$

Python 3.8 (pre-release), 73 bytes

b=c=1;exec(a:="print(b);open(*'aw').write(f'b,c={c,b+c};exec(a:=%r)'%a)")

filename should be a

Explanation

b=c=1;exec(a:="print(b);open(*'aw').write(f'b,c={c,b+c};exec(a:=%r)'%a)")
b=c=1                                                                      # Set b and c to 1
           a:="print(b);open(*'aw').write(f'b,c={c,b+c};exec(a:=%r)'%a)"   # Set a to the string and return it
      exec(                                                             )  # Execute `a` as python code
               print(b)                                                    # Prints b
                        open(*'aw')                                        # Opens a files named `a` in write mode
                                          f'b,c={c,b+c};exec(a:=%r)'%a     # String Formatting: 'b,c=(<c>,<b+c>);exec(a:="<a>")'
                                   .write(                            )    # Write the string to the file

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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