Корзина равномерно: расстояние между остатками неравномерно

Я пишу скрипт для равномерного распределения произвольного числа $epi по произвольному количеству ячеек $dpi. epi означает число концов на дюйм. dpi означает число вмятин на дюйм. Есть 3 требования:

  • bin number should be reduced by the lowest common factor if possible
    • e.g. 10 epi in 6 dpi should be represented by 5 epi in 3 dpi
  • bin population should be as uniform as possible
    • e.g. 2-2-1 is better than 3-1-1
  • short bins should be evenly distributed across the bin array
    • e.g. 1-0-1-0-1 is better than 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(&$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(), чтобы бинирование, требующее более одного цикла, давало приемлемое распределение?


person dnagirl    schedule 18.10.2011    source источник
comment
@Jonathan Chan: Это не домашнее задание. Я работаю над веб-сайтом для ткацкой группы. Им нужен виджет замены тростника, чтобы им не приходилось использовать такие диаграммы: joowl.com/reedsubs. html Я мог бы просто сохранить диаграмму в базе данных и извлечь записи, но мне кажется, что это должна быть решаемая математическая задача.   -  person dnagirl    schedule 21.10.2011
comment
кто бы ни проголосовал за мой вопрос, объяснение будет оценено по достоинству. Я думаю, что вопрос ясен, и кода и вывода достаточно, чтобы читатели могли увидеть проблему, с которой я столкнулся.   -  person dnagirl    schedule 21.10.2011


Ответы (3)


Я надеюсь, что это поможет или, по крайней мере, укажет вам правильное направление:

protected function space(&$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
)

Хотя в результате получается равномерное распределение.

P.S. Я не совсем понял реальную проблему, связанную с веб-сайтом, с которой вы столкнулись, но я надеюсь, что правильно понял математическую концепцию.

person Stanislav Shabalin    schedule 21.10.2011

Можете ли вы попробовать следующую функцию для пространства.

protected function space(&$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(&$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]++;
            }    
    }     

Я надеюсь, что это работает. Я не уверен в ожидаемых результатах других вкладов, которые вы указали в своем комментарии. Поэтому я предполагаю, что следующее верно. Если я ошибаюсь, пожалуйста, опубликуйте ожидаемые результаты других входов.

http://ideone.com/L4fUv

С уважением,

person Anush Prem    schedule 21.10.2011
comment
К сожалению, это работает только для некоторых входных данных. Взгляните на этот запуск с функцией space(): ideone.com/YHF6g. Вы увидите, что 2-й и 4-й пример распределен неравномерно. Спасибо - person dnagirl; 21.10.2011
comment
каков ожидаемый результат для 2-го и 4-го примеров? - person Anush Prem; 21.10.2011
comment
2-й пример (13epi на 8 точек на дюйм) должен быть 2-2-1-2-2-1-2-1 Ваш дает 2-2-2-2-2-1-1-1. 4-й пример слишком длинный, чтобы помещать его здесь, но 1 и 0 должны быть равномерно распределены, а не блок всех 1, а затем блок всех 0. - person dnagirl; 25.10.2011

Вау, этот код ужасен, имхо :) Рекурсия для поиска общего делителя, безумное использование 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(&$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;
    }

}
person XzKto    schedule 24.10.2011
comment
array_splice($reed,$insert_pos,0,$base); выдает ошибку array_splice() ожидает, что параметр 1 будет массивом, заданным целым числом - person dnagirl; 25.10.2011
comment
@dnagirl: Хех, на каких входных данных? У меня все в порядке. Проверено на данных с ideone.com/YHF6g. Вы использовали мой класс или просто функцию space() отдельно? Пример: ideone.com/sP0OF (те же данные, но мой класс). - person XzKto; 25.10.2011