From b1e56809baa158aa658ca221e9310020eb424783 Mon Sep 17 00:00:00 2001 From: abmmhasan Date: Wed, 6 Dec 2023 22:14:13 +0600 Subject: [PATCH] refactor draw classes and README --- .github/workflows/ci.yml | 38 ------ .github/workflows/codacy.yml | 60 ---------- .github/workflows/php.yml | 38 ++++++ .gitignore | 4 +- README.md | 156 +++++++----------------- composer.json | 5 +- src/Draw/GrandDraw.php | 111 +++++++++++++++++ src/Draw/LuckyDraw.php | 226 ++++++++++++++++++----------------- src/Draw/MegaDraw.php | 95 --------------- 9 files changed, 316 insertions(+), 417 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/codacy.yml create mode 100644 .github/workflows/php.yml create mode 100644 src/Draw/GrandDraw.php delete mode 100644 src/Draw/MegaDraw.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index fb1f0e8..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: "build" - -on: [ "pull_request", "push" ] - -jobs: - run: - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: [ ubuntu-latest, windows-latest, macOS-latest ] - php-versions: [ '7.0','7.1','7.2','7.3','7.4','8.0', '8.1' ] - name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Check PHP Version - run: php -v \ No newline at end of file diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml deleted file mode 100644 index 523f897..0000000 --- a/.github/workflows/codacy.yml +++ /dev/null @@ -1,60 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow checks out code, performs a Codacy security scan -# and integrates the results with the -# GitHub Advanced Security code scanning feature. For more information on -# the Codacy security scan action usage and parameters, see -# https://github.com/codacy/codacy-analysis-cli-action. -# For more information on Codacy Analysis CLI in general, see -# https://github.com/codacy/codacy-analysis-cli. - -name: Codacy Security Scan - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '43 6 * * 0' - -permissions: - contents: read - -jobs: - codacy-security-scan: - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - name: Codacy Security Scan - runs-on: ubuntu-latest - steps: - # Checkout the repository to the GitHub Actions runner - - name: Checkout code - uses: actions/checkout@v2 - - # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b - with: - # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository - # You can also omit the token and run the tools that support default configurations - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - verbose: true - output: results.sarif - format: sarif - # Adjust severity of non-security issues - gh-code-scanning-compat: true - # Force 0 exit code to allow SARIF file generation - # This will handover control about PR rejection to the GitHub side - max-allowed-issues: 2147483647 - - # Upload the SARIF file generated in the previous step - - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@v1 - with: - sarif_file: results.sarif diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..02820e0 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,38 @@ +name: "Stability Test" + +on: + push: + branches: [ '*' ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ ubuntu-latest ] + php-versions: [ '8.0', '8.1', '8.2', '8.3' ] + name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer:v2 + coverage: xdebug + + - name: Check PHP Version + run: php -v + + - name: Validate Composer + run: composer validate --strict + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Package Audit + run: composer audit diff --git a/.gitignore b/.gitignore index 45b1244..c7f6889 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea -/vendor \ No newline at end of file +/vendor +composer.lock +example.php \ No newline at end of file diff --git a/README.md b/README.md index 6e192f5..7e2f5c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # Game Draw -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/abmmhasan/game-draw/build) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/56e7f49275dc4042b67d53b4209b193d)](https://www.codacy.com/gh/abmmhasan/Game-Draw/dashboard?utm_source=github.com&utm_medium=referral&utm_content=abmmhasan/Game-Draw&utm_campaign=Badge_Grade) ![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/abmmhasan/game-draw) ![Packagist Downloads](https://img.shields.io/packagist/dt/abmmhasan/game-draw) @@ -8,20 +7,18 @@ ![Packagist Version](https://img.shields.io/packagist/v/abmmhasan/game-draw) ![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/abmmhasan/game-draw) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/abmmhasan/game-draw) -![Lines of code](https://img.shields.io/tokei/lines/github/abmmhasan/game-draw) -The Lucky Draw class takes an example array (explained below) and generates Item and Item count for winners. -The Mega Draw class takes 2 example array (explained below) and generates winners for each prize according to given amount per prize. +The Game Draw library provides 2 different way of winner selection based on user's input and selected method. -> Please don't use this to generate things/prizes with People's hard earned money. It is intended to make things fun with bonus gifts only. +> Please don't use this to generate things/prizes with People's hard-earned money. It is intended to make things fun with bonus gifts only. ## Prerequisits -Language: PHP 7+ +Language: PHP 8/+ -PHP Extension: BCMath (may need to install manually in Linux servers) +PHP Extension: BCMath (may need to install manually) ## Installation @@ -34,16 +31,16 @@ composer require abmmhasan/game-draw ### Input Data ```php -[ +$products = [ [ 'item' => 'product_000_NoLuck', // Item code or Identifier 'chances' => '100000', // Item Chances - 'amounts '=> [ 1 ] // Item Amounts + 'amounts'=> [ 1 ] // Item Amounts ], [ 'item' => 'product_001', 'chances' => '1000', - 'amounts' => [ rand(1,100) ] // Random Value passing + 'amounts' => '1.5,10.00001,1' // Weighted CSV formatted range (min,max,bias) ], [ 'item' => 'product_002', @@ -52,7 +49,6 @@ composer require abmmhasan/game-draw 1 => 100, // Amount chances 5 => 50, // Format: Amount => Chances 10 => 10.002, // Fraction allowed - rand(50,60) => 1, // Random Value in Amount ] ], [ @@ -75,16 +71,14 @@ composer require abmmhasan/game-draw - **item**: Provide your item's unique identifier -- **chances**: Weight of item. +- **chances**: Weight of item (Float/Int). - It will be compared along all the items in array. - The higher the chances the greater the chances of getting the item. - - Fraction number supported - - In case of active inventory we can pass available item stock here + - In case of active inventory you can pass available item stock here -- **amounts**: Array of Item amount. It can be any like following: - - Single Value, i.e. [ 1 ] or random single value, i.e. [ 1-100 ] - - Fraction number supported - - Can be weighted amount, i.e. +- **amounts**: String or Array of Item amount (Float/Int). It can be any like: + - (array) Single Positive value, i.e. [ 1 ] or Multiple Positive value (randomly picked), i.e. [ 1, 2, 3, 5] + - (array) Weighted amount, i.e. ```php [ 5 => 100, @@ -92,39 +86,32 @@ composer require abmmhasan/game-draw 50 => 10, 80 => 5.001 ] - ``` - - We can also pass random single value, i.e. [ 50-100 ] in amount part using rand() or mt_rand(). - ```php - [ - 1 => 100, - 5 => 50, - 10 => 10, - rand(50,100) => 5 - ] - ``` - - Or can be selective amount for random pick - ```php - [ 10, 15, 30, 50, 90 ] ``` + - (String) Weighted CSV formatted range (min,max,bias) ```'1,10.00001,0.001'``` + - Only 3 members allowed in CSV format **min,max,bias** + - Max should be greater than or equal to min, bias should be greater than 0 + - The higher the bias, the more the chance to pick the lowest amount ### Output Data -```markdown -product_000_NoLuck (1) // Item Code and Amount +```php +$luckyDraw = new AbmmHasan\Draw\LuckyDraw($products); +$luckyDraw->pick() ``` +Will output the data similar as following, ```php -list( $p, $c ) = (new LuckyDraw($prizes))->draw(); +[ + 'item' => 'product_000_NoLuck', // The item name + 'amount' => 1 // the selected amount +] ``` -- We will pass the Formatted Input i.e. $prizes -- From above example, (after execution) $p will be the Item Code and $c will be the item count. - ### Inventory Solutions Available stock should be passed (after subtracting used amount from stock amount) in chances properly. -## Usage (Mega Draw) +## Usage (Grand Draw) ### Input Data @@ -143,30 +130,32 @@ $prizes = - **amounts**: Amount of gift. It must be a positive integer value. -To pass users where the Gifts are general: +To pass users, you've to make a CSV file with at-least 1 column. 1st column will indicate user identity. -```php -$users = -['user01','user02','user03',..........,'userNNNNNNN']; // user identity +```csv +"usr47671", +"usr57665", +"usr47671",..... ``` -Or where the gifts are specifc per user group +### Output Data ```php -$users = -[ - 'user01'=>'product_002', // user identity => Item Code/Identifier - 'user02'=>'product_003', - 'user03'=>'product_002', - 'user04'=>'product_001', - . - . - 'user NNNNNNN'=>'product_002' -]; +$bucket = new GrandDraw(); + +// set resources +$bucket->setItems([ // set prizes + 'product_001' => 10, // Item Code/Identifier => Amount of the item + 'product_002' => 5, + 'product_003' => 3, + 'product_004' => 2, + 'product_005' => 1 +])->setUserListFilePath('./Sample1000.csv'); // set the CSV file location + +// get the winners +$bucket->getWinners() ``` - -### Output Data - +Will provide the output similar as following, ```php Array ( @@ -182,46 +171,6 @@ Array [7] => usr82268 [8] => usr16521 [9] => usr24864 - [10] => usr52595 - [11] => usr39674 - [12] => usr52520 - [13] => usr42316 - [14] => usr41327 - [15] => usr41461 - [16] => usr74861 - [17] => usr40589 - [18] => usr79599 - [19] => usr86757 - [20] => usr92409 - [21] => usr51569 - [22] => usr37905 - [23] => usr43123 - [24] => usr98934 - [25] => usr56999 - [26] => usr26529 - [27] => usr37097 - [28] => usr8417 - [29] => usr65328 - [30] => usr11656 - [31] => usr56668 - [32] => usr87999 - [33] => usr83457 - [34] => usr39765 - [35] => usr31917 - [36] => usr22395 - [37] => usr27971 - [38] => usr89124 - [39] => usr42330 - [40] => usr30652 - [41] => usr19458 - [42] => usr96018 - [43] => usr32073 - [44] => usr55307 - [45] => usr23103 - [46] => usr37772 - [47] => usr64712 - [48] => usr39795 - [49] => usr3161 ) [product_002] => Array @@ -254,21 +203,6 @@ Array ) ``` -To get Gift for General Case: - -```php -print_r((new Megadraw())->get($prizes,$users)); -``` - -To get Gift for Grouped Case: - -```php -print_r((new Megadraw())->get($prizes,$users,true)); -``` - -- We will pass the Formatted Input i.e. $prizes -- From the above example, (after execution) we will get Users won in each category. - ## Support Having trouble? Create an issue! diff --git a/composer.json b/composer.json index 6fb7924..c572898 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,10 @@ } }, "require": { - "php": ">=7.0", + "php": ">=8.0", "ext-bcmath": "*" + }, + "require-dev": { + "symfony/var-dumper": "^6.3" } } diff --git a/src/Draw/GrandDraw.php b/src/Draw/GrandDraw.php new file mode 100644 index 0000000..b716088 --- /dev/null +++ b/src/Draw/GrandDraw.php @@ -0,0 +1,111 @@ +userListFilePath = new SplFileObject($userListFilePath); + $this->userListFilePath->setFlags(SplFileObject::READ_CSV); + return $this; + } + + /** + * Sets the items for the BucketDraw object. + * + * @param array $items The array of items to be set. + * @return GrandDraw The updated BucketDraw object. + */ + public function setItems(array $items): GrandDraw + { + $this->items = $items; + return $this; + } + + /** + * Retrieves the winners of the draw. + * + * @param int $retryCount The number of times to retry drawing a unique winner (default: 10) + * @return array The array containing the selected winners per item. + * @throws Exception + */ + public function getWinners(int $retryCount = 10): array + { + $line = $this->getLineCount($this->userListFilePath->getRealPath()); + $selectedWinners = []; + foreach ($this->items as $item => $count) { + $selectedWinners[$item] = $this->draw($count, $line, $retryCount); + } + return $selectedWinners; + } + + /** + * Draws a specified number of winners from a user list. + * + * @param int $pickCount The number of winners to pick. + * @param int $lineCount The total number of lines in the user list. + * @param int $retryCount The maximum number of retries before giving up. + * @return array An array of the selected winners' IDs. + * @throws Exception + */ + private function draw(int $pickCount, int $lineCount, int $retryCount): array + { + $failCount = 0; + $seekMax = $lineCount - 1; + $winners = []; + for ($i = 0; $i < $pickCount; $i++) { + $line = random_int(0, $seekMax); + $this->userListFilePath->seek($line); + $id = $this->userListFilePath->fgetcsv()[0]; + if (!in_array($id, $this->selected)) { + $this->selected[] = $winners[] = $id; + $failCount = 0; + continue; + } + $pickCount++; + if (++$failCount > $retryCount) { + break; + } + } + return $winners; + } + + + /** + * Retrieves the line count of a given file. + * + * @param string $filePath The path of the file to get the line count for. + * @return int The total number of lines in the file. + * @throws Exception If there is an error retrieving the line count. + */ + private function getLineCount(string $filePath): int + { + switch (PHP_OS_FAMILY) { + case 'Darwin': + case 'Linux': + exec("wc -l $filePath", $lineCount); + break; + default: + exec('type "' . $filePath . '" | find /v /c ""', $lineCount); + } + return (int)strtok($lineCount[0], " "); + } +} diff --git a/src/Draw/LuckyDraw.php b/src/Draw/LuckyDraw.php index 96f51fa..a0289bc 100644 --- a/src/Draw/LuckyDraw.php +++ b/src/Draw/LuckyDraw.php @@ -1,165 +1,169 @@ - * @copyright Copyright (c), 2019 A. B. M. Mahmudul Hasan - * @license MIT public license - */ +use Exception; +use InvalidArgumentException; +use LengthException; +use UnexpectedValueException; + class LuckyDraw { - private $items = []; - private $fraction; - private $check; - private $draw = []; - - /** - * @param array $items the list of items - * @param bool $fraction If the chances/amounts include fraction number(true by default) or not(false) - * @param bool $check If the items are already checked before can omit by passing false - * @exception If required keys not present/values for the keys are not properly formatted - */ - public function __construct(array $items, bool $fraction = true, bool $check = true) + public function __construct(private array $items) { - $this->init($items, $fraction, $check); } - private function init(array $items, bool $fraction = true, bool $check = true) + /** + * Checks the validity of the items array. + * + * @throws LengthException if the number of items is less than 1. + * @throws InvalidArgumentException if required keys (item, chances, amounts) are not present for any item. + */ + private function check(): void { - if (count($items) < 1) { - throw new \LengthException('Invalid number of items!'); + if (count($this->items) < 1) { + throw new LengthException('Invalid number of items!'); } - if ($check) { - foreach ($items as $item) { - if (!isset($item['item']) || !isset($item['chances']) || !isset($item['amounts'])) { - throw new \InvalidArgumentException('Required keys(item,chances,amounts) not present with all items!'); - } elseif (!is_numeric($item['chances'])) { - throw new \UnexpectedValueException('Chances should be a positive number(integer/float)!'); - } elseif (!is_array($item['amounts'])) { - throw new \UnexpectedValueException('Amounts should be a formatted array!'); - } + foreach ($this->items as $item) { + if (!isset($item['item']) || !isset($item['chances']) || !isset($item['amounts'])) { + throw new InvalidArgumentException('Required keys(item,chances,amounts) not present with all items!'); } } - $this->check = $check; - $this->fraction = $fraction; - $this->gift($items); } /** - * @return array + * Picks an item & amount from the list based on chances. + * + * @param bool $check Indicates whether to run the check method before picking an item (default: true) + * @return array Returns an array containing the picked item and its amount + * @throws Exception */ - public function draw(): array + public function pick(bool $check = true): array { - return $this->draw; + $check && $this->check(); + $items = array_column($this->items, 'chances', 'item'); + $items = $this->prepare($items); + $pickedItem = $this->draw($items); + $amounts = $this->items[array_search($pickedItem, array_column($this->items, 'item'))]['amounts']; + is_string($amounts) && $amounts = [$this->weightedAmountRange($amounts)]; + return [ + 'item' => $pickedItem, + 'amount' => match (true) { + count($amounts) === 1 => current($amounts), + $this->isSequential($amounts) => $amounts[array_rand($amounts)], + default => $this->draw($amounts) + } + ]; } /** - * @return void + * Generates a weighted random number within a specified range. + * + * @param string $amounts A string containing the minimum, maximum, and bias values separated by commas. + * @return float|int A single randomly generated number within the specified range. + * @throws UnexpectedValueException If the amount range is invalid or the bias is less than or equal to 0. */ - private function multiply() + private function weightedAmountRange(string $amounts): float|int { - if (!$this->fraction) return; - if (($length = $this->setFraction()) === 0) return; - $this->items = array_combine( - array_keys($this->items), - array_map('bcmul', $this->items, - array_fill(0, count($this->items), - str_pad(1, $length, '0')))); + $amounts = str_getcsv($amounts); + count($amounts) !== 3 && throw new UnexpectedValueException('Invalid amount range (expected: min,max,bias).'); + [$min, $max, $bias] = $amounts; + $max <= $min && throw new UnexpectedValueException('Maximum value should be greater than minimum.'); + $bias <= 0 && throw new UnexpectedValueException('Bias should be greater than 0.'); + $min = floatval($min); + $max = floatval($max); + $bias = floatval($bias); + $selected = min( + round($min + pow(lcg_value(), $bias) * ($max - $min + 1), $this->getFractionLength([$min, $max])), + $max + ); + return max($selected, $min); } /** - * @return int + * Draws among an array of items based on given weight. + * + * @param array $items The array of items to be processed. + * @return string The selected item from the array. + * @throws Exception if the random number generation fails. */ - private function setFraction(): int + private function draw(array $items): string { - $length = 0; - foreach ($this->items as $item) { - if ((int)$item != $item) { - $fraction = strlen(explode(".", $item)[1]) + 1; - if ($fraction > $length) { - $length = $fraction; - } + if (count($items) === 1) { + return key($items); + } + + $random = random_int(1, array_sum($items)); + foreach ($items as $key => $value) { + $random -= (int)$value; + if ($random <= 0) { + return $key; } } - return (int)$length; + return array_search(max($items), $items); } /** - * @return void + * Prepares an array of items. + * + * @param array $items The array of items to be prepared. + * @return array The prepared array of items. + * @throws UnexpectedValueException */ - private function getPositive() + private function prepare(array $items): array { - $this->items = array_filter($this->items, function ($value) { - return $value > 0; - }); + if ($length = $this->getFractionLength($items)) { + $items = $this->multiply($items, $length); + } + return $items; } /** - * @param $array - * @return bool + * Calculate the length of the fraction part in an array of items. + * + * @param array $items The array of items to calculate the fraction length from. + * @return int The length of the fraction part. + * @throws UnexpectedValueException */ - private function numSequence($array): bool + private function getFractionLength(array $items): int { - if (!array_key_exists(0, $array)) return false; - return array_keys($array) === range(0, count($array) - 1); + $length = 0; + foreach ($items as $item) { + $item > 0 || throw new UnexpectedValueException('Chances should be positive decimal number!'); + $fraction = strpos($item, '.'); + $fraction && $length = max(strlen($item) - $fraction - 1, $length); + } + return (int)$length; } /** - * @param $items - * @return void + * Multiplies each item in the given array by a specified decimal length. + * + * @param array $items The array of items to be multiplied. + * @param int $length The length to multiply each item by. + * @return array The resulting array with each item multiplied by the specified length. */ - private function gift($items) + private function multiply(array $items, int $length): array { - $this->items = array_column($items, 'chances', 'item'); - $item = $this->generate(); - $amounts = $items[array_search($item, array_column($items, 'item'))]['amounts']; - if (count($amounts) == 1) { - $count = current($amounts); - } elseif ($this->numSequence($amounts)) { - $count = $amounts[rand(0, count($amounts) - 1)]; - } else { - $this->items = $amounts; - $count = $this->generate(); - } - $this->draw = [$item, $count]; + $padding = '1' . str_repeat('0', $length); + return array_map(function ($value) use ($padding) { + return bcmul($value, $padding); + }, $items); } /** - * @return false|int|mixed|string + * Determines if an array is sequential. + * + * @param array $array The array to check. + * @return bool Returns true if the array is sequential, false otherwise. */ - private function generate() + private function isSequential(array $array): bool { - if (count($this->items) == 1) return current($this->items); - $this->getPositive(); - $this->multiply(); - $sum = array_sum($this->items); - if ($sum > mt_getrandmax() || $sum < 1) { - if (!$this->check) { - return false; - } - throw new \UnexpectedValueException('Chances(Item/Amount) out of range!'); - } - $rand = mt_rand(1, (int)$sum); - foreach ($this->items as $key => $value) { - $rand -= (int)$value; - if ($rand <= 0) { - return $key; - } + if (!array_key_exists(0, $array)) { + return false; } - return array_search(max($this->items), $this->items); + $keys = array_keys($array); + return $keys === array_keys($keys); } } diff --git a/src/Draw/MegaDraw.php b/src/Draw/MegaDraw.php deleted file mode 100644 index 69803f0..0000000 --- a/src/Draw/MegaDraw.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @copyright Copyright (c), 2019 A. B. M. Mahmudul Hasan - * @license MIT public license - */ -class MegaDraw -{ - /** - * @param array $items the list of items - * @param array $users - * @param bool $grouped - * @return array with Item Code/Name and Item Counter - * @exception If required keys not present/values for the keys are not properly formatted - */ - public function get(array $items, array $users, bool $grouped = false): array - { - if (count($items) < 1) { - throw new \LengthException('Invalid number of items!'); - } - if (!self::intValue($items) || !self::positiveValue($items)) { - throw new \UnexpectedValueException('Prize quantity should be a positive integer value!'); - } - if ($grouped) return self::groupedGift($items, $users); - else return self::generalGift($items, $users); - } - - private static function intValue(array $array): bool - { - return $array === array_filter($array, 'is_int'); - } - - private static function positiveValue(array $array): bool - { - return min($array) >= 0; - } - - private static function generalGift($items, $users): array - { - $users = self::select($users, array_sum($items)); - $select = array(); - foreach ($items as $k => $v) { - $select[$k] = array_splice($users, 0, $v); - } - return $select; - } - - private static function groupedGift($items, $users): array - { - $users = self::groupBase($users); - $gift_array = []; - foreach ($users as $gift => $userGroup) { - $gift_array[$gift] = self::select($userGroup, $items[$gift]); - } - return $gift_array; - } - - private static function groupBase($users): array - { - $group = array(); - foreach ($users as $user => $gift) { - $group[$gift][] = $user; - } - return $group; - } - - private static function select($users, $total): array - { - $select = array(); - for ($i = 0; $i < $total; $i++) { - $offset = rand(0, count($users) - 1); - if (!isset($users[$offset])) { - break; - } - $select[] = $users[$offset]; - array_splice($users, $offset, 1); - } - return $select; - } -}