Umwandeln von Verified Women in Sexy Girls

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
snafu
User
Beiträge: 6866
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wo ist eigentlich der Mod, wenn man ihn mal braucht...?
Benutzeravatar
snafu
User
Beiträge: 6866
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dieser Thread kann gerne auch gelöscht werden, versteht ja nun eh keiner mehr.
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Na dann machen wir was verständliches daraus. Richard Dawkin's Weasel program.

Code: Alles auswählen

#!/usr/bin/env python3
import random
from string import ascii_letters

ALPHABET = ascii_letters + " "
MUTATION_CHANCE = 0.05  # per character.
COPY_COUNT = 100  # per generation.


def copy(text):
    return "".join(
        (
            random.choice(ALPHABET)
            if random.random() <= MUTATION_CHANCE
            else character
        )
        for character in text
    )


def calculate_score(text_a, text_b):
    assert len(text_a) == len(text_b)
    return sum(
        character_a == character_b
        for character_a, character_b in zip(text_a, text_b)
    )


def main():
    text = "Verified Women"
    target = "Sexy Girls    "
    assert len(text) == len(target)
    assert set(target) <= set(ALPHABET)

    while text != target:
        text = max(
            (copy(text) for _ in range(COPY_COUNT)),
            key=lambda candidate: calculate_score(candidate, target),
        )
        print(text)


if __name__ == "__main__":
    main()

Code: Alles auswählen

Verified Wom n
Veryfied Wom n
VehyGted Wom n
VehyGted Wom n
VexyGted Wom n
VexyGted W m n
VexyGted W m n
VexyGted W m n
VexyGfed W m n
Sexylfed Q m n
Sexylfed Q m n
Sexylfed Q m n
Sexylfid Q m n
Sexylfid Q m n
SexylfidbQ m n
SexylfidbQ m n
Sexy fidbQ m n
Sexy fidbQ m n
Sexy fidbQ m  
Sexy fidbs m  
Sexy fidbs m  
Sexy fidbs m  
Sexy fidbs m  
Sexy fidbs m  
Sexy fidPs m  
Sexy fidls m  
Sexy fidls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy firls R  
Sexy Girls R  
Sexy Girls R  
Sexy Girls R  
Sexy Girls R  
Sexy Girls R  
Sexy Girls R  
Sexy Girls R  
Sexy Girls
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
nezzcarth
User
Beiträge: 1762
Registriert: Samstag 16. April 2011, 12:47

@__blackjack__: Interessante Idee! :) Mir erscheint es hier nur evtl. leicht verständlicher, den Hamming-Abstand zwischen den beiden Strings zu minimieren, statt einen davon abgeleiteten Fitness-Score zu maximieren (auch wenn das bei genetischen Algorithmen üblicher sein mag); man will ja einen String, der möglichst wenige Unterschiede aufweist.
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@nezzcarth: Ich habe mich halt an die Vorgaben von der Wikipediaseite gehalten. Der hat das ursprüngliche Programm 1986 in BASIC geschrieben. Und ich denke gerade die Einfachheit trägt zu seinem Argument bei, das weder die Mutation noch die Fitness-Funktion besonders schlau sind, und sich trotzdem relativ schnell das Ergebnis einstellt.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
snafu
User
Beiträge: 6866
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Phänomenal. :D
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Da Dawkins das zuerst in BASIC hatte, wollte ich das auch mal machen (CBM BASIC auf dem VIC-20):

Code: Alles auswählen

4 L=RND(-TI)
5 TI$="000000"
10 A$="VERIFIED WOMEN"
20 B$="SEXY GIRLS    "
30 L=LEN(A$)
40 DIM A(L),B(L),C(L),D(L)
50 FORI=1TOL
60 A(I)=ASC(MID$(A$,I,1))
70 B(I)=ASC(MID$(B$,I,1))
80 NEXT:GOSUB1000
90 BS=-1
100 FORI=1TO100
110 FORJ=1TOL
120 IFRND(1)>.05THENC=A(J):GOTO160
130 C=INT(RND(1)*27)
140 IFC=26THENC=32:GOTO160
150 C=C+65
160 C(J)=C:NEXT
170 S=0:FORJ=1TOL
180 S=S-(B(J)=C(J)):NEXT
190 IFS<BSTHEN210
200 BS=S:FORJ=1TOL:D(J)=C(J):NEXT
210 NEXT
220 FORJ=1TOL:A(J)=D(J):NEXT
230 GOSUB1000
240 IFBS<LTHEN100
250 PRINTTI$
999 END
1000 FORI=1TOL:PRINTCHR$(A(I));:NEXT:PRINT
1010 RETURN
Läuft so zwischen 20 und 30 Minuten bis die Umwandlung komplett ist. Das Beispiel ist deutlich kürzer als der Spruch mit dem Wiesel, aber er hatte 1986 wahrscheinlich schon deutlich leistungsfähigere Hardware dafür.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
nezzcarth
User
Beiträge: 1762
Registriert: Samstag 16. April 2011, 12:47

@__blackjack__: Ja, ich verstehe, dass es auf Wikipedia anders beschrieben wird. Allerdings ändern sich durch meinen Vorschlag nur zwei Zeilen in der Python-Version. Aber wie gesagt, fänd' ich nur logischer, das muss für einen Biologen ja nicht gelten. :) Zur Performance frage ich mich auch, auf was für einen Rechner Dawkins wohl 1986 als Biologe so Zugriff hatte; insb. falls sich die Angabe von 11 Sekunden für eine Pascal-Version auch auf dasselbe Jahr bezieht.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1239
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Hmmmmm Python ist sexy, wir brauchen keine ...
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Auf dem PC wäre das so die Zeit von TurboPascal 3. Ohne speziell auf Geschwindigkeit geachtet zu haben:

Code: Alles auswählen

program Weasel;
type
  TInstance = String[14];
const
  Source: TInstance = 'Verified Women';
  Target: TInstance = 'Sexy Girls    ';

function RandomCharacter: Char;
var
  n: Byte;
begin
  n := Random(53);
  if n < 26 then n := n + Ord('a')
            else if n < 52 then n := n - 26 + Ord('A')
                           else n := Ord(' ');
  RandomCharacter := Chr(n);
end;

procedure Copy(var source: TInstance; var target: TInstance);
var
  i: Byte;
begin
  for i := 1 to Length(source) do
    if Random <= 0.05 then target[i] := RandomCharacter
                      else target[i] := source[i];
end;

function CalculateScore(var instance: TInstance): Integer;
var
  i: Byte;
  score: Integer;
begin
  score := 0;
  for i := 1 to Length(instance) do
    if instance[i] = Target[i] then score := score + 1;
  CalculateScore := score;
end;

procedure Mutate(var instance: TInstance);
var
  i, j: Byte;
  bestScore, score: Integer;
  mutated, best: TInstance;
begin
  bestScore := -1;
  for i := 1 to 100 do
    begin
      Copy(instance, mutated);
      score := CalculateScore(mutated);
      if score > bestScore then
        begin
          bestScore := score;
          best := mutated;
        end;
    end;
  instance := best;
end;

begin
  Randomize;
  repeat
    Mutate(Source);
    WriteLn(Source);
  until Source = Target;
end.
Braucht so 30 bis 40 Sekunden auf einem XT-Klon mit etwas unter 10 Mhz. Und auch wenn es zu der Zeit schon AT-Rechner mit 286er gab, denke ich eher nicht, dass das bei ihm ein IBM-PC war. Wobei natürlich auch so Sachen wie die Mutationsrate Einfluss auf die Laufzeit haben können. Wir wissen ja nicht wie das Programm exakt ausgesehen hat.

Ich bin auch immer wieder fasziniert wie langsam GW-BASIC auf so einem Rechner ist. Verglichen mit der 8-Bit-Konkurrenz zwar schneller, aber wenn man sich den Prozessortakt und den Preisunterschied anschaut, sieht das schon irgendwie schlecht aus. Das hier braucht so zwischen 9 und 15 Minuten:

Code: Alles auswählen

10 DEFINT A-Z:RANDOMIZE(TIMER):T1!=TIMER
20 A$="Verified Women":B$="Sexy Girls    ":IF LEN(A$)<>LEN(B$) THEN STOP
25 C$=SPACE$(LEN(A$)):Z$=" ":FOR I=0 TO 25:Z$=Z$+CHR$(65+I)+CHR$(97+I):NEXT
30 PRINT A$:WHILE A$<>B$:BS=-1:FOR I=1 TO 100:FOR J=1 TO LEN(A$)
40 IF RND>.05 THEN X$=MID$(A$,J,1) ELSE X$=MID$(Z$,INT(RND*53)+1,1)
50 MID$(C$,J,1)=X$:NEXT:S=0:FOR J=1 TO LEN(A$):S=S-(MID$(C$,J,1)=MID$(B$,J,1))
60 NEXT:IF S>BS THEN BS=S:D$=C$
70 NEXT:A$=D$:PRINT A$:WEND:PRINT TIMER-T1!
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
nezzcarth
User
Beiträge: 1762
Registriert: Samstag 16. April 2011, 12:47

__blackjack__ hat geschrieben: Montag 17. Juni 2024, 20:00 Wobei natürlich auch so Sachen wie die Mutationsrate Einfluss auf die Laufzeit haben können. Wir wissen ja nicht wie das Programm exakt ausgesehen hat.
Die ideale Mutationsrate für den ursprünglichen String und die Populationsgröße von 100 liegt bei meinen Tests zwischen 4-5%. Insofern sind die 5% evtl. nicht das absolute Optimum, aber schon sehr gut gewählt und die Unterschiede in der Anzahl der nötigen Generationen im Mittel sehr gering, sodass es auf die exakte Zahl aus dem Bereich vielleicht nicht so ankommt. Eine deutlich größere Population (ruhig um die 1000 oder mehr) sorgt aber tatsächlich für eine sehr viel schnellere Konvergenz

Für das Beispiel aus dem Thread hier bekomme ich mit einer etwas höheren Mutationsrate von 0.095 merklich bessere Ergebnisse, auch schon bei einer Population von 100. Das liegt daran, dass die sinnvolle Mutationsrate von der Zeichenkettenlänge abhängt (die tatsächlich genau die Hälfte des "Wiesel-Strings" ist.)
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ein bisschen manuelles Code-Inlining, einfacherer Code zur Auswahl eines zufälligen Zeichens mit einer vorbereiteten Zeichenkette mit allen möglichen Zeichen, weniger Parameterübergaben, weniger Zeichenketten kopieren (dafür mehr auf einmal im Speicher), den Score merken und für die Endbedingung verwenden statt Zeichenkettenvergleich, und die Pascal-Version ist runter auf 14 bis 24 Sekunden:

Code: Alles auswählen

program Weasel;
type
  TInstance = String[14];
const
  Source: TInstance = 'Verified Women';
  Target: TInstance = 'Sexy Girls    ';
  InstancesPerGeneration = 100;
  LetterMutationChance = 0.05;
  AlphabetLength = 53;
var
  Alphabet: String[AlphabetLength];  { Lower- & uppercase letters & space. }
  instance: TInstance;
  score: Integer;

procedure InitAlphabet;
var
  i, j: Byte;
begin
  Alphabet[0] := Chr(AlphabetLength);
  for i := 0 to 25 do
    begin
      j := (i SHL 1) + 1;
      Alphabet[j] := Chr(Ord('a') + i);
      Alphabet[j + 1] := Chr(Ord('A') + i);
    end;
  Alphabet[AlphabetLength] := ' ';
end;

function MutateAndScore(var mutated: TInstance): Integer;
var
  i: Byte;
  score: Integer;
begin
  score := 0;
  mutated[0] := Chr(Length(instance));
  for i := 1 to Length(instance) do
    begin
      if Random <= LetterMutationChance
        then mutated[i] := Alphabet[Random(53) + 1]
        else mutated[i] := instance[i];

      if mutated[i] = Target[i] then score := score + 1;
    end;
  MutateAndScore := score;
end;

procedure MutateAndSelect;
var
  i, j: Byte;
  bestScore, mutatedScore: Integer;
  bestIndex: Byte;
  mutated: array[1..InstancesPerGeneration] of TInstance;
begin
  bestScore := score;
  for i := 1 to InstancesPerGeneration do
    begin
      mutatedScore := MutateAndScore(mutated[i]);
      if mutatedScore > bestScore then
        begin
          bestScore := mutatedScore;
          bestIndex := i;
        end;
    end;

  if bestScore > score then
    begin
      score    := bestScore;
      instance := mutated[bestIndex];
    end;
end;

begin
  Randomize; InitAlphabet;
  instance := Source; score := 0;
  repeat
    MutateAndSelect;
    WriteLn(instance);
  until score = Length(Target);
end.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten