Страницы

Поиск по вопросам

Показаны сообщения с ярлыком php7. Показать все сообщения
Показаны сообщения с ярлыком php7. Показать все сообщения

понедельник, 24 февраля 2020 г.

Как выделить диапазоны чисел из массива?

#массивы #php7


Мучаюсь уже час, не получается выделить диапазоны из массива.
Допустим, есть массив [0,1,2,3,5,6,7,9,13,20]. На выходе хочу получить строку 0-3,5-7,9,13,20.
Пробовал циклом проходить по массиву и с помощью next и prev заменять числа на тире
или добавлять запятую, но выходит какая-то фигня. Подскажите, наверняка есть простые
алгоритмы для этой задачи или хелпер в Yii2?
    


Ответы

Ответ 1



А вот примитивненько через цикл, просто на php :) function array_to_string($arr) { sort($arr, SORT_NUMERIC); $kk = count($arr); $result = ''; for ($i = 0; $i <= $kk; $i++) { if (isset($arr[$i])) { if (!count($a)) { $a[] = $arr[$i]; continue; } if ($arr[$i] == end($a) + 1) { $a[] = $arr[$i]; continue; } } if (count($a) < 3) { $result .= implode(',', $a) . ','; } else { $result .= reset($a) . '-'. end($a) . ','; } if (isset($arr[$i])) { $a = [$arr[$i]]; } } return trim($result, ','); } echo array_to_string([0,1,2,3,5,6,7,9,13,20]) . "
\n"; echo array_to_string([0,1,3,5,6,7,8,9,10,11,12,13,20,21]) . "
\n"; Результат 0-3,5-7,9,13,20 0,1,3,5-13,20,21

Ответ 2



Первый вариант: $myArray = [0,1,2,3,5,6,7,9,13,20]; //last value is dropped so add something useless to be dropped array_push($myArray, null); $rangeArray = array(); array_walk($myArray, function($val) use (&$rangeArray){ static $oldVal, $rangeStart; if (is_null($rangeStart)) goto init; if ($oldVal+1 == $val) { $oldVal = $val; return; } if ($oldVal == $rangeStart) { array_push($rangeArray, $rangeStart); goto init; } array_push($rangeArray, $rangeStart . '-' . $oldVal); init: { $rangeStart = $val; $oldVal = $val; } }); echo '
'.print_r($rangeArray, true).'
'; Второй вариант: $numbers = [0,1,2,3,5,6,7,9,13,20]; $ranges[] = array($numbers[0],$numbers[0]); // initial value foreach ($numbers as $number) { $range = array_pop($ranges); $extend = ($range[1] == $number-1); $ranges[] = array($range[0],$extend ? $number : $range[1]); if (!$extend) $ranges[] = array($number,$number); } echo '
'.print_r($ranges,TRUE).'
'; будет содержать массив такого вида: Array ( [0] => Array ( [0] => 0 [1] => 0 ) [1] => Array ( [0] => 0 [1] => 3 ) [2] => Array ( [0] => 5 [1] => 7 ) [3] => Array ( [0] => 9 [1] => 9 ) [4] => Array ( [0] => 13 [1] => 13 ) [5] => Array ( [0] => 20 [1] => 20 ) ) то есть из него видно, что если элемент в ячейке 0 и 1 одинаковый - значит он уникальный, если разные - значит это диапазон. Отфильтровать это можно потом так: foreach ($ranges as $range) { $output[] = ($range[0] == $range[1]) ? $range[0] : $range[0].'-'.$range[1]; } echo '
'.print_r($output,TRUE).'
'; будет уже выводить Array ( [0] => 0-3 [1] => 5-7 [2] => 9 [3] => 13 [4] => 20 ) Примеры взяты из https://codereview.stackexchange.com/questions/80080/aggregate-array-values-into-ranges Там же можно посмотреть еще парочку примеров.

Ответ 3



Вероятно, есть более оптимальное решение, но на скорую руку получился вполне годный код. Комментариев больше написал, чем кода... //упорядоченный входной массив $arr = [0,2,3,5,6,7,9,13,15,16,17,20]; //сюда будем складывать ranges: ["0","2-3","5-7",...] $ranges = []; //а в этой переменной будем хранить текущий range: [from, to] $currentRange = [$arr[0],$arr[0]]; //перебираем со второго элемента и до +последнего for($i = 1; $i <= count($arr); $i++){ //на последней итерации, когда $i > count($arr), просто закроем последний range if(!isset($arr[$i])) { $ranges[] = $currentRange[0] !== $currentRange[1] ? implode('-', $currentRange) : $currentRange[0]; } else { //если последний элемент текущего range равен "текущее значение - 1" - продляем range на текущий элемент if ($currentRange[1] === intval($arr[$i]) - 1) { $currentRange[1] = $arr[$i]; } else { //закрываем текущий range (скидываем в ranges новую строку) $ranges[] = $currentRange[0] !== $currentRange[1] ? implode('-', $currentRange) : $currentRange[0]; //открываем новый range с текущего места $currentRange = [$arr[$i], $arr[$i]]; } } } //склеиваем результирующий массив в строку через запятую $resultStr = implode(',', $ranges);

пятница, 14 февраля 2020 г.

Перевод текста в двоичный код PHP

#php #php7 #системы_счисления


Как можно перевести текст в двоичный код?

Пробовал так, он переводит, но как то криво, в отличии от decbin:

$x = "abc12345";
$x = base_convert($x, 16, 2);
echo $x;




И если decbin работает как надо, почему base_convert работает непонятно как?
на официальной странице есть пример показывающий работу 

Пример #1 Пример использования base_convert()




Результат выполнения данного примера:

101000110111001100110100


Сравнил две функции:

";
$y = "abc12345";
$y = base_convert($y, 16, 2);
echo $y;
?>


Результат выполнения:

110000111000101100011110001110010110011110100110101

10101011110000010010001101000101


От чего такая разница? 


  base_convert — Преобразовывает числа между произвольными системами счисления
  
  decbin — Переводит число из десятичной системы счисления в двоичную
  
  ord - Возвращает ASCII код символа


Самое интересно в том что если сделать так 




Результат будет одинаковым:

110000111000101100011110001110010110011110100110101
110000111000101100011110001110010110011110100110101

    


Ответы

Ответ 1



base_convert — Преобразовывает числа между произвольными системами счисления abc12345 - это корректное шестнадцатеричное число из 4 байт, эквивалент 2881561413 в десятичной системе счисления. В то же время циклом с вызовом ord по строке вы обрабатываете значение не как число, а как массив строковых байт. Так, символ a - это байт \x61 (но только для ASCII-совместимых кодировок! Может быть иначе). Затем вы некорректно собираете результирующую строку, теряя ведущие нули результата. И потому получаете последовательность из некорректных 51 бит вместо 64: for ($i = 0; $i < strlen($x); $i++) { echo str_pad(decbin(ord($x[$i])), 8, '0', STR_PAD_LEFT); } 0110000101100010011000110011000100110010001100110011010000110101 Почему 64 бита? Потому что в ASCII строке abc12345 всего 8 байт, каждый символ закодирован значением в 8 бит. Но в 16-ричном числе abc12345 только 4 байта, потому что одна 16-ричная цифра кодирует 4 бита информации. Вы преобразуете на самом деле разные исходные данные и потому получаете различный результат. Вот такое малое различие, которое может быть непонятно для языка с мягкой динамической типизацией, но крайне важное.

Ответ 2



$x = "abc12345"; for ($i = 0; $i < strlen($x); $i++) { echo decbin(ord($x[$i])) . "\n"; }

суббота, 8 февраля 2020 г.

PHP: Использовать переменную как тип для приведения к нужному типу

#php #php7


Нужно что-то вроде этого:

$type = 'double';
$text = '0.90000000000002';
$double = ($type)$text;


Т.е. тип, к которому будет приведена переменная, зависит от $type.

И нет, я не хотел бы использовать if/switch.
    


Ответы

Ответ 1



Воспользуйтесь функцией bool settype(mixed &$var, string $type) $type = 'double'; $text = '0.90000000000002'; settype($text, $type);

Ответ 2



Как преобразование типов вероятней всего не получится. Но можно попробовать использовать функции *val $type = 'double'; $text = '0.90000000000002'; $func = $type . "val"; $double = $func($text);

пятница, 31 января 2020 г.

Ошибка после смены версии PHP

#php #php7


После смены версии PHP с 5.4 на 7.0 появилась ошибка:


  Deprecated: Methods with the same name as their class will not be
  constructors in a future version of PHP; Browser has a deprecated
  constructor in /home/u408663914/public_html/core/class/browser.php on
  line 135


Помогите, пожалуйста, исправить!


  Строка 135: class Browser {


Весь код файла: http://ideone.com/CcefNk
    


Ответы

Ответ 1



У вас в классе есть метод с таким же названием как и класс: public function Browser($useragent="") { $this->reset(); if( $useragent != "" ) { $this->setUserAgent($useragent); } else { $this->determine(); } } Уже давно так не рекомендуют делать, вместо этого используйте магический метод __construct: public function __construct($useragent="") { $this->reset(); if( $useragent != "" ) { $this->setUserAgent($useragent); } else { $this->determine(); } } UPD. Как верно подметил @xEdelweiss если используете сторонюю библиотеку, то лучше обновите её, вместо того чтобы самому менять исходный код.

Ответ 2



Вам следует использовать более новую версию библиотеки, которая к тому же умеет определять и делать то, что ваша - устаревшая - не умеет. Скачать новую версию можно здесь.

php математические операции высокой точности

#php #php7


При работе с числами на php вылазит проблема с точностью.
Например:

0.1 - 0.000023 = 0.09997700000000001


Пошарил по разным закаулкам интернета и везде предлагают разные решения:

round();
sprintf();
number_format();
и много других библиотек и функции


Собственно прошу совета, какой метод наиболее правильный и который будет всегда работать
с максимальной точностью
    


Ответы

Ответ 1



В документации по этому поводу есть отсылка к математическим вычисления произвольной точности:(в блоке "внимание") http://php.net/manual/ru/language.types.float.php

Ответ 2



Советую использовать библиотеку BCMath для вычисления высокой точности. Например: $a = 0.1; $b = 0.00023; echo bcsub($a, $b, 10); Третий необязательный параметр используется для установки количества знаков после десятичного разделителя в результате.

суббота, 7 декабря 2019 г.

Как исправить или убрать ошибки вида “Declaration of … should be compatible with”, появившиеся с переходом на PHP 7

#php7


С переходом на PHP 7 лог ошибок по многим старым проектам оказался забит под завязку
сообщениями подобного вида:

PHP Warning: Declaration of Example::do(Foo $a, Bar $b, $c = 0) should 
  be compatible with ExampleParent::do($c = null) in Example.php on line 22548


Это только один пример ошибки. Вариантов таких ошибок может быть множество.

Усложняют проблему следующие моменты:


Код проекта - бесконечный. Покрытие тестами - нулевое. Отрефакторить и исправить
все ошибки займет приличное время. Никто не оплатит эту работу. Короче, исправлять
все эти ошибки за исключением совсем уж простых - не вариант.
Можно было бы вообще отключить все предупреждения, но тогда есть риск пропустить
какие-то действительно важные ошибки. Отключать предупреждения совсем - не вариант.
Не хотелось бы терять все те улучшения в части скорости и стабильности, которые есть
в PHP 7. Поддержка PHP 5 уже считай что закончилась, значит переходить на PHP 7 так
или иначе будет нужно. Возвращаться на PHP 5 - не вариант.


С учётом вышесказанного, как можно освободить логи сервера только от этих ошибок?
    


Ответы

Ответ 1



Если не засовывать голову в песок, что есть сил стараясь не трогать старый код, которые пока еще работает, то есть ряд случаев когда исправления могут быть внесены либо просто, либо выполнимо. В примерах ниже класс B является подклассом для A. Примеры ниже не обязательно устранят принципиальные нарушения принципа подстановки (LSP), но хотя бы PHP не будет о них ругаться. Особой простой случай когда у метода подкласса есть новый аргумент, у которого нет умолчального значения. Просто добавляете значение по умолчанию и живете дальше. Пример: Declaration of B::foo() should be compatible with A::foo($bar = null) Вы сделаете: - public function foo() + public function foo($bar = null) Если в методе подкласса добавляются ограничения, то следует убрать их из сигнатуры метода, перенеся в тело функции. Declaration of B::add(Baz $baz) should be compatible with A::add($n) Вы захотите использовать assert или кидать исключение, в зависимости от серьезности ошибки. - public function add(Baz $baz) + public function add($baz) { + assert($baz instanceof Baz); Если вы видите что ограничения в сигнатуре метода используются лишь в целях документации, то перенесите их туда, где документируются параметры метода, то есть в комментарий. - protected function setValue(Baz $baz) + /** + * @param Baz $baz + */ + protected function setValue($baz) { + /** @var $baz Baz */ Если в методе подкласса меньше аргументов, чем в суперклассе, и вы можете сделать аргументы опциональными в суперклассе, достаточно будет заменить неиспользуемые аргументы заглушками. Declaration of B::foo($param = '') should be compatible with A::foo($x = 40, $y = '') Вы сделаете: - public function foo($param = '') + public function foo($param = '', $_ = null) Если вы видите что какие-то аргументы в подкласс стали обязательными, уберите требование обязательности из сигнатуры метода в тело метода. - protected function foo($bar) + protected function foo($bar = null) { + if (empty($bar['key'])) { + throw new Exception("Invalid argument"); + } Иногда может быть проще изменить метод суперкласса, удалив опциональный аргументы, перейдя на использование магии func_get_args. Не забудьте задокументировать этот виртуальный аргумент. /** + * @param callable $bar */ - public function getFoo($bar = false) + public function getFoo() { + if (func_num_args() && $bar = func_get_arg(0)) { + // go on with $bar Понятно что убрать больше чем один аргумент таким образом может быть сложно. Все гораздо интересней если у вас есть серьезные нарушения принципа Лисковой. Если ваши аргументы не требуют типа, все просто. Достаточно сделать все аргументы опциональными, затем вручную проверяя их наличие. С такой ошибкой: Declaration of B::save($key, $value) should be compatible with A::save($foo = NULL) Вы сделаете: - public function save($key, $value) + public function save($key = null, $value = null) { + if (func_num_args() < 2) { + throw new Exception("Required argument missing"); + } Обратите внимание что мы не можем использовать функцию func_get_args() в этом случае потому что эта функция не учитывает аргументы со значениями по умолчанию. Их просто не будет в возвращенном значении. Если у вас есть целые семейства классов с сильно отличным интерфейсом, может иметь смысл изменить их еще больше. Переименуем функцию, нарушающую принцип подстановки. Потом добавьте метод-прокси для вызова нового, переименованного, метода в едином для всей иерархии отклоняющихся классов подклассе. function save($arg = null) // соответствует родителю { $args = func_get_args(); return $this->saveExtra(...$args); // отличный интерфейс } Таким образом вы не устраните нарушение принципа подстановки (новые классы все также нельзя вызывать будто они старые), но вы сохраните всевозможные проверки на типы данных, которые есть в сигнатурах функций.

Ответ 2



Такие ошибки как были предусмотрены с PHP 5.0 и самого появления type hinting в PHP так и остались. Единственное что сначала уровень E_STRICT не входил в E_ALL и должен был быть указан явно, затем начиная с PHP 5.4 начал входить. После этого в PHP 7.0 ошибки из E_STRICT перераспределили по другим типам ошибок. Сообщество сошлось на мнении, что нарушение контракта базового класса - вещь плохая и должна быть заметна, потому присвоили этой ошибке уровень E_WARNING. Жаль, конечно, что в 2004 году изменили изначальную реализацию этой проверки, где такой тип ошибок генерировал вовсе E_COMPILE_ERROR. Ну кто же виноват, что ни вы ни ваши коллеги по проекту за столько лет ни разу не послушали, что говорит ближайший друг разработчика - компилятор вашего языка программирования. Что делать дальше: исправлять. Рефакторить такие грубые ошибки будет долго и это говорит об общем качестве кода. Наверняка даже E_NOTICE не включали. игнорировать ошибки в логе прятать голову в песок и прятать ошибки Настроек "такие варнинги выводи, а такие нет" - к счастью нет. Конкретную описанную в вопросе ошибку можно перенести в рантайм. В базовом классе указан один параметр без типа с дефолтным значением, дочерний класс может принимать дополнительные необязательные аргументы метода и это не является нарушением контракта класса. class ExampleParent { public function dosome ($c = null) {} } class Example extends ExampleParent { /* * переименовываем существующий метод * в будущем коде используем его вместо dosome */ public function readablenamemethod(Foo $a, Bar $b, $c = 0) { } /** * на его место ставим заглушку и вручную проверяем аргументы на совместимость * @deprecated */ public function dosome ($a = null, $b = null, $c = null) { if ($a instanceof Foo and $b instanceof Bar) { // на такой набор параметров мы хотим реагировать return $this->readablenamemethod($a, $b, $c ?? 0); } throw new \InvalidArgumentException('wrong arguments given'); } } Если же базовый класс требует какой-то класс аргументом, а дочерний в этом месте хочет скаляр или совсем другой объект - то меняйте контракт базового класса с type hinting на проверку в рантайме.

Ответ 3



Не всегда возможно исправить старый код, который вы, к тому же, не писали. Например, берёте какую-нибудь библиотеку из PEAR и наслаждаетесь. Про код, не знавший тестов, просто молчу. Потому можно сделать так: if (PHP_MAJOR_VERSION >= 7) { set_error_handler(function ($errno, $errstr) { return strpos($errstr, 'Declaration of') === 0; }, E_WARNING); } Этот обработчик ошибок помечает предупреждения как обработанные, возвращая истину для всех сообщений, начинающихся с указанной строки. Такие обработанные преду­преж­де­ния не пойдут на запись в лог. Код будет работать только в PHP 7 и более поздних. Если же проблема возникает только в какой-то части кода, которую можно выделить по ката­логу или путям до файлов, то можно глушить эти ошибки только для определённых файлов: set_error_handler(function ($errno, $errstr, $file) { return strpos($file, 'path/to/legacy/library') !== false && strpos($errstr, 'Declaration of') === 0; }, E_WARNING);

понедельник, 8 апреля 2019 г.

Ошибка после смены версии PHP

После смены версии PHP с 5.4 на 7.0 появилась ошибка:
Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Browser has a deprecated constructor in /home/u408663914/public_html/core/class/browser.php on line 135
Помогите, пожалуйста, исправить!
Строка 135: class Browser {
Весь код файла: http://ideone.com/CcefNk


Ответ

У вас в классе есть метод с таким же названием как и класс:
public function Browser($useragent="") { $this->reset(); if( $useragent != "" ) { $this->setUserAgent($useragent); } else { $this->determine(); } }
Уже давно так не рекомендуют делать, вместо этого используйте магический метод __construct
public function __construct($useragent="") { $this->reset(); if( $useragent != "" ) { $this->setUserAgent($useragent); } else { $this->determine(); } }
UPD. Как верно подметил @xEdelweiss если используете сторонюю библиотеку, то лучше обновите её, вместо того чтобы самому менять исходный код.

понедельник, 15 октября 2018 г.

Как исправить или убрать ошибки вида “Declaration of … should be compatible with”, появившиеся с переходом на PHP 7

С переходом на PHP 7 лог ошибок по многим старым проектам оказался забит под завязку сообщениями подобного вида:
PHP Warning: Declaration of Example::do(Foo $a, Bar $b, $c = 0) should be compatible with ExampleParent::do($c = null) in Example.php on line 22548
Это только один пример ошибки. Вариантов таких ошибок может быть множество.
Усложняют проблему следующие моменты:
Код проекта - бесконечный. Покрытие тестами - нулевое. Отрефакторить и исправить все ошибки займет приличное время. Никто не оплатит эту работу. Короче, исправлять все эти ошибки за исключением совсем уж простых - не вариант. Можно было бы вообще отключить все предупреждения, но тогда есть риск пропустить какие-то действительно важные ошибки. Отключать предупреждения совсем - не вариант. Не хотелось бы терять все те улучшения в части скорости и стабильности, которые есть в PHP 7. Поддержка PHP 5 уже считай что закончилась, значит переходить на PHP 7 так или иначе будет нужно. Возвращаться на PHP 5 - не вариант.
С учётом вышесказанного, как можно освободить логи сервера только от этих ошибок?


Ответ

Если не засовывать голову в песок, что есть сил стараясь не трогать старый код, которые пока еще работает, то есть ряд случаев когда исправления могут быть внесены либо просто, либо выполнимо. В примерах ниже класс B является подклассом для A. Примеры ниже не обязательно устранят принципиальные нарушения принципа подстановки (LSP), но хотя бы PHP не будет о них ругаться.
Особой простой случай когда у метода подкласса есть новый аргумент, у которого нет умолчального значения. Просто добавляете значение по умолчанию и живете дальше. Пример:
Declaration of B::foo() should be compatible with A::foo($bar = null)
Вы сделаете:
- public function foo() + public function foo($bar = null) Если в методе подкласса добавляются ограничения, то следует убрать их из сигнатуры метода, перенеся в тело функции.
Declaration of B::add(Baz $baz) should be compatible with A::add($n)
Вы захотите использовать assert или кидать исключение, в зависимости от серьезности ошибки.
- public function add(Baz $baz) + public function add($baz) { + assert($baz instanceof Baz);
Если вы видите что ограничения в сигнатуре метода используются лишь в целях документации, то перенесите их туда, где документируются параметры метода, то есть в комментарий.
- protected function setValue(Baz $baz) + /** + * @param Baz $baz + */ + protected function setValue($baz) { + /** @var $baz Baz */ Если в методе подкласса меньше аргументов, чем в суперклассе, и вы можете сделать аргументы опциональными в суперклассе, достаточно будет заменить неиспользуемые аргументы заглушками.
Declaration of B::foo($param = '') should be compatible with A::foo($x = 40, $y = '')
Вы сделаете:
- public function foo($param = '') + public function foo($param = '', $_ = null) Если вы видите что какие-то аргументы в подкласс стали обязательными, уберите требование обязательности из сигнатуры метода в тело метода.
- protected function foo($bar) + protected function foo($bar = null) { + if (empty($bar['key'])) { + throw new Exception("Invalid argument"); + } Иногда может быть проще изменить метод суперкласса, удалив опциональный аргументы, перейдя на использование магии func_get_args. Не забудьте задокументировать этот виртуальный аргумент.
/** + * @param callable $bar */ - public function getFoo($bar = false) + public function getFoo() { + if (func_num_args() && $bar = func_get_arg(0)) { + // go on with $bar
Понятно что убрать больше чем один аргумент таким образом может быть сложно. Все гораздо интересней если у вас есть серьезные нарушения принципа Лисковой. Если ваши аргументы не требуют типа, все просто. Достаточно сделать все аргументы опциональными, затем вручную проверяя их наличие. С такой ошибкой:
Declaration of B::save($key, $value) should be compatible with A::save($foo = NULL)
Вы сделаете:
- public function save($key, $value) + public function save($key = null, $value = null) { + if (func_num_args() < 2) { + throw new Exception("Required argument missing"); + }
Обратите внимание что мы не можем использовать функцию func_get_args() в этом случае потому что эта функция не учитывает аргументы со значениями по умолчанию. Их просто не будет в возвращенном значении. Если у вас есть целые семейства классов с сильно отличным интерфейсом, может иметь смысл изменить их еще больше. Переименуем функцию, нарушающую принцип подстановки. Потом добавьте метод-прокси для вызова нового, переименованного, метода в едином для всей иерархии отклоняющихся классов подклассе.
function save($arg = null) // соответствует родителю { $args = func_get_args(); return $this->saveExtra(...$args); // отличный интерфейс }
Таким образом вы не устраните нарушение принципа подстановки (новые классы все также нельзя вызывать будто они старые), но вы сохраните всевозможные проверки на типы данных, которые есть в сигнатурах функций.