Kod na stałe modyfikujący się


14

Teraz wszyscy wiemy, że większość języków ma bardzo proste sposoby na „samodzielną modyfikację” kodu. Co jednak, jeśli faktycznie zmodyfikujesz kod i edytujesz jego części ... na dysku?

Twoim celem jest utworzenie kodu, który wypisze liczbę, a następnie edytuje własny plik, zastępując go numerem kolejnym w sekwencji Fibonacciego:

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

Zasady

  1. Nie możesz przechowywać numerów poza kodem. Bez komentarzy, bez informowania skryptu o wyjściu, bez EOF itp.
  2. Jeśli kod działa z dowolną nazwą pliku, odejmij 2 od ilości bajtów i wpisz $BYTESNOW ($ORIGINALBYTES - 2)tytuł. (Zakłada się, że nazwy plików mieszczą się w zakresie dowolnej ścieżki alfanumerycznej).
  3. Twój kod musi zapisywać dane wyjściowe do pliku we własnym zakresie, bez żadnej pomocy zewnętrznego potoku.
  4. Twój kod może zaczynać się od zera lub zera. To nie ma znaczenia

8
Następnym razem opublikuj swój pomysł w piaskownicy i pozostaw post na kilka dni, aby otrzymać opinię.
JungHwan Min

2
Czy można wywoływać program, wywołując interpreter języka programowania (np. perl6 program), Czy też musi on zawierać linię shebang, aby można ją było wywołać jako ./program?
smls

1
Ponadto, jeśli nie chcemy skorzystać z premii -2 bajtów, czy możemy wybrać jednobajtową nazwę pliku, czy też musi to być programi czy możemy założyć, że znajduje się ona w bieżącym katalogu roboczym?
smls

Czy można dopuścić do niepowodzenia, gdy duże liczby zaczną domyślnie przekształcać się w notację wykładniczą?
Patrick Roberts,

Dlaczego tylko 2 bajty premii? Większość języków, np. Lua, miej łatwiej po prostu zrobić "a"zamiast arg[0]. To nie wydaje się tego warte.
ATaco

Odpowiedzi:


7

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

2
I think you could save 1 byte by starting from [1+0] instead of [-1+1] (see 4th rule of the challenge)
Leo

2
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 :)
Leo

@Leo That's a nice advice, thank you !
zeppelin

2

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.


1
Can't you just use f=open(...) instead of the with statement?
FlipTack

2

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.


1

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

1

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
...

1

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


1
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)#
Artyer

1

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

I like this ! Well done !
zeppelin

@zeppelin Thank you -- this avoids the issues with the previous version we had.
Mitchell Spector

1

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!


It's much shorter if you don't do the bonus
12Me21

forcing a specific filename makes me feel like a freak. I'm beating half these answers anyway
snail_

I think not turning off the LOAD dialog is much worse.
12Me21

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.
12Me21

0

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.


0

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}/}}

0

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.


0

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

0

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.