Nested Sets, изменение порядка вывода ветвей.

_RVK_

Новичок
Nested Sets, изменение порядка вывода ветвей.

Мне нужно менять порядок элементов дерева. Например

|-item1
| |-item11
| |-item12
| |-item13

Вывести

|-item1
| |-item11
| |-item13
| |-item12

Как видно родитель тот же, но порядок другой.
У меня упорядочивание происходит по компоненте left элемента. Соответственно нужно изменять её, но как это сделать с гарантией того что структура дерева не изменится.

Пока писал, подумал что для перемещения на 1 позицию, нужно просто left и right компонетнты item12 присвоить элементу item13 и наоборот. Может есть другие варианты?

P.S. Может cтоит добавить метод в phpDBTree?
 

Макс

Старожил PHPClub
Diesel
в поиск по форуму. Была большая тема про dbtree, там примеры кода были в виде готовых методов. Еще в LJ :: ru_php недавно класс показывали и там был свой способ для изменения порядка
 

_RVK_

Новичок
Посмотрел ссылки, но не стал использовать готовый метод, а написал сам. Получилось проще. Вроде работает. Может я чего не учел?
PHP:
//************************************************************************
// Moves element to up or down if this possible.
// $id : an ID of an element
// $direction : where move element. If this parametr =1 that moves to up else if -1 to down
// Returns : false if element not has been moved, otherwise true
function move($id,$direction=1)
{
    $direction = ($direction==-1) ? -1 : 1; 
    $this->sql = ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->id.'='.$id.
                 ' ORDER BY '.$this->left;
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    if (!$this->db->num_rows($res)) return false;
    $data = $this->db->fetch_array($res);
    $cleft = $data[$this->left];
    $cright = $data[$this->right];
    $level = $data[$this->level];
    
    //Получаем замещаемую ветвь
    $this->sql = ($direction>0) ? 
                 ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->right.'<'.($cleft).' AND '.$this->level.'='.$level.' ORDER BY '.$this->right.' DESC LIMIT 0,1' : 
                 ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->left.'>'.($cright).' AND '.$this->level.'='.$level.' ORDER BY '.$this->left.' ASC LIMIT 0,1';
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    if (!$this->db->num_rows($res)) return false;
    $data = $this->db->fetch_array($res);
    
    //Вычисляем смещение
    $offset = ($data[$this->right] - $data[$this->left] + 1)*$direction;
    $offset1 = ($cright-$cleft+1)*$direction;
    
    //Сохраняем замещаемую ветвь
    $this->sql = ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->left.
                 ' BETWEEN '.$data[$this->left].' AND '.$data[$this->right];
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    $old = ' WHERE ';
    while ($data = $this->db->fetch_array($res))
    {
       $old .= ' '.$this->id.'='.$data[$this->id].' OR ';
    }
    $old = substr_replace($old,'',strlen($old)-4);
    
    //Перемещаем ветви
    $this->sql = ' UPDATE '.$this->table.
                 ' SET '.$this->left.'='.$this->left.'-'.$offset.
                 ' , '.$this->right.'='.$this->right.'-'.$offset.
                 ' WHERE  '.$this->left.
                 ' BETWEEN '.$cleft.' AND '.$cright;
    $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    
    $this->sql = ' UPDATE '.$this->table.
                 ' SET '.$this->left.'='.$this->left.'+'.$offset1.
                 ', '.$this->right.'='.$this->right.'+'.$offset1.$old; 
    $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    
    return true;
}
Да, и такой вопрос: right ветви всегда равна left-1 следующей ветки того же уровня? Или допускается дефрагментация?
 

vladax

Новичок
Да, и такой вопрос: right ветви всегда равна left-1 следующей ветки того же уровня? Или допускается дефрагментация?
Если ты имеешь в виду ветви одного родителя, находящиеся на одном уровне, то имхо да.
 

_RVK_

Новичок
Не допускается, а в общем случае так и происходит
Что это значит? То есть нельзя, но если очень хочется то можно?
Дело в то что в моем методе соседняя ветка определяется как right=left-1, что работает когда дерево не дефрагментировано. Я могу и подругому определять соседа, но стоит ли?

-~{}~ 09.09.04 09:36:

Немного подправил, теперь не зависит от фрагментации.

-~{}~ 09.09.04 17:20:

Нашел ошибку. Вот правильная версия метода, на случай если кто будет использовать этот код.
PHP:
//************************************************************************
// Moves element to up or down if this possible.
// $id : an ID of an element
// $direction : where move element. If this parametr =1 that moves to up else if -1 to down
// Returns : false if element not has been moved, otherwise true
function move($id,$direction=1)
{
    $direction = ($direction==-1) ? -1 : 1; 
    $this->sql = ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->id.'='.$id.
                 ' ORDER BY '.$this->left;
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    if (!$this->db->num_rows($res)) return false;
    $data = $this->db->fetch_array($res);
    $cleft = $data[$this->left];
    $cright = $data[$this->right];
    $level = $data[$this->level];
    
    //Получаем родителя
    $res = $this->getParent($id,1);
    $data_parent = $this->db->fetch_array($res);
    $this->sql = 'SELECT '.$this->left.','.$this->right.' FROM '.$this->table.' WHERE '.$this->id.'='.$data_parent[$this->id];
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    $data_parent = $this->db->fetch_array($res);
    if (!$this->db->num_rows($res)) return false;
    
    //Получаем замещаемую ветвь
    $this->sql = ($direction>0) ? 
                 ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->right.'<'.($cleft).' AND '.$this->level.'='.$level.' AND '.$this->left.' BETWEEN '.$data_parent[$this->left].' AND '.$data_parent[$this->right].' ORDER BY '.$this->right.' DESC LIMIT 0,1' : 
                 ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->left.'>'.($cright).' AND '.$this->level.'='.$level.' AND '.$this->left.' BETWEEN '.$data_parent[$this->left].' AND '.$data_parent[$this->right].' ORDER BY '.$this->left.' ASC LIMIT 0,1';
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    if (!$this->db->num_rows($res)) return false;
    $data = $this->db->fetch_array($res);
    
    //Вычисляем смещение
    $offset = ($data[$this->right] - $data[$this->left] + 1)*$direction;
    $offset1 = ($cright-$cleft+1)*$direction;
    
    //Сохраняем замещаемую ветвь
    $this->sql = ' SELECT '.$this->id.','.$this->left.','.$this->right.','.$this->level.
                 ' FROM '.$this->table.
                 ' WHERE '.$this->left.
                 ' BETWEEN '.$data[$this->left].' AND '.$data[$this->right];
    $res = $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    $old = ' WHERE ';
    while ($data = $this->db->fetch_array($res))
    {
       $old .= ' '.$this->id.'='.$data[$this->id].' OR ';
    }
    $old = substr_replace($old,'',strlen($old)-4);
    
    //Перемещаем ветви
    $this->sql = ' UPDATE '.$this->table.
                 ' SET '.$this->left.'='.$this->left.'-'.$offset.
                 ' , '.$this->right.'='.$this->right.'-'.$offset.
                 ' WHERE  '.$this->left.
                 ' BETWEEN '.$cleft.' AND '.$cright;
    $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    
    $this->sql = ' UPDATE '.$this->table.
                 ' SET '.$this->left.'='.$this->left.'+'.$offset1.
                 ', '.$this->right.'='.$this->right.'+'.$offset1.$old; 
    $this->db->query($this->sql) or die('phpDbTree error on line '.__LINE__.': '.$this->db->error());
    
    return true;
}
 

PhpGuest

Guest
PHP:
 /** 
 
     Пусть для некоторого родительского узла с идентификатором $ID
     существует пронумерованный список его дочерних узлов первого уровня.
     Нумерация идет о 0 до n.
     
     Обозначим I{} - некоторое поддерево, начинающиеся от дочернего узла i
     первого уровня родительского узла  с идентификатором $ID.
     
     Обозначим J{} - некоторое поддерево, начинающиеся от другого дочернего узла j
     первого уровня того же родительского узла  с идентификатором $ID.

     Тогда функция  ChangeOrder($ID,$i,$j) перемещает поддерево I{} соответствующие номеру i
     на место поддерева J{} соответсвующего номеру j. Причем если перемещение идет в направление
     возрастания номеров (j>i) то поддерево J{} и идущие до него поддеревья смещаются к началу спика.
     А если (j<i) то поддерево J{} и следующие за нм смещаются к концу списка.
     
  */                                          
function ChangeOrder($ID,$i,$j){
   
   $res=$this->enumChildren($ID);
   $children=$this->db->result2array($res);     
   $n=count($children);
   if ($i==$j) return false;
   if (($i>$n-1 ) || ($i<0) ) return false;
   if (($j>$n-1 ) || ($j<0) ) return false;   
                       
   $nodeI=$this->getElementInfo($children[$i][$this->id]);
   $nodeI[$this->id]=$children[$i][$this->id]; $nodeI[$this->left]=$nodeI[0]; $nodeI[$this->right]=$nodeI[1];
   
   $nodeJ=$this->getElementInfo($children[$j][$this->id]);
   $nodeJ[$this->id]=$children[$j][$this->id]; $nodeJ[$this->left]=$nodeJ[0]; $nodeJ[$this->right]=$nodeJ[1];
   
   $i_sum=$nodeI[$this->right]-$nodeI[$this->left]+1; 
   
   if ($j>$i) {   // перенос от начала к концу списка
    $ij_sum=$nodeJ[$this->right]-$nodeI[$this->right];     
    $sql="UPDATE IGNORE {$this->table} SET
        {$this->left}=IF({$this->left}>={$nodeI[$this->left]} AND {$this->right}<={$nodeI[$this->right]},
                         {$this->left}+$ij_sum,
                         IF(( {$this->left}>{$nodeI[$this->right]} ) AND ( {$this->right}<={$nodeJ[$this->right]}),{$this->left}-$i_sum,{$this->left})
                        ),
        {$this->right}=IF({$this->left}>={$nodeI[$this->left]} AND {$this->right}<={$nodeI[$this->right]},
                          {$this->right}+$ij_sum,
                          IF(( {$this->left}>{$nodeI[$this->right]} ) AND ( {$this->right}<={$nodeJ[$this->right]}),{$this->right}-$i_sum,{$this->right})
                         )
        ";
    
    if(!$this->db->query($sql)) return false;
   } 
   else
   {  // перенос от конца в начало
        
        $ij_sum=$nodeI[$this->left]-$nodeJ[$this->left];     
        $sql="UPDATE IGNORE {$this->table} SET
        {$this->left}=IF({$this->left}>={$nodeI[$this->left]} AND {$this->right}<={$nodeI[$this->right]},
                         {$this->left}-$ij_sum,
                         IF(( {$this->left}>={$nodeJ[$this->left]} ) AND ( {$this->right}<{$nodeI[$this->left]}),{$this->left}+$i_sum,{$this->left})
                        ),
        {$this->right}=IF({$this->left}>={$nodeI[$this->left]} AND {$this->right}<={$nodeI[$this->right]},
                          {$this->right}-$ij_sum,
                          IF(( {$this->left}>={$nodeJ[$this->left]} ) AND ( {$this->right}<{$nodeI[$this->left]}),{$this->right}+$i_sum,{$this->right})
                         )
        ";
    
           if(!$this->db->query($sql)) return false;        
   }
                
   return true;             
}  // func
 

_RVK_

Новичок
PhpGuest
Обрами свой код тегами [ php ][ /php ] и прокоментируй его.
 

PhpGuest

Guest
я еще к стати баг нашел - метод moveAll разваливает дерево если перемещать категорию к своему же родителю
 

_RVK_

Новичок
Я прочитал коментарий раз 5, но так и не понял что делает твоя функция. Ты мог бы написать коментарий обычными славами, а не в стиле доказательства теоремы Пифагора
 

PhpGuest

Guest
ну теоретически должна двигать не на одну вверх или вниз а
черз многа. те. ей указывается для кокого родителя и какой по порядку узел поставить на какое по порядку место в списке дочерних первого уровня

(я ее чес говоря сильно не тестировал)
 

_RVK_

Новичок
ИМХО, неудачно. Как узнать номер одного узла, и другого? У записей нет номера. Луше указывать id перемещаемой записи, и id до какой записи переместить.
 

PhpGuest

Guest
имхо тодже неудобно
ну это надстроить можно )
 

_RVK_

Новичок
PhpGuest
Так надстрой? Ты для чего здесь свой код выложил? Для того что бы показать каой ты крутой? Или чтобы люди пользовалсь? Если последнее, то доведи до ума. И протестируй хорошо, прежде чем выкладывать.
 

PhpGuest

Guest
спокуха!

кому надо - разберется ;)

имхо вполне юзабилити )
 

PhpGuest

Guest
Автор оригинала: Diesel
Как это понимать? Ладно, можешь не отвечать. Если лень, то так и скажи. Но кидать полуфабрикат не стоило.
неудобно но пригодно к использованию )

-~{}~ 12.10.04 19:31:

лана забей
 
Сверху