Browse Source

Merge branch '06-list_ops'

Frédéric G. MARAND 2 months ago
parent
commit
8d64dff1e6

+ 17 - 0
list-ops/.exercism/config.json

@@ -0,0 +1,17 @@
+{
+  "authors": [
+    "homersimpsons"
+  ],
+  "files": {
+    "solution": [
+      "ListOps.php"
+    ],
+    "test": [
+      "ListOpsTest.php"
+    ],
+    "example": [
+      ".meta/example.php"
+    ]
+  },
+  "blurb": "Implement basic list operations."
+}

+ 1 - 0
list-ops/.exercism/metadata.json

@@ -0,0 +1 @@
+{"track":"php","exercise":"list-ops","id":"b8af1c9c5e76467c9e627ed45df75100","url":"https://exercism.org/tracks/php/exercises/list-ops","handle":"Fairgame","is_requester":true,"auto_approve":false}

+ 52 - 0
list-ops/HELP.md

@@ -0,0 +1,52 @@
+# Help
+
+## Running the tests
+
+## Running the tests
+
+1. Go to the root of your PHP exercise directory, which is `<EXERCISM_WORKSPACE>/php`.
+   To find the Exercism workspace run
+
+       ➜ exercism debug | grep Workspace
+
+1. Get [PHPUnit] if you don't have it already.
+
+       ➜ wget -O phpunit https://phar.phpunit.de/phpunit-9.phar
+       ➜ chmod +x phpunit
+       ➜ ./phpunit --version
+
+2. Execute the tests:
+
+       ➜ ./phpunit file_to_test.php
+
+   For example, to run the tests for the Hello World exercise, you would run:
+
+       ➜ ./phpunit HelloWorldTest.php
+
+[PHPUnit]: https://phpunit.de
+
+## Submitting your solution
+
+You can submit your solution using the `exercism submit ListOps.php` command.
+This command will upload your solution to the Exercism website and print the solution page's URL.
+
+It's possible to submit an incomplete solution which allows you to:
+
+- See how others have completed the exercise
+- Request help from a mentor
+
+## Need to get help?
+
+If you'd like help solving the exercise, check the following pages:
+
+- The [PHP track's documentation](https://exercism.org/docs/tracks/php)
+- The [PHP track's programming category on the forum](https://forum.exercism.org/c/programming/php)
+- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
+- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
+
+Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
+
+To get help if you're having trouble, you can use one of the following resources:
+
+ - [/r/php](https://www.reddit.com/r/php) is the PHP subreddit.
+ - [StackOverflow](https://stackoverflow.com/questions/tagged/php) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

+ 83 - 0
list-ops/ListOps.php

@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * By adding type hints and enabling strict type checking, code can become
+ * easier to read, self-documenting and reduce the number of potential bugs.
+ * By default, type declarations are non-strict, which means they will attempt
+ * to change the original type to match the type specified by the
+ * type-declaration.
+ *
+ * In other words, if you pass a string to a function requiring a float,
+ * it will attempt to convert the string value to a float.
+ *
+ * To enable strict mode, a single declare directive must be placed at the top
+ * of the file.
+ * This means that the strictness of typing is configured on a per-file basis.
+ * This directive not only affects the type declarations of parameters, but also
+ * a function's return type.
+ *
+ * For more info review the Concept on strict type checking in the PHP track
+ * <link>.
+ *
+ * To disable strict typing, comment out the directive below.
+ */
+
+declare(strict_types=1);
+
+class ListOps {
+
+  public function append(array $list1, array $list2): array {
+    $list = $list1;
+    foreach ($list2 as $item) {
+      $list[] = $item;
+    }
+    return $list;
+  }
+
+  public function concat(array $list1, array ...$listn): array {
+    $res = $list1;
+    foreach ($listn as $list) {
+      foreach ($list as $item) {
+        $res[] = $item;
+      }
+    }
+    return $res;
+  }
+
+  /**
+   * @param callable(mixed $item): bool $predicate
+   */
+  public function filter(callable $predicate, array $list): array {
+    return array_values(array_filter($list, $predicate));
+  }
+
+  public function length(array $list): int {
+    return count($list);
+  }
+
+  /**
+   * @param callable(mixed $item): mixed $function
+   */
+  public function map(callable $function, array $list): array {
+    return array_map($function, $list);
+  }
+
+  /**
+   * @param callable(mixed $accumulator, mixed $item): mixed $function
+   */
+  public function foldl(callable $function, array $list, $accumulator) {
+    return array_reduce($list, $function, $accumulator);
+  }
+
+  /**
+   * @param callable(mixed $accumulator, mixed $item): mixed $function
+   */
+  public function foldr(callable $function, array $list, $accumulator) {
+    return array_reduce(array_reverse($list), $function, $accumulator);
+  }
+
+  public function reverse(array $list): array {
+    return array_reverse($list);
+  }
+
+}

+ 263 - 0
list-ops/ListOpsTest.php

@@ -0,0 +1,263 @@
+<?php
+
+/*
+ * By adding type hints and enabling strict type checking, code can become
+ * easier to read, self-documenting and reduce the number of potential bugs.
+ * By default, type declarations are non-strict, which means they will attempt
+ * to change the original type to match the type specified by the
+ * type-declaration.
+ *
+ * In other words, if you pass a string to a function requiring a float,
+ * it will attempt to convert the string value to a float.
+ *
+ * To enable strict mode, a single declare directive must be placed at the top
+ * of the file.
+ * This means that the strictness of typing is configured on a per-file basis.
+ * This directive not only affects the type declarations of parameters, but also
+ * a function's return type.
+ *
+ * For more info review the Concept on strict type checking in the PHP track
+ * <link>.
+ *
+ * To disable strict typing, comment out the directive below.
+ */
+
+declare(strict_types=1);
+
+use PHPUnit\Framework\ExpectationFailedException;
+
+class ListOpsTest extends PHPUnit\Framework\TestCase
+{
+    public static function setUpBeforeClass(): void
+    {
+        require_once 'ListOps.php';
+    }
+
+    /**
+     * @testdox append entries to a list and return the new list -> empty lists
+     */
+    public function testAppendEmptyLists()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([], $listOps->append([], []));
+    }
+
+    /**
+     * @testdox append entries to a list and return the new list -> list to empty list
+     */
+    public function testAppendNonEmptyListToEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([1, 2, 3, 4], $listOps->append([1, 2, 3, 4], []));
+    }
+
+    /**
+     * @testdox append entries to a list and return the new list -> empty list to list
+     */
+    public function testAppendEmptyListToNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([1, 2, 3, 4], $listOps->append([], [1, 2, 3, 4]));
+    }
+
+    /**
+     * @testdox append entries to a list and return the new list -> non-empty lists
+     */
+    public function testAppendNonEmptyLists()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([1, 2, 2, 3, 4, 5], $listOps->append([1, 2], [2, 3, 4, 5]));
+    }
+
+    /**
+     * @testdox concatenate a list of lists -> empty list
+     */
+    public function testConcatEmptyLists()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([], $listOps->concat([], []));
+    }
+
+    /**
+     * @testdox concatenate a list of lists -> list of lists
+     */
+    public function testConcatLists()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([1, 2, 3, 4, 5, 6], $listOps->concat([1, 2], [3], [], [4, 5, 6]));
+    }
+
+    /**
+     * @testdox concatenate a list of lists -> list of nested lists
+     */
+    public function testConcatNestedLists()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([[1], [2], [3], [], [4, 5, 6]], $listOps->concat([[1], [2]], [[3]], [[]], [[4, 5, 6]]));
+    }
+
+    /**
+     * @testdox filter list returning only values that satisfy the filter function -> empty list
+     */
+    public function testFilterEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            [],
+            $listOps->filter(static fn ($el) => $el % 2 === 1, [])
+        );
+    }
+
+    /**
+     * @testdox filter list returning only values that satisfy the filter function -> non empty list
+     */
+    public function testFilterNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            [1, 3, 5],
+            $listOps->filter(static fn ($el) => $el % 2 === 1, [1, 2, 3, 5])
+        );
+    }
+
+    /**
+     * @testdox returns the length of a list -> empty list
+     */
+    public function testLengthEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(0, $listOps->length([]));
+    }
+
+    /**
+     * @testdox returns the length of a list -> non-empty list
+     */
+    public function testLengthNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(4, $listOps->length([1, 2, 3, 4]));
+    }
+
+    /**
+     * @testdox returns a list of elements whose values equal the list value transformed by the mapping function -> empty list
+     */
+    public function testMapEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            [],
+            $listOps->map(static fn ($el) => $el + 1, [])
+        );
+    }
+
+    /**
+     * @testdox returns a list of elements whose values equal the list value transformed by the mapping function -> non-empty list
+     */
+    public function testMapNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            [2, 4, 6, 8],
+            $listOps->map(static fn ($el) => $el + 1, [1, 3, 5, 7])
+        );
+    }
+
+    /**
+     * @testdox folds (reduces) the given list from the left with a function -> empty list
+     */
+    public function testFoldlEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            2,
+            $listOps->foldl(static fn ($acc, $el) => $el * $acc, [], 2)
+        );
+    }
+
+    /**
+     * @testdox folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list
+     */
+    public function testFoldlDirectionIndependentNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            15,
+            $listOps->foldl(static fn ($acc, $el) => $acc + $el, [1, 2, 3, 4], 5)
+        );
+    }
+
+    /**
+     * @testdox folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list
+     */
+    public function testFoldlDirectionDependentNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            64,
+            $listOps->foldl(static fn ($acc, $el) => $el / $acc, [1, 2, 3, 4], 24)
+        );
+    }
+
+    /**
+     * @testdox folds (reduces) the given list from the right with a function -> empty list
+     */
+    public function testFoldrEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            2,
+            $listOps->foldr(static fn ($acc, $el) => $el * $acc, [], 2)
+        );
+    }
+
+    /**
+     * @testdox folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list
+     */
+    public function testFoldrDirectionIndependentNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            15,
+            $listOps->foldr(static fn ($acc, $el) => $acc + $el, [1, 2, 3, 4], 5)
+        );
+    }
+
+    /**
+     * @testdox folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list
+     */
+    public function testFoldrDirectionDependentNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals(
+            9,
+            $listOps->foldr(static fn ($acc, $el) => $el / $acc, [1, 2, 3, 4], 24)
+        );
+    }
+
+    /**
+     * @testdox reverse the elements of a list -> empty list
+     */
+    public function testReverseEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([], $listOps->reverse([]));
+    }
+
+    /**
+     * @testdox reverse the elements of a list -> non-empty list
+     */
+    public function testReverseNonEmptyList()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([7, 5, 3, 1], $listOps->reverse([1, 3, 5, 7]));
+    }
+
+    /**
+     * @testdox reverse the elements of a list -> list of lists is not flattened
+     */
+    public function testReverseNonEmptyListIsNotFlattened()
+    {
+        $listOps = new ListOps();
+        $this->assertEquals([[4, 5, 6], [], [3], [1, 2]], $listOps->reverse([[1, 2], [3], [], [4, 5, 6]]));
+    }
+}

+ 47 - 0
list-ops/README.md

@@ -0,0 +1,47 @@
+# List Ops
+
+Welcome to List Ops on Exercism's PHP Track.
+If you need help running the tests or submitting your code, check out `HELP.md`.
+
+## Instructions
+
+Implement basic list operations.
+
+In functional languages list operations like `length`, `map`, and `reduce` are very common.
+Implement a series of basic list operations, without using existing functions.
+
+The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include:
+
+- `append` (_given two lists, add all items in the second list to the end of the first list_);
+- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_);
+- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_);
+- `length` (_given a list, return the total number of items within it_);
+- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_);
+- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_);
+- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_);
+- `reverse` (_given a list, return a list with all the original items, but in reversed order_).
+
+Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant.
+
+## Callable
+
+In PHP there is a concept of [callable](https://www.php.net/manual/en/language.types.callable.php).
+
+Those can take multiple forms, but we will focus on [anonymous functions](https://www.php.net/manual/en/functions.anonymous.php).
+
+It is possible to create an anonymous function in a variable and call it with parameters:
+
+```php
+$double = function ($number) {
+    return $number * 2;
+}
+
+$double(2); // returns 4
+$double(4); // returns 8
+```
+
+## Source
+
+### Created by
+
+- @homersimpsons