diff --git a/DIRECTORY.md b/DIRECTORY.md
index 34967082b359..b1adc23f6e61 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -717,6 +717,7 @@
   * [Archimedes Principle](physics/archimedes_principle.py)
   * [Casimir Effect](physics/casimir_effect.py)
   * [Centripetal Force](physics/centripetal_force.py)
+  * [Grahams Law](physics/grahams_law.py)
   * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py)
   * [Hubble Parameter](physics/hubble_parameter.py)
   * [Ideal Gas Law](physics/ideal_gas_law.py)
diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py
index 45b8be651f6e..388e7219f54b 100644
--- a/genetic_algorithm/basic_string.py
+++ b/genetic_algorithm/basic_string.py
@@ -21,6 +21,54 @@
 random.seed(random.randint(0, 1000))
 
 
+def evaluate(item: str, main_target: str) -> tuple[str, float]:
+    """
+    Evaluate how similar the item is with the target by just
+    counting each char in the right position
+    >>> evaluate("Helxo Worlx", "Hello World")
+    ('Helxo Worlx', 9.0)
+    """
+    score = len([g for position, g in enumerate(item) if g == main_target[position]])
+    return (item, float(score))
+
+
+def crossover(parent_1: str, parent_2: str) -> tuple[str, str]:
+    """Slice and combine two string at a random point."""
+    random_slice = random.randint(0, len(parent_1) - 1)
+    child_1 = parent_1[:random_slice] + parent_2[random_slice:]
+    child_2 = parent_2[:random_slice] + parent_1[random_slice:]
+    return (child_1, child_2)
+
+
+def mutate(child: str, genes: list[str]) -> str:
+    """Mutate a random gene of a child with another one from the list."""
+    child_list = list(child)
+    if random.uniform(0, 1) < MUTATION_PROBABILITY:
+        child_list[random.randint(0, len(child)) - 1] = random.choice(genes)
+    return "".join(child_list)
+
+
+# Select, crossover and mutate a new population.
+def select(
+    parent_1: tuple[str, float],
+    population_score: list[tuple[str, float]],
+    genes: list[str],
+) -> list[str]:
+    """Select the second parent and generate new population"""
+    pop = []
+    # Generate more children proportionally to the fitness score.
+    child_n = int(parent_1[1] * 100) + 1
+    child_n = 10 if child_n >= 10 else child_n
+    for _ in range(child_n):
+        parent_2 = population_score[random.randint(0, N_SELECTED)][0]
+
+        child_1, child_2 = crossover(parent_1[0], parent_2)
+        # Append new string to the population list.
+        pop.append(mutate(child_1, genes))
+        pop.append(mutate(child_2, genes))
+    return pop
+
+
 def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int, str]:
     """
     Verify that the target contains no genes besides the ones inside genes variable.
@@ -70,17 +118,6 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int,
         total_population += len(population)
 
         # Random population created. Now it's time to evaluate.
-        def evaluate(item: str, main_target: str = target) -> tuple[str, float]:
-            """
-            Evaluate how similar the item is with the target by just
-            counting each char in the right position
-            >>> evaluate("Helxo Worlx", Hello World)
-            ["Helxo Worlx", 9]
-            """
-            score = len(
-                [g for position, g in enumerate(item) if g == main_target[position]]
-            )
-            return (item, float(score))
 
         # Adding a bit of concurrency can make everything faster,
         #
@@ -94,7 +131,7 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]:
         #
         # but with a simple algorithm like this, it will probably be slower.
         # We just need to call evaluate for every item inside the population.
-        population_score = [evaluate(item) for item in population]
+        population_score = [evaluate(item, target) for item in population]
 
         # Check if there is a matching evolution.
         population_score = sorted(population_score, key=lambda x: x[1], reverse=True)
@@ -121,41 +158,9 @@ def evaluate(item: str, main_target: str = target) -> tuple[str, float]:
             (item, score / len(target)) for item, score in population_score
         ]
 
-        # Select, crossover and mutate a new population.
-        def select(parent_1: tuple[str, float]) -> list[str]:
-            """Select the second parent and generate new population"""
-            pop = []
-            # Generate more children proportionally to the fitness score.
-            child_n = int(parent_1[1] * 100) + 1
-            child_n = 10 if child_n >= 10 else child_n
-            for _ in range(child_n):
-                parent_2 = population_score[  # noqa: B023
-                    random.randint(0, N_SELECTED)
-                ][0]
-
-                child_1, child_2 = crossover(parent_1[0], parent_2)
-                # Append new string to the population list.
-                pop.append(mutate(child_1))
-                pop.append(mutate(child_2))
-            return pop
-
-        def crossover(parent_1: str, parent_2: str) -> tuple[str, str]:
-            """Slice and combine two string at a random point."""
-            random_slice = random.randint(0, len(parent_1) - 1)
-            child_1 = parent_1[:random_slice] + parent_2[random_slice:]
-            child_2 = parent_2[:random_slice] + parent_1[random_slice:]
-            return (child_1, child_2)
-
-        def mutate(child: str) -> str:
-            """Mutate a random gene of a child with another one from the list."""
-            child_list = list(child)
-            if random.uniform(0, 1) < MUTATION_PROBABILITY:
-                child_list[random.randint(0, len(child)) - 1] = random.choice(genes)
-            return "".join(child_list)
-
         # This is selection
         for i in range(N_SELECTED):
-            population.extend(select(population_score[int(i)]))
+            population.extend(select(population_score[int(i)], population_score, genes))
             # Check if the population has already reached the maximum value and if so,
             # break the cycle.  If this check is disabled, the algorithm will take
             # forever to compute large strings, but will also calculate small strings in