diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..173228f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 +max_line_length = 80 +indent_style = space +indent_size = 4 +insert_final_newline = true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..529c034 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @drupol diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d9159b2 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at am@localheinz.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..5ebc01b --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# CONTRIBUTING + +We're using [Travis CI](https://travis-ci.com) as a continuous integration system. + +For details, see [`.travis.yml`](../.travis.yml). + +## Tests + +We're using [`grumphp/grumphp`](https://github.com/phpro/grumphp) to drive the development. + +Run + +```bash +./vendor/bin/grumphp run +``` + +to run all the tests. + +## Coding Standards + +We are using [`drupol/php-conventions`](https://github.com/drupol/php-conventions) to enforce coding standards. + +Run + +```bash +./vendor/bin/grumphp run +``` + +to automatically detect/fix coding standard violations. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b4c89d9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: drupol diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..3421bc3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +## Steps required to reproduce the problem + +1. +2. +3. + +## Expected Result + +* + +## Actual Result + +* diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e448c72 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +This PR + +* [x] +* [ ] +* [ ] + +Follows #. +Related to #. +Fixes #. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..8eb151c --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,10 @@ +daysUntilStale: 60 + +daysUntilClose: 7 + +staleLabel: stale + +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. diff --git a/.gitignore b/.gitignore index 524e5d8..2dc8d6a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,13 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties -test.php -.gitignore \ No newline at end of file +.gitignore +/composer.lock +/.php_cs.cache +/test.php +/test2.php +/tests/_output/ +/CHANGELOG.md +/taskman.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index b39596c..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,61 +0,0 @@ -build: - environment: - php: '5.6' - -filter: - paths: - - 'src/*' - -tools: - external_code_coverage: - timeout: 600 - php_mess_detector: - config: - code_size_rules: { cyclomatic_complexity: true, npath_complexity: true, excessive_method_length: true, excessive_class_length: true, excessive_parameter_list: true, excessive_public_count: true, too_many_fields: true, too_many_methods: true, excessive_class_complexity: true } - design_rules: { number_of_class_children: true, depth_of_inheritance: true, coupling_between_objects: true } - unused_code_rules: { unused_local_variable: true, unused_private_method: true, unused_formal_parameter: true } - naming_rules: { short_variable: true, long_variable: true, short_method: true, boolean_method_name: true } - controversial_rules: { camel_case_class_name: true, camel_case_property_name: true, camel_case_method_name: true, camel_case_parameter_name: true, camel_case_variable_name: true } - php_cs_fixer: - config: - level: all - fixers: { unused_use: true, phpdoc_params: true, braces: true, php_closing_tag: true } - php_analyzer: - config: - suspicious_code: { enabled: true, overriding_parameter: true, overriding_closure_use: true, parameter_closure_use_conflict: true, parameter_multiple_times: true, non_existent_class_in_instanceof_check: true, non_existent_class_in_catch_clause: true, assignment_of_null_return: true, non_commented_switch_fallthrough: true, non_commented_empty_catch_block: true, overriding_private_members: true, use_statement_alias_conflict: true, precedence_in_condition_assignment: true } - verify_php_doc_comments: { enabled: true, parameters: true, return: true, suggest_more_specific_types: true, ask_for_return_if_not_inferrable: true, ask_for_param_type_annotation: true } - loops_must_use_braces: { enabled: true } - simplify_boolean_return: { enabled: true } - phpunit_checks: { enabled: true } - reflection_fixes: { enabled: true } - use_statement_fixes: { enabled: true, order_alphabetically: true, remove_unused: true, preserve_multiple: false, preserve_blanklines: false } - parameter_reference_check: { enabled: false } - checkstyle: { enabled: false, no_trailing_whitespace: true, naming: { enabled: true, local_variable: '^[a-z][a-zA-Z0-9]*$', abstract_class_name: ^Abstract|Factory$, utility_class_name: 'Utils?$', constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$', property_name: '^[a-z][a-zA-Z0-9]*$', method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$', parameter_name: '^[a-z][a-zA-Z0-9]*$', interface_name: '^[A-Z][a-zA-Z0-9]*Interface$', type_name: '^[A-Z][a-zA-Z0-9]*$', exception_name: '^[A-Z][a-zA-Z0-9]*Exception$', isser_method_name: '^(?:is|has|should|may|supports)' } } - unreachable_code: { enabled: false } - check_access_control: { enabled: false } - typo_checks: { enabled: false } - check_variables: { enabled: false } - check_calls: { enabled: true, too_many_arguments: true, missing_argument: true, argument_type_checks: lenient } - dead_assignments: { enabled: false } - check_usage_context: { enabled: true, foreach: { value_as_reference: true, traversable: true } } - reflection_checks: { enabled: false } - precedence_checks: { enabled: true, assignment_in_condition: true, comparison_of_bit_result: true } - basic_semantic_checks: { enabled: false } - unused_code: { enabled: false } - deprecation_checks: { enabled: false } - useless_function_calls: { enabled: false } - metrics_lack_of_cohesion_methods: { enabled: false } - metrics_coupling: { enabled: true, stable_code: { namespace_prefixes: { }, classes: { } } } - doctrine_parameter_binding: { enabled: false } - doctrine_entity_manager_injection: { enabled: false } - symfony_request_injection: { enabled: false } - doc_comment_fixes: { enabled: false } - php_code_sniffer: - config: - standard: PSR2 - sniffs: { psr2: { classes: { property_declaration_sniff: true }, methods: { method_declaration_sniff: true } } } - sensiolabs_security_checker: true - php_loc: true - php_pdepend: true - php_sim: true - php_changetracking: true diff --git a/.travis.yml b/.travis.yml index 75ca950..bdee833 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,15 +9,17 @@ cache: - $HOME/.composer/cache php: - - 5.6 - 7.1 + - 7.2 + - 7.3 install: - composer install script: - composer grumphp - - composer phpunit + - composer codecept-coverage after_success: - - composer scrutinizer \ No newline at end of file + - phpenv config-rm xdebug.ini + - bash <(curl -s https://codecov.io/bash) -f tests/_output/coverage.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c946b00 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Pol Dellaiera + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index cd607c8..b8f015f 100644 --- a/README.md +++ b/README.md @@ -1,162 +1,68 @@ -## PHPartition -[![Build Status](https://travis-ci.org/drupol/phpartition.svg?branch=master)](https://travis-ci.org/drupol/phpartition) [![Code Coverage](https://scrutinizer-ci.com/g/drupol/phpartition/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/drupol/phpartition/?branch=master) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/drupol/phpartition/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/drupol/phpartition/?branch=master) [![Dependency Status](https://www.versioneye.com/user/projects/58551f4d4d6466004c28cc8f/badge.svg?style=flat-square)](https://www.versioneye.com/user/projects/58551f4d4d6466004c28cc8f) +[![Latest Stable Version](https://img.shields.io/packagist/v/drupol/phpartition.svg?style=flat-square)](https://packagist.org/packages/drupol/phpartition) + [![GitHub stars](https://img.shields.io/github/stars/drupol/phpartition.svg?style=flat-square)](https://packagist.org/packages/drupol/phpartition) + [![Total Downloads](https://img.shields.io/packagist/dt/drupol/phpartition.svg?style=flat-square)](https://packagist.org/packages/drupol/phpartition) + [![Build Status](https://img.shields.io/travis/drupol/phpartition/master.svg?style=flat-square)](https://travis-ci.org/drupol/phpartition) + [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/drupol/phpartition/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/drupol/phpartition/?branch=master) + [![License](https://img.shields.io/packagist/l/drupol/phpartition.svg?style=flat-square)](https://packagist.org/packages/drupol/phpartition) -In number theory and computer science, the partition problem is the task of deciding whether a given multiset of items can be partitioned into multiple balanced subsets. +# PHPartition + +## Description + +In number theory and computer science, the partition problem ([wikipedia](https://en.wikipedia.org/wiki/Partition_problem)) +is the task of splitting a set of items into multiple balanced subsets. + +This library not only handles dividing a partition in two, but it handles as many numbers of them if needed, where the +goal is to divide a set n objects into a given number subsets, minimizing the difference between the smallest and the +largest subset sums (multi-way). + +## Documentation + +Implemented algorithms: +* Greedy +* Linear +* Combinations ([custom anytime algorithm](https://en.wikipedia.org/wiki/Anytime_algorithm)) + +Blog post: [https://not-a-number.io/2019/TODO](https://not-a-number.io/2019/TODO) ## Requirements -* PHP >= 5.6, -* (optional) [PHPUnit](https://phpunit.de/) to run tests. +* PHP >= 7.1 + +## Installation + +```composer require drupol/phpartition``` + +## Optional packages + +* [drupol/phpermutations](https://github.com/drupol/phpermutations): To use the Anytime algorithm. ## Examples ```php setData($data); -$greedy->setSize(3); -$result = $greedy->getResult(); - -// $result is: -/* - * Array - ( - [0] => Array - ( - [0] => 9 - [1] => 5 - [2] => 1 - ) - - [1] => Array - ( - [0] => 7 - [1] => 6 - [2] => 3 - ) - - [2] => Array - ( - [0] => 11 - [1] => 5 - ) - ) - */ - -$simple = new \drupol\phpartition\Algorithm\Simple(); -$simple->setData($data); -$simple->setSize(3); -$result = $simple->getResult(); - -// $result is: -/* - * Array -( - [0] => Array - ( - [0] => 5 - [1] => 11 - ) - - [1] => Array - ( - [0] => 1 - [1] => 7 - [2] => 3 - ) - - [2] => Array - ( - [0] => 5 - [1] => 6 - [2] => 9 - ) -) - */ -``` -You may also pass objects or array but then, you'll have to define how to access their value. +include './vendor/autoload.php'; -```php - 'anything A', - 'weight' => 1, - ), - array( - 'item' => 'anything B', - 'weight' => 2, - ), - array( - 'item' => 'anything C', - 'weight' => 3, - ), - array( - 'item' => 'anything D', - 'weight' => 4, - ), -); - -$greedy = new \drupol\phpartition\Algorithm\Greedy(); -$greedy->setData($data); -$greedy->setSize(2); -$greedy->setItemAccessCallback(function($item) { - return $item['weight']; -}); -$result = $greedy->getResult(); - -// $result is -/* - * Array - ( - [0] => Array - ( - [0] => Array - ( - [item] => anything C - [weight] => 3 - ) - - [1] => Array - ( - [item] => anything B - [weight] => 2 - ) - - ) - - [1] => Array - ( - [0] => Array - ( - [item] => anything D - [weight] => 4 - ) - - [1] => Array - ( - [item] => anything A - [weight] => 1 - ) - - ) - - ) - */ ``` -It's also possible to mix the type of object to partition. +## Code quality, tests and benchmarks + +Every time changes are introduced into the library, [Travis CI](https://travis-ci.org/drupol/phpartition/builds) run the +tests and the benchmarks. + +The library has unit tests written using [PHPUnit](http://www.phpunit.de/) through [Codeception](https://codeception.com/). +Feel free to check them out in the `tests` directory. Run `composer codecept` to trigger the tests. + +Before each commit some inspections are executed with [GrumPHP](https://github.com/phpro/grumphp), +run `./vendor/bin/grumphp run` to check manually. + +## Contributing + +Feel free to contribute to this library by sending Github pull requests. I'm quite reactive :-) ## TODO - Implement Complete Karmarkar-Karp (CKK) algorithm, -- Documentation. - +- More documentation. diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100644 index 0000000..8f9db53 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,19 @@ +actor_suffix: Tester +paths: + tests: tests + data: tests/_data + log: tests/_output + support: tests/_support + envs: tests/_envs +settings: + bootstrap: _bootstrap.php + colors: true + memory_limit: 1024M + lint: true +extensions: + enabled: + - Codeception\Extension\RunFailed +coverage: + enabled: true + include: + - src/* diff --git a/composer.json b/composer.json index 19ce00e..72d444b 100644 --- a/composer.json +++ b/composer.json @@ -1,15 +1,18 @@ { "name": "drupol/phpartition", - "description": "Partition problem for balanced arrays splitting made easy.", "type": "library", + "description": "Partition problem for balanced arrays splitting made easy.", + "keywords": [ + "math", + "numbers", + "statistics", + "partition", + "greedy", + "karmarkar", + "karp" + ], "homepage": "https://github.com/drupol/phpartition", - "keywords": ["math", "numbers", "statistics", "partition", "greedy", "karmarkar", "karp"], - "license": "GPL-2.0+", - "support": { - "issues": "https://github.com/drupol/phpartition/issues", - "source": "https://github.com/drupol/phpartition" - }, - "minimum-stability": "stable", + "license": "MIT", "authors": [ { "name": "Pol Dellaiera", @@ -18,31 +21,30 @@ } ], "require": { - "oefenweb/statistics": "^1.1", - "drupol/phpermutations": "^1.2", - "phootwork/collection": "^1.4" + "php": ">= 7.1.3" }, "require-dev": { - "phpunit/phpunit": "^5.7", - "mockery/mockery": "^0.9", - "squizlabs/php_codesniffer": "^2.0", - "satooshi/php-coveralls": "^1.0", - "phpunit/php-code-coverage": "^4.0", - "scrutinizer/ocular": "^1.3", - "phpro/grumphp": "^0.11" - }, - "scripts": { - "phpcs": "./vendor/bin/phpcs --standard=PSR2 --ignore=vendor .", - "phpcbf": "./vendor/bin/phpcbf --standard=PSR2 --ignore=vendor .", - "phpunit": "./vendor/bin/phpunit --coverage-clover build/logs/clover.xml -c tests/phpunit.xml tests", - "grumphp": "./vendor/bin/grumphp run", - "coveralls": "./vendor/bin/coveralls", - "scrutinizer": "./vendor/bin/ocular code-coverage:upload --format=php-clover build/logs/clover.xml" + "codeception/codeception": "^3", + "drupol/php-conventions": "^1.5", + "drupol/phpermutations": "^1.3" }, "autoload": { "psr-4": { - "drupol\\phpartition\\": "src/", - "drupol\\phpartition\\Tests\\": "tests/src/" + "drupol\\phpartition\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "drupol\\phpartition\\tests\\": "tests/" + } + }, + "scripts": { + "codecept": "./vendor/bin/codecept run", + "codecept-coverage": "./vendor/bin/codecept run --coverage --coverage-xml --env travis", + "grumphp": "./vendor/bin/grumphp run" + }, + "support": { + "issues": "https://github.com/drupol/phpartition/issues", + "source": "https://github.com/drupol/phpartition" } } diff --git a/grumphp.yml.dist b/grumphp.yml.dist index 3d98c90..60e8038 100644 --- a/grumphp.yml.dist +++ b/grumphp.yml.dist @@ -1,4 +1,14 @@ +imports: + - { resource: vendor/drupol/php-conventions/config/php71/grumphp.yml } + parameters: - git_dir: . - bin_dir: vendor/bin - tasks: { } + tasks.phpstan.ignore_patterns: + - vendor/ + - spec/ + - tests/ + tasks.phpcs.ignore_patterns: + - vendor/ + - spec/ + - tests/ + extra_tasks: + codeception: ~ diff --git a/src/Algorithm/BruteForce.php b/src/Algorithm/BruteForce.php deleted file mode 100644 index 4d72b5c..0000000 --- a/src/Algorithm/BruteForce.php +++ /dev/null @@ -1,55 +0,0 @@ -getDataPartition()->sortByValue('ASC'); - $partitionSize = ($this->getSize() > $this->getDataPartition()->size()) ? - $this->getDataPartition()->size() : - $this->getSize(); - - for ($p = $partitionSize; $p > 1; $p--) { - $best = $this->getDataPartition()->getWeight(); - $target = ($best) / $p; - $goodSubset = array(); - $maxSize = floor($this->getDataPartition()->size() / $p); - - for ($i = 1; $i <= $maxSize; $i++) { - $permutations = new Permutations($this->getDataPartition()->toArray(), $i); - foreach ($permutations->generator() as $subset) { - $x = 0; - foreach ($subset as $item) { - $x += $item->getValue(); - if (abs($x - $target) - abs($best - $target) < 0) { - $best = $x; - $goodSubset = $subset; - } - } - } - } - - $this->getPartitionContainer()->insert($this->getPartition()->addItems($goodSubset)); - $this->getDataPartition()->deleteItems($goodSubset); - } - - $this->getPartitionContainer()->insert($this->getPartition()->addItems($this->getDataPartition()->toArray())); - - return $this->getPartitionContainer()->getPartitionsItemsArray(); - } -} diff --git a/src/Algorithm/BruteForceCustomA.php b/src/Algorithm/BruteForceCustomA.php deleted file mode 100644 index d3f8e03..0000000 --- a/src/Algorithm/BruteForceCustomA.php +++ /dev/null @@ -1,75 +0,0 @@ -getDataPartition()->sortByValue('ASC'); - // Compute the maximum value of the variance. - $variance = pow(2, $this->getDataPartition()->size()); - // Set a default value for the variable that will contain the solution. - $solution = null; - // Get the number of elements in the input dataset. - $count = $this->getDataPartition()->size(); - // Compute the size of a chunk. Ceiling the value because it cannot be zero. - $chunkSize = ceil($count / $this->getSize()); - // Get the rest of the division of the element count by the number of - // partitions. - $rest = $count % $this->getSize(); - - $permutations = new Permutations($this->getDataPartition()->toArray()); - - // Loop through each permutation and - // compute the variance of each subsetchunks. - $i = 0; - foreach ($permutations->generator() as $subset) { - $i++; - // Get the variance of the sums array. - $varianceCandidate = Statistics::variance( - array_map(function ($items) { - $partition = new Partition(); - $partition->addItems($items); - return $partition->getWeight(); - }, array_chunk($subset, $chunkSize)) - ); - - // If we've found a better variance with this subset, store it. - if ($varianceCandidate < $variance) { - $variance = $varianceCandidate; - $solution = $subset; - - // If the variance is equal to the size of the set module the number of - // partition that we want, that means that's the best candidate, - // store the value and exit the loop prematurely. - if ($rest == $varianceCandidate) { - break; - } - } - } - - // Store each chunks into a subset in the SubsetContainer. - foreach (array_chunk($solution, $chunkSize) as $subsetChunks) { - $this->getPartitionContainer()->insert($this->getPartition()->addItems($subsetChunks)); - } - - return $this->getPartitionContainer()->getPartitionsItemsArray(); - } -} diff --git a/src/Algorithm/Greedy.php b/src/Algorithm/Greedy.php deleted file mode 100644 index ef77c92..0000000 --- a/src/Algorithm/Greedy.php +++ /dev/null @@ -1,34 +0,0 @@ -getDataPartition()->sortByValue('DESC'); - return parent::getResult(); - } - - /** - * {@inheritdoc} - */ - public function getPartitionWeight(Partition $partition) - { - return $partition->getWeight(); - } -} diff --git a/src/Algorithm/Simple.php b/src/Algorithm/Simple.php deleted file mode 100644 index 39da01a..0000000 --- a/src/Algorithm/Simple.php +++ /dev/null @@ -1,16 +0,0 @@ -partitionContainer = new PartitionContainer(); - $this->partitionContainer->setAlgo($this); - } - - /** - * {@inheritdoc} - */ - public function setData(array $data = array()) - { - $this->data = $data; - $this->setDataPartition($this->generateDataPartition($this->getData())); - } - - /** - * {@inheritdoc} - */ - public function getData() - { - return $this->data; - } - - /** - * {@inheritdoc} - */ - public function setDataPartition(Partition $partition) - { - $this->dataPartition = $partition; - } - - /** - * {@inheritdoc} - */ - public function getDataPartition() - { - return $this->dataPartition; - } - - /** - * {@inheritdoc} - */ - public function generateDataPartition(array $items = array()) - { - return $this->getPartition()->addItems( - array_map(function ($item) { - if ($item instanceof PartitionItemInterface) { - return $item; - } - return new PartitionItem( - $item, - $this->getItemAccessCallback() - ); - }, $items) - ); - } - - /** - * {@inheritdoc} - */ - public function setSize($size) - { - $this->getPartitionContainer()->setSize($size); - } - - /** - * {@inheritdoc} - */ - public function getSize() - { - return $this->getPartitionContainer()->getSize(); - } - - /** - * {@inheritdoc} - */ - public function setItemAccessCallback(callable $callable = null) - { - $this->itemAccessCallback = $callable; - $this->setData($this->getData()); - } - - /** - * {@inheritdoc} - */ - public function getItemAccessCallback() - { - return $this->itemAccessCallback; - } - - /** - * {@inheritdoc} - */ - public function getPartitionContainer() - { - return $this->partitionContainer; - } - - /** - * {@inheritdoc} - */ - public function getResult() - { - $this->getPartitionContainer()->addItemsToPartition($this->getDataPartition()->toArray()); - - return $this->getPartitionContainer()->getPartitionsItemsArray(); - } - - /** - * {@inheritdoc} - */ - public function getPartitionWeight(Partition $partition) - { - return $partition->size(); - } - - /** - * {@inheritdoc} - */ - public function getPartition() - { - $partition = new Partition(); - $partition->setAlgo($this); - - return $partition; - } -} diff --git a/src/Combinations.php b/src/Combinations.php new file mode 100644 index 0000000..84c88d9 --- /dev/null +++ b/src/Combinations.php @@ -0,0 +1,140 @@ +timeout = $timeout; + } + + /** + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * @param int $timeout + * + * @return $this + */ + public function setTimeout(int $timeout) + { + $this->timeout = $timeout; + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function fillPartitions(Partitions $partitions, Partition $dataset, int $chunks): void + { + $partition = new Partition($dataset); + $partition->uasort(static function ($left, $right) { + return $left->getWeight() <=> $right->getWeight(); + }); + $dataset = $partition->getArrayCopy(); + + for ($p = $chunks; 1 < $p; --$p) { + $partition = new Partition($dataset); + + $best = $partition->getWeight(); + $target = $best / $p; + $maxSize = \floor($partition->count() / $p); + + $goodSubset = $this->getSubsets($maxSize, $dataset, $target, $best); + + // We cannot use array_udiff() to compare objects because we only need + // to remove one object at a time. + foreach ($goodSubset as $item) { + if (false !== $key = \array_search($item, $dataset, true)) { + unset($dataset[$key]); + } + } + + if (!empty($goodSubset)) { + $partitionsArray[] = $goodSubset; + } + } + + $partitionsArray[] = $dataset; + + foreach ($partitionsArray as $key => $subset) { + $partitions->partition($key)->exchangeArray($subset); + } + } + + /** + * @param float $maxSize + * @param array $dataset + * @param float $target + * @param float $best + * + * @return array + */ + private function getSubsets($maxSize, $dataset, $target, $best) + { + $start_time = \time(); + $goodSubsets = $dataset; + $timeout = $this->getTimeout(); + + for ($i = 1; $i <= $maxSize; ++$i) { + $permutations = new CombinationsGenerator($dataset, $i); + + foreach ($permutations->generator() as $subset) { + if (\time() - $start_time > $timeout) { + return $goodSubsets; + } + + $x = 0; + + foreach ($subset as $item) { + $x += $item->getWeight(); + + if (\abs($x - $target) < \abs($best - $target)) { + $best = $x; + $goodSubsets = $subset; + } + } + } + } + + return $goodSubsets; + } +} diff --git a/src/Contract/Partition.php b/src/Contract/Partition.php new file mode 100644 index 0000000..79549ab --- /dev/null +++ b/src/Contract/Partition.php @@ -0,0 +1,9 @@ +uasort( + static function (PartitionItem $left, PartitionItem $right) { + return $right->getWeight() <=> $left->getWeight(); + } + ); + + foreach ($dataset as $data) { + $partitions + ->sort() + ->partition(0) + ->append($data); + } + } +} diff --git a/src/GreedyAlt.php b/src/GreedyAlt.php new file mode 100644 index 0000000..5b3e9d5 --- /dev/null +++ b/src/GreedyAlt.php @@ -0,0 +1,90 @@ +count() === $chunks) { + foreach ($dataset as $key => $item) { + $partitions->partition((int) $key)->exchangeArray([$item]); + } + + return; + } + + // Edge case handling. + if (0 === $chunks) { + throw new \InvalidArgumentException('Chunks must be different from 0.'); + } + + // Greedy needs a dataset sorted DESC. + $dataset->uasort( + static function (PartitionItem $left, PartitionItem $right) { + return $right->getWeight() <=> $left->getWeight(); + } + ); + + $best = $dataset->getWeight() / $chunks; + + for ($p = 1; $p < $chunks; ++$p) { + $partition = $partitions->partition($p - 1); + + while ($partition->getWeight() < $best && 1 < $dataset->count()) { + $key = $this->findOptimalItemKey($partition, $dataset, $best); + $partition->append($dataset[$key]); + unset($dataset[$key]); + } + } + + $partitions->partition($p - 1)->exchangeArray($dataset); + } + + /** + * @param \drupol\phpartition\Partition\Partition $partition + * @param \drupol\phpartition\Partition\Partition $dataset + * @param float $best + * + * @return null|false|int|string + */ + private function findOptimalItemKey(Partition $partition, Partition $dataset, $best) + { + // If the current partition is empty, then use the highest value of the + // dataset. + if (0 === $partition->count()) { + // As the dataset is sorted DESC, return the highest value. + return \key($dataset); + } + + // Find which number in the $dataset would be the best fit by checking + // which one is closer to the best solution. + // Best solution is the sum of the dataset values divided by the number + // of chunks we want to have. + $partitionWeightMinusBest = $partition->getWeight() - $best; + + $solutions = \array_map( + static function (PartitionItem $item) use ($partitionWeightMinusBest) { + return \abs($partitionWeightMinusBest + $item->getWeight()); + }, + \iterator_to_array($dataset) + ); + + \asort($solutions); + + return \key($solutions); + } +} diff --git a/src/GreedyAltAlt.php b/src/GreedyAltAlt.php new file mode 100644 index 0000000..24701cd --- /dev/null +++ b/src/GreedyAltAlt.php @@ -0,0 +1,72 @@ +count() === $chunks) { + foreach ($dataset as $key => $item) { + $partitions->partition((int) $key)->exchangeArray([$item]); + } + + return; + } + + // Edge case handling. + if (0 === $chunks) { + throw new \InvalidArgumentException('Chunks must be different from 0.'); + } + + // Greedy needs a dataset sorted DESC. + $dataset->uasort( + static function (PartitionItem $left, PartitionItem $right) { + return $left->getWeight() <=> $right->getWeight(); + } + ); + + $partitionsArray = []; + $best = $dataset->getWeight() / $chunks; + + for ($p = 1; $p < $chunks; ++$p) { + $partition = $this->getPartitionFactory()::create(); + + while ($partition->getWeight() < $best && 1 < $dataset->count()) { + $bounds = $dataset->getBounds(); + // Get the key of the item with the lowest value. + $key = $bounds[0]; + + if (0 === $partition->count()) { + // Get the key of the item with the highest value. + $key = $bounds[1]; + } + + $partition->append($dataset[$key]); + unset($dataset[$key]); + $dataset->exchangeArray(\array_values($dataset->getArrayCopy())); + } + + $partitionsArray[] = $partition; + } + + $partitionsArray[] = $dataset; + + foreach ($partitionsArray as $key => $subset) { + $partitions->partition($key)->exchangeArray($subset); + } + } +} diff --git a/src/GreedyAltAltAlt.php b/src/GreedyAltAltAlt.php new file mode 100644 index 0000000..1cc68f5 --- /dev/null +++ b/src/GreedyAltAltAlt.php @@ -0,0 +1,60 @@ +uasort( + static function (PartitionItem $left, PartitionItem $right) { + return $right->getWeight() <=> $left->getWeight(); + } + ); + + $partitionsArray = []; + $best = $dataset->getWeight() / $chunks; + + for ($p = 1; $p < $chunks; ++$p) { + $partition = $this->getPartitionFactory()::create(); + + \reset($dataset); + $key = \key($dataset); + $partition->append($dataset[$key]); + unset($dataset[$key]); + $dataset->exchangeArray(\array_values(\array_reverse($dataset->getArrayCopy()))); + + while ($partition->getWeight() < $best) { + \reset($dataset); + $key = \key($dataset); + $partition->append($dataset[$key]); + unset($dataset[$key]); + } + + $partitionsArray[] = $partition; + + $dataset->exchangeArray(\array_values(\array_reverse($dataset->getArrayCopy()))); + } + + $partitionsArray[] = $dataset; + + foreach ($partitionsArray as $key => $subset) { + $partitions->partition($key)->exchangeArray($subset); + } + } +} diff --git a/src/Linear.php b/src/Linear.php new file mode 100644 index 0000000..067f8ee --- /dev/null +++ b/src/Linear.php @@ -0,0 +1,113 @@ +getArrayCopy(); + + // See https://github.com/technically-php/linear-partitioning for + // original version of this algorithm. + + // An array S of non-negative numbers {s1, ... ,sn} + $s = \array_merge([null], $dataset); // adapt indices here: [0..n-1] => [1..n] + + // Integer K - number of ranges to split items into + $k = $chunks; + $n = \count($dataset); + + // Let D[n,k] be the position of K-th divider + // which produces the minimum possible cost partitioning of N elements to K ranges + $d = []; + + // Let p be the sum of first i elements (cost calculation optimization) + $p = []; + + // 1) Init prefix sums array + // pi = sum of {s1, ..., si} + $p[0] = $this->getPartitionItemFactory()::create(0); + + for ($i = 1; $i <= $n; ++$i) { + $p[$i] = $this->getPartitionItemFactory()::create($p[$i - 1]->getWeight() + $s[$i]->getWeight()); + } + + // Let M[n,k] be the minimum possible cost over all partitionings of N elements to K ranges + $m = []; + + // 2) Init boundaries + for ($i = 1; $i <= $n; ++$i) { + // The only possible partitioning of i elements to 1 range is a single all-elements range + // The cost of that partitioning is the sum of those i elements + $m[$i][1] = $p[$i]; // sum of {s1, ..., si} -- optimized using pi + } + + for ($j = 1; $j <= $k; ++$j) { + // The only possible partitioning of 1 element into j ranges is a single one-element range + // The cost of that partitioning is the value of first element + $m[1][$j] = $s[1]; + } + // 3) Main recurrence (fill the rest of values in table M) + for ($i = 2; $i <= $n; ++$i) { + for ($j = 2; $j <= $k; ++$j) { + $solutions = []; + + for ($x = 1; ($i - 1) >= $x; ++$x) { + $solutions[] = [ + 0 => $this->getPartitionItemFactory()::create( + \max( + $m[$x][$j - 1]->getWeight(), + $p[$i]->getWeight() - $p[$x]->getWeight() + ) + ), + 1 => $x, + ]; + } + + \usort( + $solutions, + static function (array $x, array $y) { + return $x[0] <=> $y[0]; + } + ); + + $best_solution = $solutions[0]; + $m[$i][$j] = $best_solution[0]; + $d[$i][$j] = $best_solution[1]; + } + } + + // 4) Reconstruct partitioning + $i = $n; + $j = $k; + $partition = []; + + while (0 < $j) { + // delimiter position + $dp = $d[$i][$j] ?? 0; + // Add elements after delimiter {sdp, ..., si} to resulting $partition. + $partition[] = \array_slice($s, $dp + 1, $i - $dp); + // Step forward: look for delimiter position for partitioning M[$dp, $j-1] + $i = $dp; + --$j; + } + + foreach ($partition as $i => $p) { + $partitions->partition($i)->exchangeArray($p); + } + } +} diff --git a/src/Partition.php b/src/Partition.php deleted file mode 100644 index 1778b71..0000000 --- a/src/Partition.php +++ /dev/null @@ -1,167 +0,0 @@ -addItems($elements); - } - - /** - * Add an item to the partition. - * - * @param PartitionItem $item - * The item to add to the partition. - * - * @return $this - * Return itself. - */ - public function addItem(PartitionItem $item) - { - $this->set(spl_object_hash($item), $item); - - return $this; - } - - /** - * Add items to the partition. - * - * @param PartitionItem[] $items - * The items to add to the partition. - * - * @return $this - * Return itself. - */ - public function addItems(array $items = array()) - { - foreach ($items as $item) { - $this->addItem($item); - } - - return $this; - } - - /** - * Get an array of original items. - * - * @return array - * The original items. - */ - public function getRawItems() - { - return array_values( - array_map(function ($item) { - return $item->getItem(); - }, $this->toArray()) - ); - } - - /** - * Get the weight of the partition. - * - * @return int - * The partition's weight. - */ - public function getWeight() - { - return array_reduce($this->toArray(), function ($sum, $item) { - $sum += $item->getValue(); - return $sum; - }); - } - - /** - * Set the algorithm to use. - * - * @param BasePartitionAlgorithm $algo - * The algorithm to use. - */ - public function setAlgo(BasePartitionAlgorithm $algo) - { - $this->algo = $algo; - } - - /** - * Get the algorithm in use. - * - * @return PartitionAlgorithmInterface - * The algorithm in use. - */ - public function getAlgo() - { - return $this->algo; - } - - /** - * Delete items from the partition. - * - * @param PartitionItem[] $items - * The items to delete. - */ - public function deleteItems(array $items = array()) - { - foreach ($items as $item) { - $this->delete($item); - } - } - - /** - * Delete an item from the partition. - * - * @param PartitionItem $item - * The item to delete. - */ - public function delete(PartitionItem $item) - { - $this->remove(spl_object_hash($item)); - } - - /** - * Sort the items of the partition in a particular order. - * - * @param string $order - * ASC for ascending, DESC for descending. - * - * @return $this - */ - public function sortByValue($order = 'ASC') - { - if ('ASC' == $order) { - $this->sort(function ($itemA, $itemB) { - return $itemA->getValue() > $itemB->getValue(); - }); - } - - if ('DESC' == $order) { - $this->sort(function ($itemA, $itemB) { - return $itemA->getValue() < $itemB->getValue(); - }); - } - - return $this; - } -} diff --git a/src/Partition/Partition.php b/src/Partition/Partition.php new file mode 100644 index 0000000..6b269e7 --- /dev/null +++ b/src/Partition/Partition.php @@ -0,0 +1,75 @@ +getValue(); + }, + $this->getArrayCopy() + ); + } + + /** + * {@inheritdoc} + */ + public function getBounds(): array + { + $max = \PHP_INT_MAX; + $min = \PHP_INT_MIN; + + $bounds = []; + + foreach ($this as $index => $item) { + if ($item instanceof PartitionItemInterface) { + $item = $item->getWeight(); + } + + if ($item < $max) { + $max = $item; + $bounds[0] = $index; + } + + if ($item > $min) { + $min = $item; + $bounds[1] = $index; + } + } + + return $bounds; + } + + /** + * {@inheritdoc} + */ + public function getWeight(): float + { + $weight = 0; + + foreach ($this as $item) { + if ($item instanceof PartitionItemInterface) { + $item = $item->getWeight(); + } + + $weight += $item; + } + + return $weight; + } +} diff --git a/src/Partition/PartitionFactory.php b/src/Partition/PartitionFactory.php new file mode 100644 index 0000000..1469fe6 --- /dev/null +++ b/src/Partition/PartitionFactory.php @@ -0,0 +1,16 @@ +value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getWeight(): float + { + return $this->value; + } +} diff --git a/src/Partition/PartitionItemFactory.php b/src/Partition/PartitionItemFactory.php new file mode 100644 index 0000000..c85979e --- /dev/null +++ b/src/Partition/PartitionItemFactory.php @@ -0,0 +1,22 @@ +getAlgo()->getPartitionWeight($partitionA); - $partitionBWeight = $partitionB->getAlgo()->getPartitionWeight($partitionB); - - if ($partitionAWeight == $partitionBWeight) { - return 0; - } - - return ($partitionAWeight > $partitionBWeight) ? -1 : +1; - } - - /** - * Set the size of the container. - * - * @param int $size - * The size. - */ - public function setSize($size) - { - $this->size = $size; - - for ($i = 0; $i < $size; $i++) { - $subset = new Partition(); - $subset->setAlgo($this->getAlgo()); - $this->insert($subset); - } - } - - /** - * Return the size of the container. - * - * @return int - * The number of partitions the container has. - */ - public function getSize() - { - return $this->size; - } - - /** - * Add items to the partition. - * - * @param PartitionItem[] $items - * The items to add. - */ - public function addItemsToPartition(array $items = array()) - { - foreach ($items as $item) { - $this->addItemToPartition($item); - } - } - - /** - * Add an item to the first partition. - * - * @param \drupol\phpartition\PartitionItem $item - * The item to add. - */ - public function addItemToPartition(PartitionItem $item) - { - $this->top(); - $subset = $this->extract(); - $subset->addItem($item); - $this->insert($subset); - } - - /** - * Get the partition in the container. - * - * @return Partition[] - * An array of partitions. - */ - public function getPartitions() - { - $data = array(); - $clone = clone $this; - - for ($clone->top(); $clone->valid(); $clone->next()) { - $data[] = $clone->current(); - } - - return $data; - } - - /** - * Return the items from each partitions in the container. - * - * @return mixed[] - * The items. - */ - public function getPartitionsItemsArray() - { - $data = array(); - - foreach ($this->getPartitions() as $subset) { - $data[] = $subset->getRawItems(); - } - - return array_values(array_filter($data)); - } - - /** - * Set the algorithm to use. - * - * @param \drupol\phpartition\BasePartitionAlgorithm $algo - * The algorithm. - */ - public function setAlgo(BasePartitionAlgorithm $algo) - { - $this->algo = $algo; - } - - /** - * Get the algorithm. - * - * @return BasePartitionAlgorithm - * The algorithm. - */ - public function getAlgo() - { - return $this->algo; - } -} diff --git a/src/PartitionItem.php b/src/PartitionItem.php deleted file mode 100644 index d6ecfbd..0000000 --- a/src/PartitionItem.php +++ /dev/null @@ -1,53 +0,0 @@ -item = $item; - $this->valueOrCallable = $valueOrCallable; - } - - /** - * {@inheritdoc} - */ - public function getValue() - { - if (is_callable($this->valueOrCallable)) { - return call_user_func($this->valueOrCallable, $this->item); - } - - if (!is_null($this->valueOrCallable)) { - return $this->valueOrCallable; - } - - return is_numeric($this->item) ? $this->item : 0; - } - - /** - * {@inheritdoc} - */ - public function getItem() - { - return $this->item; - } -} diff --git a/src/PartitionItemInterface.php b/src/PartitionItemInterface.php deleted file mode 100644 index 4d960c6..0000000 --- a/src/PartitionItemInterface.php +++ /dev/null @@ -1,28 +0,0 @@ -exportArrayCopy()); + }, + $this + ->run($chunks) + ->getLastRun() + ->partitions() + ); + } + + /** + * {@inheritdoc} + */ + public function getDataset() + { + return $this->dataset; + } + + /** + * @return \drupol\phpartition\Partition\Partition + */ + final public function getDatasetPartition() + { + $partition = $this->getPartitionFactory()::create(); + + foreach ($this->getDataset() as $data) { + $partition->append($this->toPartitionItem($data)); + } + + return $partition; + } + + /** + * @return \drupol\phpartition\Partitions\Partitions + */ + final public function getLastRun() + { + return $this->lastRun; + } + + /** + * @return \drupol\phpartition\Partition\PartitionFactory + */ + public function getPartitionFactory() + { + return $this->partitionFactory ?? new PartitionFactory(); + } + + /** + * @return \drupol\phpartition\Partition\PartitionItemFactory + */ + public function getPartitionItemFactory() + { + return $this->partitionItemFactory ?? new PartitionItemFactory(); + } + + /** + * @return \drupol\phpartition\Partitions\PartitionsFactory + */ + public function getPartitionsFactory() + { + return $this->partitionsFactory ?? new PartitionsFactory(); + } + + /** + * {@inheritdoc} + */ + final public function run(int $chunks = 1) + { + $partitions = $this->getPartitionsFactory()::create($chunks); + + $dataPartition = $this->getDatasetPartition(); + + $this->fillPartitions($partitions, $dataPartition, $chunks); + + $this->lastRun = $partitions; + + return $this; + } + + /** + * {@inheritdoc} + */ + final public function setDataset(iterable $dataset) + { + $this->dataset = $dataset; + + return $this; + } + + /** + * @param mixed $originalItem + * + * @return \drupol\phpartition\Contract\Weightable + */ + public function toPartitionItem($originalItem): Weightable + { + return $this->getPartitionItemFactory()::create($originalItem); + } + + /** + * @param \drupol\phpartition\Partitions\Partitions $partitions + * @param Partition $dataset + * @param int $chunks + */ + abstract protected function fillPartitions(Partitions $partitions, Partition $dataset, int $chunks): void; +} diff --git a/src/Partitions/Partitions.php b/src/Partitions/Partitions.php new file mode 100644 index 0000000..70d1471 --- /dev/null +++ b/src/Partitions/Partitions.php @@ -0,0 +1,76 @@ +storage['size'] = $size; + $partitionFactory = $partitionFactory ?? new PartitionFactory(); + + for ($i = 0; $i < $size; ++$i) { + $this->storage['partitions'][$i] = $partitionFactory::create(); + } + } + + /** + * {@inheritdoc} + */ + public function count() + { + return \count($this->storage['partitions']); + } + + /** + * @param int $index + * + * @return \drupol\phpartition\Partition\Partition + */ + public function partition(int $index) + { + return $this->storage['partitions'][$index]; + } + + /** + * @return \drupol\phpartition\Partition\Partition[] + */ + public function partitions() + { + return $this->storage['partitions']; + } + + /** + * @param null|callable $compareCallable + * + * @return \drupol\phpartition\Partitions\Partitions + */ + public function sort(callable $compareCallable = null) + { + if (null === $compareCallable) { + $compareCallable = static function (Partition $item1, Partition $item2) { + return $item1->getWeight() <=> $item2->getWeight(); + }; + } + + \usort($this->storage['partitions'], $compareCallable); + + return $this; + } +} diff --git a/src/Partitions/PartitionsFactory.php b/src/Partitions/PartitionsFactory.php new file mode 100644 index 0000000..ed12a04 --- /dev/null +++ b/src/Partitions/PartitionsFactory.php @@ -0,0 +1,13 @@ +getItems()); - } - - /** - * @param \drupol\phpartition\SubsetItem $item - */ - public function addItem(SubsetItem $item) - { - $this->items[$item->getKey()] = $item; - } - - /** - * @param SubsetItem[] $items - */ - public function addItems(array $items = array()) - { - foreach ($items as $item) { - $this->addItem($item); - } - } - - /** - * @return SubsetItem[] - */ - public function getItems() - { - return (array) $this->items; - } - - /** - * @param SubsetItem[] $items - */ - public function setItems(array $items = array()) - { - $this->items = $items; - } - - /** - * @return array - */ - public function getItemsValues() - { - $data = array(); - - foreach ($this->getItems() as $item) { - $data[$item->getKey()] = $item->getValue(); - } - - return $data; - } - - /** - * @return array - */ - public function getRawItems() - { - $data = array(); - - foreach ($this->getItems() as $item) { - $data[] = $item->getItem(); - } - - return $data; - } - - /** - * @return int - */ - public function getWeight() - { - $sum = 0; - - foreach ((array) $this->items as $item) { - $sum += $item->getValue(); - } - - return $sum; - } - - /** - * @param \drupol\phpartition\BasePartitionAlgorithm $algo - */ - public function setAlgo(BasePartitionAlgorithm $algo) - { - $this->algo = $algo; - } - - /** - * @return BasePartitionAlgorithm - */ - public function getAlgo() - { - return $this->algo; - } - - /** - * Clear the subset. - */ - public function clear() - { - $this->setItems(); - } - - /** - * @param $key - * - * @return \drupol\phpartition\SubsetItem - */ - public function getItem($key) - { - return $this->items[$key]; - } - - /** - * @param SubsetItem[] $items - */ - public function deleteItems(array $items = array()) - { - foreach ($items as $item) { - $this->delete($item); - } - } - - /** - * @param \drupol\phpartition\SubsetItem $item - */ - public function delete(SubsetItem $item) - { - foreach ($this->items as $key => $value) { - if ($value->getKey() == $item->getKey()) { - unset($this->items[$key]); - } - } - } - - /** - * @param string $order - * - * @return $this - */ - public function sortByValue($order = 'ASC') - { - $data = $this->getItems(); - - if ('ASC' == $order) { - usort($data, function ($itemA, $itemB) { - return $itemA->getValue() > $itemB->getValue(); - }); - } - - if ('DESC' == $order) { - usort($data, function ($itemA, $itemB) { - return $itemA->getValue() < $itemB->getValue(); - }); - } - - $this->setItems($data); - - return $this; - } -} diff --git a/src/SubsetContainer.php b/src/SubsetContainer.php deleted file mode 100644 index 84ed753..0000000 --- a/src/SubsetContainer.php +++ /dev/null @@ -1,109 +0,0 @@ -getAlgo()->getSubsetWeight($a); - $bl = $b->getAlgo()->getSubsetWeight($b); - - if ($al == $bl) { - return 0; - } - return ($al > $bl) ? -1 : +1; - } - - /** - * @param $partition - */ - public function setPartition($partition) - { - for ($i=0; $i<$partition; $i++) { - $subset = new Subset(); - $subset->setAlgo($this->getAlgo()); - $this->insert($subset); - } - } - - /** - * @param SubsetItem[] $items - */ - public function addItemsToSubset(array $items = array()) - { - foreach ($items as $item) { - $this->addItemToSubset($item); - } - } - - /** - * @param \drupol\phpartition\SubsetItem $item - */ - public function addItemToSubset(SubsetItem $item) - { - $this->top(); - $subset = $this->extract(); - $subset->addItem($item); - $this->insert($subset); - } - - /** - * @return Subset[] - */ - public function getSubsets() - { - $data = array(); - $clone = clone $this; - - for ($clone->top(); $clone->valid(); $clone->next()) { - $data[] = $clone->current(); - } - - return $data; - } - - /** - * @return array - */ - public function getSubsetsAndItemsAsArray() - { - $data = array(); - - foreach ($this->getSubsets() as $subset) { - $data[] = $subset->getRawItems(); - } - - return array_values(array_filter($data)); - } - - /** - * @param \drupol\phpartition\BasePartitionAlgorithm $algo - */ - public function setAlgo(BasePartitionAlgorithm $algo) - { - $this->algo = $algo; - } - - /** - * @return BasePartitionAlgorithm - */ - public function getAlgo() - { - return $this->algo; - } -} diff --git a/src/SubsetItem.php b/src/SubsetItem.php deleted file mode 100644 index 19b8ed8..0000000 --- a/src/SubsetItem.php +++ /dev/null @@ -1,54 +0,0 @@ -key = $key; - $this->value = $value; - $this->item = $item; - } - - /** - * @return mixed - */ - public function getValue() - { - return $this->value; - } - - /** - * @return mixed - */ - public function getKey() - { - return $this->key; - } - - /** - * @return mixed - */ - public function getItem() - { - return $this->item; - } -} diff --git a/src/Utils/Statistics.php b/src/Utils/Statistics.php new file mode 100644 index 0000000..a0eb3d3 --- /dev/null +++ b/src/Utils/Statistics.php @@ -0,0 +1,62 @@ +getWeight() / $partition->count(); + } + + /** + * @param \drupol\phpartition\Partition\Partition $partition + * + * @return float + */ + public static function standardDeviationPartition(Partition $partition) + { + $mean = self::meanPartition($partition); + + $sumSquareDiff = \array_sum(\array_map( + static function ($sum) use ($mean) { + return ($sum - $mean) ** 2; + }, + $partition->getArrayCopy() + )); + + return ($sumSquareDiff / $partition->count()) ** .5; + } + + /** + * @param \drupol\phpartition\Partitions\Partitions $partitions + * + * @return float + */ + public static function standardDeviationPartitions(Partitions $partitions) + { + $partition = new Partition( + \array_map( + static function (Partition $partition) { + return $partition->getWeight(); + }, + $partitions->partitions() + ) + ); + + return self::standardDeviationPartition($partition); + } +} diff --git a/test.php b/test.php deleted file mode 100644 index c21b3e4..0000000 --- a/test.php +++ /dev/null @@ -1,52 +0,0 @@ -setSize(3); - $algo->setData($input); - - $sums = []; - foreach ($algo->getResult() as $subset) { - sort($subset); - $sum = array_sum($subset); - $sums[] = $sum; - echo "[" . implode(',', $subset) . "] = " . $sum . "\n"; - } - - $stdDev = \Oefenweb\Statistics\Statistics::standardDeviation($sums); - echo "Variance: " . round($stdDev, 1) . "\n"; - - $end = microtime(true) - $start_time; - - echo "\nTime elapsed: " . $end . "\n"; -} diff --git a/tests/_data/.gitkeep b/tests/_data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/_support/Helper/Unit.php b/tests/_support/Helper/Unit.php new file mode 100644 index 0000000..7a222b3 --- /dev/null +++ b/tests/_support/Helper/Unit.php @@ -0,0 +1,12 @@ +getScenario()->runStep(new \Codeception\Step\Action('assertArrayHasKey', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $key + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertArrayNotHasKey() + */ + public function assertArrayNotHasKey($key, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertArrayNotHasKey', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that haystack contains needle + * + * @param $needle + * @param $haystack + * @param string $message + * + * @see \Codeception\Module\Asserts::assertContains() + */ + public function assertContains($needle, $haystack, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertContains', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $expectedCount + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertCount() + */ + public function assertCount($expectedCount, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertCount', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is empty. + * + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertEmpty() + */ + public function assertEmpty($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEmpty', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that two variables are equal. + * + * @param $expected + * @param $actual + * @param string $message + * @param float $delta + * + * @see \Codeception\Module\Asserts::assertEquals() + */ + public function assertEquals($expected, $actual, $message = null, $delta = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEquals', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that condition is negative. + * + * @param $condition + * @param string $message + * + * @see \Codeception\Module\Asserts::assertFalse() + */ + public function assertFalse($condition, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFalse', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks if file exists + * + * @param string $filename + * @param string $message + * + * @see \Codeception\Module\Asserts::assertFileExists() + */ + public function assertFileExists($filename, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileExists', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks if file doesn't exist + * + * @param string $filename + * @param string $message + * + * @see \Codeception\Module\Asserts::assertFileNotExists() + */ + public function assertFileNotExists($filename, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertFileNotExists', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $expected + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertGreaterOrEquals() + */ + public function assertGreaterOrEquals($expected, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterOrEquals', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that actual is greater than expected + * + * @param $expected + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertGreaterThan() + */ + public function assertGreaterThan($expected, $actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterThan', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that actual is greater or equal than expected + * + * @param $expected + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertGreaterThanOrEqual() + */ + public function assertGreaterThanOrEqual($expected, $actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertGreaterThanOrEqual', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $class + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertInstanceOf() + */ + public function assertInstanceOf($class, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertInstanceOf', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $type + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertInternalType() + */ + public function assertInternalType($type, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertInternalType', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsArray() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsArray($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsArray', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsBool() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsBool($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsBool', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsCallable() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsCallable($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsCallable', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertIsEmpty() + */ + public function assertIsEmpty($actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsEmpty', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsFloat() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsFloat($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsFloat', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsInt() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsInt($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsInt', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotArray() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotArray($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotArray', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotBool() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotBool($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotBool', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotCallable() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotCallable($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotCallable', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotFloat() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotFloat($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotFloat', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotInt() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotInt($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotInt', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotNumeric() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotNumeric($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotNumeric', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotObject() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotObject($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotObject', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotResource() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotResource($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotResource', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotScalar() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotScalar($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotScalar', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNotString() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNotString($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNotString', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsNumeric() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsNumeric($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsNumeric', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsObject() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsObject($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsObject', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsResource() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsResource($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsResource', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsScalar() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsScalar($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsScalar', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertIsString() + * + * @param mixed $actual + * @param null|mixed $message + */ + public function assertIsString($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertIsString', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $expected + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertLessOrEquals() + */ + public function assertLessOrEquals($expected, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessOrEquals', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that actual is less than expected + * + * @param $expected + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertLessThan() + */ + public function assertLessThan($expected, $actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessThan', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that actual is less or equal than expected + * + * @param $expected + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertLessThanOrEqual() + */ + public function assertLessThanOrEqual($expected, $actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertLessThanOrEqual', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that haystack doesn't contain needle. + * + * @param $needle + * @param $haystack + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotContains() + */ + public function assertNotContains($needle, $haystack, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotContains', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is not empty. + * + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotEmpty() + */ + public function assertNotEmpty($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEmpty', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that two variables are not equal + * + * @param $expected + * @param $actual + * @param string $message + * @param float $delta + * + * @see \Codeception\Module\Asserts::assertNotEquals() + */ + public function assertNotEquals($expected, $actual, $message = null, $delta = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEquals', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT false (everything but false) + * + * @param $condition + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotFalse() + */ + public function assertNotFalse($condition, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotFalse', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @param $class + * @param $actual + * @param $description + * + * @see \Codeception\Module\Asserts::assertNotInstanceOf() + */ + public function assertNotInstanceOf($class, $actual, $description = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotInstanceOf', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is not NULL + * + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotNull() + */ + public function assertNotNull($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotNull', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that string not match with pattern + * + * @param string $pattern + * @param string $string + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotRegExp() + */ + public function assertNotRegExp($pattern, $string, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotRegExp', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that two variables are not same + * + * @param $expected + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotSame() + */ + public function assertNotSame($expected, $actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotSame', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that the condition is NOT true (everything but true) + * + * @param $condition + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNotTrue() + */ + public function assertNotTrue($condition, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotTrue', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that variable is NULL + * + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertNull() + */ + public function assertNull($actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNull', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that string match with pattern + * + * @param string $pattern + * @param string $string + * @param string $message + * + * @see \Codeception\Module\Asserts::assertRegExp() + */ + public function assertRegExp($pattern, $string, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertRegExp', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that two variables are same + * + * @param $expected + * @param $actual + * @param string $message + * + * @see \Codeception\Module\Asserts::assertSame() + */ + public function assertSame($expected, $actual, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertSame', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertStringContainsString() + * + * @param mixed $needle + * @param mixed $haystack + * @param null|mixed $message + */ + public function assertStringContainsString($needle, $haystack, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringContainsString', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertStringContainsStringIgnoringCase() + * + * @param mixed $needle + * @param mixed $haystack + * @param null|mixed $message + */ + public function assertStringContainsStringIgnoringCase($needle, $haystack, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringContainsStringIgnoringCase', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertStringNotContainsString() + * + * @param mixed $needle + * @param mixed $haystack + * @param null|mixed $message + */ + public function assertStringNotContainsString($needle, $haystack, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotContainsString', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * @see \Codeception\Module\Asserts::assertStringNotContainsStringIgnoringCase() + * + * @param mixed $needle + * @param mixed $haystack + * @param null|mixed $message + */ + public function assertStringNotContainsStringIgnoringCase($needle, $haystack, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringNotContainsStringIgnoringCase', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a string doesn't start with the given prefix. + * + * @param string $prefix + * @param string $string + * @param string $message + * + * @see \Codeception\Module\Asserts::assertStringStartsNotWith() + */ + public function assertStringStartsNotWith($prefix, $string, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsNotWith', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that a string starts with the given prefix. + * + * @param string $prefix + * @param string $string + * @param string $message + * + * @see \Codeception\Module\Asserts::assertStringStartsWith() + */ + public function assertStringStartsWith($prefix, $string, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertStringStartsWith', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Checks that condition is positive. + * + * @param $condition + * @param string $message + * + * @see \Codeception\Module\Asserts::assertTrue() + */ + public function assertTrue($condition, $message = null) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('assertTrue', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Handles and checks exception called inside callback function. + * Either exception class name or exception instance should be provided. + * + * ```php + * expectException(MyException::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectException(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or exception code, you can pass them with exception instance: + * ```php + * expectException(new MyException("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $exception string or \Exception + * @param $callback + * + * @see \Codeception\Module\Asserts::expectException() + */ + public function expectException($exception, $callback) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('expectException', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Handles and checks throwables (Exceptions/Errors) called inside the callback function. + * Either throwable class name or throwable instance should be provided. + * + * ```php + * expectThrowable(MyThrowable::class, function() { + * $this->doSomethingBad(); + * }); + * + * $I->expectThrowable(new MyException(), function() { + * $this->doSomethingBad(); + * }); + * ``` + * If you want to check message or throwable code, you can pass them with throwable instance: + * ```php + * expectThrowable(new MyError("Don't do bad things"), function() { + * $this->doSomethingBad(); + * }); + * ``` + * + * @param $throwable string or \Throwable + * @param $callback + * + * @see \Codeception\Module\Asserts::expectThrowable() + */ + public function expectThrowable($throwable, $callback) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('expectThrowable', \func_get_args())); + } + + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * Fails the test with message. + * + * @param $message + * + * @see \Codeception\Module\Asserts::fail() + */ + public function fail($message) + { + return $this->getScenario()->runStep(new \Codeception\Step\Action('fail', \func_get_args())); + } + + /** + * @return \Codeception\Scenario + */ + abstract protected function getScenario(); +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml deleted file mode 100644 index 63d0fbf..0000000 --- a/tests/phpunit.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - ./src/ - - - - - - ./vendor - - - ../src - - - diff --git a/tests/src/BruteForceCustomATest.php b/tests/src/BruteForceCustomATest.php deleted file mode 100644 index d0e4eef..0000000 --- a/tests/src/BruteForceCustomATest.php +++ /dev/null @@ -1,107 +0,0 @@ -setSize($input['partition']); - $this->assertEquals($input['partition'], $algo->getSize()); - - $algo->setData($input['data']); - $this->assertEquals($input['data'], $algo->getData()); - - if (isset($input['callback'])) { - $algo->setItemAccessCallback($input['callback']); - } - - $this->assertEquals($output, $algo->getResult()); - } - - /** - * Value provider. - */ - public function simpleValueProvider() - { - $a = new \StdClass(); - $a->id = 'a'; - - $b = new \StdClass(); - $b->id = 'b'; - - return [ - [ - 'input' => [ - 'data' => [1, new PartitionItem($a, 2), new PartitionItem($b, 3), 4], - 'partition' => 2, - ], - 'output' => [ - [1, 4], - [$a, $b], - ], - ], - [ - 'input' => [ - 'data' => [1, 2, 3, 4], - 'partition' => 4, - ], - 'output' => [ - [1], [4], [2], [3], - ], - ], - [ - 'input' => [ - 'data' => [1, 2, 3, 4], - 'partition' => 5, - ], - 'output' => [ - [3], [4], [1], [2], - ], - ], - [ - 'input' => [ - 'data' => [1, 2, 3, 4], - 'partition' => 2, - ], - 'output' => [ - [1, 4], - [2, 3], - ], - ], - [ - 'input' => [ - 'data' => [ - ['key' => 'item1', 'weight' => 1], - ['key' => 'item2', 'weight' => 2], - ['key' => 'item3', 'weight' => 3], - ['key' => 'item4', 'weight' => 4], - ], - 'partition' => 2, - 'callback' => function ($item) { - return $item['weight']; - }, - ], - 'output' => [ - [['key' => 'item1', 'weight' => 1], ['key' => 'item4', 'weight' => 4]], - [['key' => 'item2', 'weight' => 2], ['key' => 'item3', 'weight' => 3]], - ], - ], - ]; - } -} diff --git a/tests/src/BruteForceTest.php b/tests/src/BruteForceTest.php deleted file mode 100644 index 9830ef7..0000000 --- a/tests/src/BruteForceTest.php +++ /dev/null @@ -1,96 +0,0 @@ -setSize($input['partition']); - $this->assertEquals($input['partition'], $algo->getSize()); - - $algo->setData($input['data']); - $this->assertEquals($input['data'], $algo->getData()); - - if (isset($input['callback'])) { - $algo->setItemAccessCallback($input['callback']); - } - - $this->assertEquals( - $output, - $algo->getResult(), - "\$canonicalize = true", - $delta = 0.0, - $maxDepth = 10, - $canonicalize = true - ); - } - - /** - * Value provider. - */ - public function simpleValueProvider() - { - $a = new \StdClass(); - $a->id = 'a'; - - $b = new \StdClass(); - $b->id = 'b'; - - return [ - [ - 'input' => [ - 'data' => [1, new PartitionItem($a, 2), new PartitionItem($b, 3), 4], - 'partition' => 2, - ], - 'output' => [ - [1, 4], - [$a, $b], - ], - ], - [ - 'input' => [ - 'data' => [1, 2, 3, 4], - 'partition' => 2, - ], - 'output' => [ - [1, 4], - [2, 3], - ], - ], - [ - 'input' => [ - 'data' => [ - ['key' => 'item1', 'weight' => 1], - ['key' => 'item2', 'weight' => 2], - ['key' => 'item3', 'weight' => 3], - ['key' => 'item4', 'weight' => 4], - ], - 'partition' => 2, - 'callback' => function ($item) { - return $item['weight']; - }, - ], - 'output' => [ - [['key' => 'item1', 'weight' => 1], ['key' => 'item4', 'weight' => 4]], - [['key' => 'item2', 'weight' => 2], ['key' => 'item3', 'weight' => 3]], - ], - ], - ]; - } -} diff --git a/tests/src/GreedyTest.php b/tests/src/GreedyTest.php deleted file mode 100644 index 3dad2b8..0000000 --- a/tests/src/GreedyTest.php +++ /dev/null @@ -1,89 +0,0 @@ -setSize($input['partition']); - $this->assertEquals($input['partition'], $algo->getSize()); - - $algo->setData($input['data']); - $this->assertEquals($input['data'], $algo->getData()); - - if (isset($input['callback'])) { - $algo->setItemAccessCallback($input['callback']); - } - - $this->assertEquals($output, $algo->getResult()); - } - - /** - * Value provider. - */ - public function simpleValueProvider() - { - $a = new \StdClass(); - $a->id = 'a'; - - $b = new \StdClass(); - $b->id = 'b'; - - return [ - [ - 'input' => [ - 'data' => [1, new PartitionItem($a, 2), new PartitionItem($b, 3), 4], - 'partition' => 2, - ], - 'output' => [ - [$b, $a], - [4, 1], - ], - ], - [ - 'input' => [ - 'data' => [1, 2, 3, 4], - 'partition' => 2, - ], - 'output' => [ - [3, 2], - [4, 1], - ], - ], - [ - 'input' => [ - 'data' => [ - ['key' => 'item1', 'weight' => 1], - ['key' => 'item2', 'weight' => 2], - ['key' => 'item3', 'weight' => 3], - ['key' => 'item4', 'weight' => 4], - ], - 'partition' => 2, - 'callback' => function ($item) { - return $item['weight']; - }, - ], - 'output' => [ - [['key' => 'item3', 'weight' => 3], ['key' => 'item2', 'weight' => 2]], - [['key' => 'item4', 'weight' => 4], ['key' => 'item1', 'weight' => 1]], - ], - ], - ]; - } -} diff --git a/tests/src/PhpPartitionTestBase.php b/tests/src/PhpPartitionTestBase.php deleted file mode 100644 index eb2cc89..0000000 --- a/tests/src/PhpPartitionTestBase.php +++ /dev/null @@ -1,22 +0,0 @@ -setSize($input['partition']); - $this->assertEquals($input['partition'], $algo->getSize()); - - $algo->setData($input['data']); - $this->assertEquals($input['data'], $algo->getData()); - - if (isset($input['callback'])) { - $algo->setItemAccessCallback($input['callback']); - } - - $this->assertEquals($output, $algo->getResult()); - } - - /** - * Value provider. - */ - public function simpleValueProvider() - { - $a = new \StdClass(); - $a->id = 'a'; - - $b = new \StdClass(); - $b->id = 'b'; - - return [ - [ - 'input' => [ - 'data' => [1, new PartitionItem($a, 2), new PartitionItem($b, 3), 4], - 'partition' => 2, - ], - 'output' => [ - [1, $b], - [$a, 4], - ], - ], - [ - 'input' => [ - 'data' => [1, 2, 3, 4], - 'partition' => 2, - ], - 'output' => [ - [1, 3], - [2, 4], - ], - ], - [ - 'input' => [ - 'data' => [ - ['key' => 'item1', 'weight' => 1], - ['key' => 'item2', 'weight' => 2], - ['key' => 'item3', 'weight' => 3], - ['key' => 'item4', 'weight' => 4], - ], - 'partition' => 2, - 'callback' => function ($item) { - return $item['weight']; - }, - ], - 'output' => [ - [['key' => 'item1', 'weight' => 1], ['key' => 'item3', 'weight' => 3]], - [['key' => 'item2', 'weight' => 2], ['key' => 'item4', 'weight' => 4]], - ], - ], - ]; - } -} diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml new file mode 100644 index 0000000..026c91d --- /dev/null +++ b/tests/unit.suite.yml @@ -0,0 +1,9 @@ +# Codeception Test Suite Configuration +# +# Suite for unit or integration tests. + +actor: UnitTester +modules: + enabled: + - Asserts + - \Helper\Unit \ No newline at end of file diff --git a/tests/unit/CombinationsTest.php b/tests/unit/CombinationsTest.php new file mode 100644 index 0000000..fb640de --- /dev/null +++ b/tests/unit/CombinationsTest.php @@ -0,0 +1,83 @@ +setDataset($dataset); + + self::assertSame($expected, $anytime->export($chunks)); + } +} diff --git a/tests/unit/GreedyAltAltTest.php b/tests/unit/GreedyAltAltTest.php new file mode 100644 index 0000000..57c6429 --- /dev/null +++ b/tests/unit/GreedyAltAltTest.php @@ -0,0 +1,77 @@ +setDataset($dataset); + + self::assertSame($expected, $greedy->export($chunks)); + } +} diff --git a/tests/unit/GreedyAltTest.php b/tests/unit/GreedyAltTest.php new file mode 100644 index 0000000..2ba4306 --- /dev/null +++ b/tests/unit/GreedyAltTest.php @@ -0,0 +1,77 @@ +setDataset($dataset); + + self::assertSame($expected, $greedy->export($chunks)); + } +} diff --git a/tests/unit/GreedyTest.php b/tests/unit/GreedyTest.php new file mode 100644 index 0000000..cc24fde --- /dev/null +++ b/tests/unit/GreedyTest.php @@ -0,0 +1,83 @@ +setDataset($dataset); + + self::assertSame($expected, $greedy->export($chunks)); + } +} diff --git a/tests/unit/LinearTest.php b/tests/unit/LinearTest.php new file mode 100644 index 0000000..797dcd3 --- /dev/null +++ b/tests/unit/LinearTest.php @@ -0,0 +1,83 @@ +setDataset($dataset); + + self::assertSame($expected, $greedy->export($chunks)); + } +} diff --git a/tests/unit/Partition/PartitionTest.php b/tests/unit/Partition/PartitionTest.php new file mode 100644 index 0000000..7c9d9b5 --- /dev/null +++ b/tests/unit/Partition/PartitionTest.php @@ -0,0 +1,52 @@ +getBounds()); + } + + public function testGetBoundsProvider() + { + yield [ + [4, 5, 6, 7, 8], + [0, 4], + ]; + + yield [ + [8, 5, 6, 7, 4], + [4, 0], + ]; + + yield [ + [5, 4, 6, 8, 7], + [1, 3], + ]; + } +} diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php new file mode 100644 index 0000000..174d7fd --- /dev/null +++ b/tests/unit/_bootstrap.php @@ -0,0 +1,3 @@ +