#php #arrays #binning
#php #массивы #биннинг
Вопрос:
Я пишу скрипт для равномерного распределения произвольного числа $epi
в произвольное количество ячеек $dpi
. epi означает концы на дюйм. dpi означает вмятины на дюйм. Есть 3 требования:
- количество ячеек должно быть уменьшено на наименьший общий коэффициент, если это возможно
- например, 10 epi в 6 dpi должны быть представлены 5 epi в 3 dpi
- заполнение ячеек должно быть как можно более равномерным
- например, 2-2-1 лучше, чем 3-1-1
- короткие ячейки должны быть равномерно распределены по массиву ячеек
- например, 1-0-1-0-1 лучше, чем 1-1-1-0-0
Это то, что у меня есть до сих пор. В основном он делает то, что мне нужно, но при запуске space()
метода, если его foreach
цикл должен выполняться более одного раза, распределение $ epi неравномерно.
<?php
class ReedSubstitution{
public $epi;
public $dpi;
function substitute($e,$d){
$this->epi=is_numeric($e)?$e:0;
$this->dpi=is_numeric($d)?$d:0;
if(empty($this->epi)||empty($this->dpi)) throw new Exception('Both desired ends per unit and available dents per unit must be specified.');
if($this->epi%$this->dpi ==0) return array($this->epi/$this->dpi);//short circuit for easy case
$this->unfraction();//make equivalent integers if dpi or epi are fractional
$gcd= ($this->epi < $this->dpi) ? $this->euclid($this->epi,$this->dpi) : $this->euclid($this->dpi,$this->epi);
$e=$this->epi/$gcd;
$d=$this->dpi/$gcd;
$q=floor($e/$d);//count that every dent gets
$r=$e%$d;//extra to be spread out over array
$reed=array_fill(0,$d,$q);
$this->space($reed,$r);
return $reed;
}
protected function unfraction(){
$emult=1;
$dmult=1;
if($this->epi-intval($this->epi)){ //epi has a fractional component
list($tmp,$er)=explode('.',$this->epi);
$emult=pow(10,strlen($er));
}
if($this->dpi-intval($this->dpi)){//dpi has a fractional component
list($tmp,$dr)=explode('.',$this->dpi);
$dmult=pow(10,strlen($dr));
}
$mult=($emult>$dmult)?$emult:$dmult;
$this->epi*=$mult;
$this->dpi*=$mult;
}
/**
* @desc evenly distribute remaining ends over entirety of dents
* @param Array $reed, dents in one pattern repeat
* @param Integer $r, remaining ends to be distributed
*/
protected function space(amp;$reed,$r){
$c=count($reed);
$i=0;
$jump=($r < $c-$r) ? $r : $c-$r;//use the smallest jump possible to reduce the looping
do{
for($i; $i<$c; $i=$i $jump, $r--){
if($r==0)break;
$reed[$i] ;
}
$i=array_search(min($reed),$reed);//begin next loop (if necessary) at position with fewest ends
}while($r>0);
}
/**
* @desc return greatest common divisor as determined by Euclid's algorithm
* @param integer $large
* @param integer $small
* @return integer
*/
protected function euclid($large, $small){
$modulus=$large%$small;
if($modulus==0) {
return $small;
} else if($modulus==1){
return 1;
} else {
return $this->euclid($small,$modulus);//recursion
}
}
}
?>
Плохой вывод:
$r=new ReedSubstitution();
$arr=$r->substitute(9,28);
/* BAD DISTRIBUTION
Array
(
[0] => 1
[1] => 1
[2] => 1
[3] => 0
[4] => 0
[5] => 0
[6] => 0
[7] => 0
[8] => 0
[9] => 1
[10] => 1
[11] => 1
[12] => 0
[13] => 0
[14] => 0
[15] => 0
[16] => 0
[17] => 0
[18] => 1
[19] => 1
[20] => 0
[21] => 0
[22] => 0
[23] => 0
[24] => 0
[25] => 0
[26] => 0
[27] => 1
)
*/
Как должно выглядеть приведенное выше распределение:
/* GOOD DISTRIBUTION
Array
(
[0] => 1
[1] => 0
[2] => 0
[3] => 1
[4] => 0
[5] => 0
[6] => 1
[7] => 0
[8] => 0
[9] => 1
[10] => 0
[11] => 0
[12] => 1
[13] => 0
[14] => 0
[15] => 1
[16] => 0
[17] => 0
[18] => 1
[19] => 0
[20] => 0
[21] => 1
[22] => 0
[23] => 0
[24] => 1
[25] => 0
[26] => 0
[27] => 0
)
*/
Как я могу исправить метод, чтобы объединение, требующее более одного цикла, приводило к приемлемому распределению? space()
Комментарии:
1. @Jonathan Chan: Это не домашнее задание. Я работаю над веб-сайтом для ткацкой группы. Им нужен виджет подстановки рида, поэтому им не нужно использовать диаграммы, подобные этим: joowl.com/reedsubs.html Я мог бы просто сохранить диаграмму в базе данных и извлекать записи, но мне кажется, что это должна быть решаемая математическая задача.
2. для тех, кто отклонил мой вопрос, объяснение было бы оценено. Я думаю, что вопрос ясен, и есть достаточно кода и выходных данных, чтобы читатели могли увидеть проблему, с которой я столкнулся.
Ответ №1:
Я надеюсь, что это поможет или, по крайней мере, укажет вам правильное направление:
protected function space(amp;$reed, $r)
{
$totalReeds = count($reed);
$f = floor($r/$totalReeds);
$d = ($r % $totalReeds) / $totalReeds;
$c = 0;
for ($i = 0; $i < $totalReeds; $i )
{
$add = round($f $d $c);
$reed[$i] = $add;
$c = $c $f $d - $add;
}
}
Однако это может привести не совсем к тому, что вы могли бы ожидать:
Array
(
[0] => 2
[1] => 1
[2] => 2
[3] => 2
[4] => 1
[5] => 2
[6] => 1
[7] => 2
)
Хотя результатом является равномерное распределение.
PS Я не совсем понял реальную проблему, связанную с сайтом, с которой вы имеете дело, но я надеюсь, что правильно понял математическую концепцию.
Ответ №2:
Можете ли вы попробовать следующую функцию для пробела.
protected function space(amp;$reed, $r)
{
$totalReeds = count ($reed);
$postion = floor($totalReeds/$r);
for ($i=0; $i<=$totalReeds; $i=$i $postion, $r--) {
if ($r <= 0) break;
$reed[$i] ;
}
}
Я просто попробовал с некоторыми входными данными, и это сработало для меня. Более того, это дало мне правильный вывод для предыдущего примера, который вы привели.
Можете ли вы попробовать использовать эту функцию и сообщить мне, работает ли она для всех ваших входных данных.
— РЕДАКТИРОВАТЬ —
Попробуйте этот код.
protected function space(amp;$reed, $r)
{
$totalReeds = count ($reed);
$postion = floor($totalReeds/$r);
$postion = $postion == 1? 2 : $postion;
for ($i=0; $i<=$totalReeds; $i=$i $postion, $r--) {
if ($r <= 0) break;
if (isset($reed[$i])) $reed[$i] ;
}
}
Надеюсь, это сработает. Я не уверен в ожидаемых результатах других входных данных, которые вы указали в своем комментарии. Поэтому я предполагаю, что следующее верно. Если я ошибаюсь, пожалуйста, опубликуйте ожидаемые результаты других входных данных.
С уважением,
Комментарии:
1. К сожалению, это работает только для некоторых входных данных. Посмотрите на этот запуск с вашей
space()
функцией: ideone.com/YHF6g Вы увидите, что 2-й и 4-й примеры распределены неравномерно. Спасибо2. каков ожидаемый результат для 2-го и 4-го примеров?
3. 2-й пример (13epi более 8 точек на дюйм) должен быть
2-2-1-2-2-1-2-1
вашим2-2-2-2-2-1-1-1
. 4-й пример слишком длинный, чтобы помещать его здесь, но 1 и 0 должны быть равномерно чередованы, а не блок из всех 1, а затем блок из всех 0.
Ответ №3:
Вау, этот код ужасен, имхо 🙂 Рекурсия для поиска общего деления, безумное использование is_numeric и intval, одно одинокое исключение и т. Д.. Архитектура класса тоже странная, я бы сделал это как статический класс.
Честно говоря, я не понял математической проблемы, поскольку все, что я нашел о ячейках и вмятинах, переплетается (я не являюсь носителем языка, поэтому мог пропустить что-то очевидное), но я думаю, что понял ваши требования (если это не строгая математическая задача).
В любом случае, я немного почистил его, поэтому я публикую полный код класса, но вы можете использовать space() отдельно, если вам это не нравится. Мой алгоритм можно сильно оптимизировать (второй цикл можно удалить), но я слишком ленив, чтобы это делать.
class ReedSubstitution {
private $epi;
private $dpi;
public function substitute($e, $d) {
$this->epi = floatval($e);
$this->dpi = floatval($d);
if (empty($this->epi) || empty($this->dpi)) {
throw new Exception('Both desired ends per unit and available dents per unit must be specified.');
}
//make equivalent integers if dpi or epi are fractional
$this->unfraction();
if ($this->epi % $this->dpi == 0) {
return array($this->epi / $this->dpi);
}
$gcd = $this->euclid($this->epi, $this->dpi);
$e = $this->epi / $gcd;
$d = $this->dpi / $gcd;
$r = $e % $d; //extra to be spread out over array
$q = ($e - $r) / $d; //count that every dent gets
$reed = array_fill(0, $d, $q);
$this->space($reed, $r);
return $reed;
}
protected function unfraction() {
//Find fraction start position
$epi_fract_pos = strpos($this->epi, '.');
$dpi_fract_pos = strpos($this->dpi, '.');
//Find fraction length
$epi_fract_len = $epi_fract_pos ? (strlen($this->epi) - $epi_fract_pos - 1) : 0;
$dpi_fract_len = $dpi_fract_pos ? (strlen($this->dpi) - $dpi_fract_pos - 1) : 0;
//Calculate max fraction length
$fraction_len = max($epi_fract_len, $dpi_fract_len);
//Unfraction variables
$mult = pow(10, $fraction_len);
$this->epi*=$mult;
$this->dpi*=$mult;
}
/**
* @desc evenly distribute remaining ends over entirety of dents
* @param Array $reed, dents in one pattern repeat
* @param Integer $r, remaining ends to be distributed
*/
protected function space(amp;$reed, $r) {
$c = count($reed);
$base = $reed[0];
for ($i = 0; $i < $r; $i ) {
$reed[$i] ;
}
while ($reed[$c - 1] === $base) {
$reed[$c] = $base;
//Find the longest $base 1 array with least $base behind it
$max_base_plus_size = -$c;
$cur_base_plus_size = 0;
$cur_base_plus_pos = array(NULL, NULL);
$max_base_plus_pos = NULL;
for ($i = 0; $i <= $c; $i ) {
if ($reed[$i] != $base) {
if($cur_base_plus_pos[0]===NULL) {
$cur_base_plus_pos[0] = $i;
}
if ($reed[$i 1] == $base) {
$cur_base_plus_pos[1]=$i;
if ($max_base_plus_size < $cur_base_plus_size) {
$max_base_plus_size = $cur_base_plus_size;
$max_base_plus_pos = $cur_base_plus_pos;
}
$cur_base_plus_size = 0;
$cur_base_plus_pos = array(NULL, NULL);
}
else {
$cur_base_plus_size ;
}
} else {
$cur_base_plus_size--;
$cur_base_plus_pos[1] = $i - 1;
}
}
$insert_pos=ceil(($max_base_plus_pos[1] $max_base_plus_pos[0])/2);
array_splice($reed,$insert_pos,0,$base);
}
array_splice($reed,$c);
$reed=array_reverse($reed);//Optional, just to get the same output as you submitted
}
/**
* @desc return greatest common divisor as determined by Euclid's algorithm
* @param integer $x
* @param integer $y
* @return integer
*/
protected function euclid($x, $y) {
while ($x) {
$temp = $x;
$x = $y % $x;
$y = $temp;
}
return $temp;
}
}
Комментарии:
1.
array_splice($reed,$insert_pos,0,$base);
выдает ошибку «array_splice() ожидает, что параметр 1 будет массивом, заданным целым числом»2. @dnagirl: Хех, на каких входных данных? Для меня все в порядке. Проверено на данных из ideone.com/YHF6g . Вы использовали мой класс или просто функцию space() отдельно? Пример: ideone.com/sP0OF (те же данные, но мой класс).