Closed 6562680 closed 4 months ago
This is a "feature" of PHP that has been deprecated in 8.3 and will be removed (become an error) in 9.0.
https://www.php.net/manual/en/language.operators.increment.php#language.operators.increment.string https://wiki.php.net/rfc/saner-inc-dec-operators
Ok, just put it here:
<?php
/**
* _itertools_range(0,2) -> 0 1 2
* _itertools_range(2,0,-1) -> 2 1 0
*
* @param int $start
* @param int|null $end
* @param int|null $step
*
* @return \Generator
*/
function _itertools_range($start, $end, $step = null) : \Generator
{
$step = $step ?? 1;
if (! (($isStringStart = is_string($start)) || is_int($start) || is_float($start))) {
throw new \LogicException('The `start` should be int|float|string');
}
if (! (($isStringEnd = is_string($end)) || is_int($end) || is_float($end))) {
throw new \LogicException('The `end` should be int|float|string');
}
if (! (($isFloatStep = is_float($step)) || is_int($step))) {
throw new \LogicException('The `step` should be int|float');
}
$_step = $step;
$intStep = (int) $step;
$floatStep = (float) $step;
if ($floatStep === (float) $intStep) {
$_step = $intStep;
$isFloatStep = false;
}
if (! $_step) {
return;
}
$isModeString = ($isStringStart || $isStringEnd);
if ($isModeString && $isFloatStep) {
throw new \LogicException('The `step` should be int if `start` or `end` are strings');
}
$_start = $start;
$_end = $end;
if ($isModeString) {
if (! $isStringStart) {
$_start = (string) $_start;
}
if (! $isStringEnd) {
$_end = (string) $_end;
}
} else {
$intStart = (int) $start;
$intEnd = (int) $end;
$floatStart = (float) $start;
$floatEnd = (float) $end;
if ($floatStart === (float) $intStart) {
$_start = $intStart;
} else {
$_start = $floatStart;
}
if ($floatEnd === (float) $intEnd) {
$_end = $intEnd;
} else {
$_end = $floatEnd;
}
}
$isReverse = $_step < 0;
$i = $_start;
while (
false
|| ($isReverse && ($i >= $_end))
|| ($i <= $_end)
) {
yield $i;
if ($isModeString) {
if (false
|| ($isReverse && ($i === $_start))
|| ($i === $_end)
) {
break;
}
for ( $ii = 0; $ii < $_step; $ii++ ) {
if ($isReverse) {
$i--;
} else {
$i++;
}
}
} else {
$i += $_step;
}
}
}
foreach (_itertools_range('aa', 'zz') as $v) {
var_dump($v);
}
Output:
string(2) "aa"
string(2) "ab"
string(2) "ac"
string(2) "ad"
string(2) "ae"
string(2) "af"
string(2) "ag"
string(2) "ah"
string(2) "ai"
string(2) "aj"
...
string(2) "zz"
Common use case - product, product_repeat, combinations_unique, combinations_all, permutations
Most common practical IRL use case: you want to get decbin() for all cases until int $n
-> '00', '01', '10', '11'
It is _product_repeat(2, range(0, 1))
or for cycle with base_convert...
_product_repeat
internally use _product
that is internally use _range
We had just get in generators era
getting AI bots, that is done with generators. Skipping generator based stuff in next PHP could be a mistake...
Few days ago i've received comment in some of PHP chats... "Your speaking about generators show us that you're a coder that had 1 year of experience, because EVERYBODY knows that they're useless and the one-time toy".
It exactly explains that people didn't even understand that any ASYNC/PARALLEL operations could be realized with generators (yield callable and arguments) and event-loop (process manager).
Just
Both promise and pipeline is callables too. Actually - it has method call() that is loop with call_user_func(), and callAsync() that is Generator yields [ callable, args ] and awaiting back the [ result ]. But, it means you can create promise of pipelines or create pipeline of promises.
Await() function iterates over generator in current process (preventing yield to the above loop) delegating call to the ProcessorInterface, that is in simpliest case just call call_user_func_array().
Async() function returns yield of the passed callable and given arguments instead of doing call_user_func(). Actually is a function that return generator that takes function name and given args, yields it and awaits result to $gen->send();
Loop() function iterates over generator and run process for each yielded callable. In that process it delegates call to the ProcessorInterface like await()
function. As in any daemon-like
application tickrate is needed like usleep(1)
giving time to processor to handle CPU health. Also it limites the count of processes to N using simple array with N cells and stores process PIDs. Our process manager
is wait until cell process is finish, free the cell, then start new task. Exactly because promise-pipeline stuff parallel things goes parallel, serial things goes serial, and our loop should dont care about it. Main point is - HOLDING DATA INSIDE our "callable", state-managment
, self-contain callable...
Lambda/closure functions could be serialized as passed uses and code file/line pointer, inside one loop code (war?) never changes.
Async timer is just checking microtime() > timerExpiredAt
in infinite loop... So its "very fast" action instead of "blocking action". Any function could be "non-blocking" if you return its name and arguments to later call via process manager instead of call it immediate inside current process.
Its possible in any OS... And could be extended with pcntl_fork in linux to be more faster.
@damianwadley up
I don't understand what your commentary has to do with the reported issue: 'z'++
increments to 'aa'
which is still <= 'z'
and thus the loop does not end there, and this ability to increment letter strings will be removed in the future.
My reply is about "do not remove iterators "features" because in next 10 years iterarors could replace any task you want to design.
Description
The following code:
Resulted in this output:
But I expected this output instead:
But:
Resulted in this output:
PHP Version
PHP 8.3
Operating System
No response