7

我有以下表格文件夹

  id    name    childOf
------------------------
  1      A        0
  2      B        1
  3      C        0
  4      D        3
  5      E        2
  6      F        5

这形成一棵树:

A
-B
--E
---F
C
-D

我允许拖放文件夹,但需要防止文件夹被拖到自己的子文件夹中。

例如,D 到 B 可以,D 到 E 可以,B 到 F 不可以,因为它拖到自己的树中,但 F 到 B 可以,因为它拖到树上。

问题:如果用户选择 B 并试图将其拖到 F,我该如何防止这种情况发生?

我正在寻找逻辑,一个人怎么说,然后对其进行编码,B to F 不行,但 F to B 可以。

4

11 回答 11

4

使用您当前的数据库模式,我只看到多个选择语句的选项。你必须向上或向下检查树,直到你碰到根或最后一个孩子(就像在 nietonfirs 的回答中一样)。

我自己会在表格中添加第四列,其中包含完整路径:

id   name   childOf   pathToFolder
----------------------------------
1      A        0      ,1,
2      B        1      ,1,2,
3      C        0      ,1,3,
4      D        3      ,1,3,4,
5      E        2      ,1,2,5,
6      F        5      ,1,2,5,6,

有几种方法可以使用这些新数据。一种方法是,如果有人想移动 B,请获取有效目的地列表:SELECT id FROM folders WHERE pathToFolder NOT LIKE ',1,2,%'

这些操作不是最快的,但非常方便。

于 2013-10-16T16:55:58.947 回答
1

我想会有一种方法可以同时获取目标和目标(或整个数据对象,等等)的 id,并检查目标是否在目标路径中。如果是,则该方法将返回 false 或抛出异常,否则将返回 true。该方法可以作为控制结构引入到您的目录复制代码流中。

就个人而言,对于分层数据结构,我会将其实现为嵌套 set。设置和修复嵌套集可能很麻烦,但我发现检查节点之间的关系和获取整个子树非常方便。

这是一个 PHPUnit 测试,它对我的​​想法进行了部分且有些幼稚的实现:

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(-1);

require 'vendor/autoload.php';

class ParentingSucks
{

    public $data = array();

    public function isAllowed($targetId, $destId)
    {
        $target = $this->getById($targetId);
        $dest = $this->getById($destId);
        $parent = $this->getById($dest['childOf']);

        $isAllowed = true;
        while ($parent) {
            if ($parent['id'] == $targetId) {
                $isAllowed = false;
                break;
            }

            $parent = $this->getById($parent['childOf']);
        }

        return $isAllowed;
    }

    public function getById($id)
    {
        if (isset($this->data[$id])) {
            return $this->data[$id];
        }
        return array();
     }
}

class HowIMetYourParentDir extends PHPUnit_Framework_TestCase
{

    /**
     * @test
     * @dataProvider generate
     */
    public function droppingOnParentNotAllowed($data, $target, $dest, $outcome)
    {

        $stub = $this->getMock('ParentingSucks', null);
        $stub->data = $data;

        $result = $stub->isAllowed($target, $dest);

        $this->assertEquals($result, $outcome, 'Oh no!');
    }

    public function generate()
    {
        $fakeData = array(
            1 => array('id' => 1, 'name' => 'A', 'childOf' => 0),
            2 => array('id' => 2, 'name' => 'B', 'childOf' => 1),
            3 => array('id' => 3, 'name' => 'C', 'childOf' => 0),
            4 => array('id' => 4, 'name' => 'D', 'childOf' => 3),
            5 => array('id' => 5, 'name' => 'E', 'childOf' => 2),
            6 => array('id' => 6, 'name' => 'F', 'childOf' => 5),
        );
        return array(
            array(
                $fakeData,
                2, // target
                6, // dest
                false, // outcome
            ),
            array(
                $fakeData,
                4,
                2,
                true,
            ),
            array(
                $fakeData,
                4,
                2,
                true,
            ),
            array(
                $fakeData,
                3,
                4,
                false,
            ),
        );
    }
}              false, // outcome
            ),
            array(
                $fakeData,
                4,
                2,
                true,
            ),
            array(
                $fakeData,
                4,
                2,
                true,
            ),
            array(
                $fakeData,
                3,
                4,
                false,
            ),
        );
    }
}

变量/函数/类名称可能不适合您的域模型,所以不要介意它们。

于 2013-10-16T16:32:56.330 回答
1

逻辑相当简单:

当且ftf<>tf 不是t.

由于问题被标记为并且没有指定 RDBMS,我将假设您的文件夹位于 php 数组中,如下所示:

$namespace=array(
  1=>array('name'=>'A','parent'=>0),
  2=>array('name'=>'B','parent'=>1),
  3=>array('name'=>'C','parent'=>0),
  4=>array('name'=>'D','parent'=>3),
  5=>array('name'=>'E','parent'=>2),
  6=>array('name'=>'F','parent'=>5),
  );

要确定是否$f是 的祖先$t,我们应该从$t树开始并向上走,直到 (1) 我们找到$f或 (2)我们找到一个根(不是,因为您的命名空间中有多个根)。所以考虑以下函数:

function is_ancestor($f,$t)
  {
  global $namespace;
  $are_equal=($f==$t);
  $t_parent=$namespace[$t]['parent'];
  $is_root=!isset($namespace[$t_parent]);
  return $are_equal || (!$is_root && is_ancestor($f,$t_parent));
  }

我认为这很简单。但是有两件事:(1)显然,参数是文件夹ids,以及(2)尽管有名称,该函数不仅检查是否$f是 的祖先$t,而且检查它们是否相等。

您可以在这个 php fiddle中看到整个概念的实际应用。

于 2013-10-23T07:10:47.710 回答
1

你有几个选择:

  1. 使用当前的树结构,您可以递归地(或使用堆栈)遍历要附加到的元素的父级;如果父元素之一等于附加元素,则简单地返回错误。您还可以在 javascript 中执行该检查以提供更好的用户体验(为什么甚至允许将元素拖动到它自己的子元素:));然而,这种方法对于非常大的树可能会有点慢,但是写入真的很便宜。

  2. 通过将树结构重新设计为嵌套集,您可以更轻松地处理树;我不会深入介绍嵌套集,您可以在此页面上阅读这些内容:关于嵌套集。相反,我将让您快速了解嵌套集合的可能性。首先,您的表格如下所示:

    id 名称 lft rgt
    --------------------------
    1 一个 1 8
    2 乙 2 7
    5 E 3 6
    6 女 4 5
    3 C 9 12
    4 天 10 11

乍一看它可能看起来很奇怪,但现在不要担心。

有了它,您可以轻松检查一个节点是否是另一个节点的父节点:$static->lft > $appended->lft && $static->rgt < $appended->rgt,如果是这样,那么$appended是父节点,$static您可能会抛出错误;

您是否需要:

  • 检索给定节点的所有父节点?选择 WHERE lft < $node->lft and rgt > $node->rgt
  • 检索所有后代?选择WHERE lft > $node->lft and rgt < $node->rgt
  • 检索所有兄弟姐妹?添加额外的level列,这很容易

但是等等等等,我如何在那个结构中插入一个新节点?你可能会问。幸运的是,没有必要重新发明轮子,因为嵌套集非常流行。您可以使用这样的实现:https ://github.com/riquito/Baobab/并专注于您的应用程序;

查看 repo 中的一些示例:https ://github.com/riquito/Baobab/blob/master/src/examples/animals.php

$root_id=$tree->appendChild(NULL,array("name"=>'Animals'));

$vertebrates_id   = $tree->appendChild($root_id,array("name"=>"Vertebrates"));
$invertebrates_id = $tree->appendChild($root_id,array("name"=>"Invertebrates"));

如您所见,您不必触摸lftrgt重视,因为有人已经为您编写了该部分。如果你使用 Propel 或 Doctrine,事情就更容易了,因为它们都支持嵌套集。

于 2013-10-16T17:11:28.977 回答
0

创建两个函数并获取每个 id 的所有顶级父级,即从 B 到 A 以及类似地从 F 到它的 topmast 父级 F 到 c 并将 id 存储在两个不同的数组中。现在检查两个数组中是否有共同点否。如果是普通的cn拖动,则不能拖动

于 2013-10-23T13:25:25.287 回答
0

只需检查父母,直到您点击根文件夹?在伪代码中

// targetFolder is the target folder where the folder is going to be moved
// while sourceFolder is the folder that's moved.
parents = targetFolder.getParents();
if (parents.contains(sourceFolder)) {
    // don't allow the operation
}
于 2013-10-08T09:13:42.403 回答
0

我不明白你的问题。算法真的很简单。

每次调用 PHP 时,首先检查该文件夹是否不是目标文件夹的子文件夹。现在,在您的情况下,这将行不通。您不应该使用数字 ID 来保存您应该使用的文件夹和文件,因为所有操作系统都使用文件路径。

像这样:

url                    type     description
-------------------------------------------------
ROOT/folder            2        Image folder...
ROOT/hallo             1        Hallo directory..
ROOT/hallo/subfolder   1        I am a sub dir..

现在您只需要一列,但它将是一个全文索引。当您想检查一个文件夹是否不是其自己的子文件夹的父文件夹时,您只需检查 url。

就像当我画ROOT/halloROOT/hallo/subfolder你一样,只需这样做:

ROOT/hallo = 10 个字符

ROOT/hallo/子文件夹 = 20 个字符

<?php

    $selected_folder = 'ROOT/hallo';
    $subject_folder = 'ROOT/hallo/subfolder';

    $length = strlen($selected_folder);
    if($selected_folder != substr($subject_folder,0,$length)){
         echo 'go';
    }
    else{
        echo 'Can not relocate folder to its own subfolder';
    }

?>
于 2013-10-22T07:08:03.380 回答
0

这个问题让我想起了我语言课上的事情。所以我想出了一个这样的解决方案:让我以一种想象的方式构建你的树。我会说 R 代表 root (id:0) 和 -1 最后。

R:AC
A:B
B:E
E:F
F:-1
C:D
D:-1

但如果我们想快速做事,这还不够。让我们也添加这些:

R:BC
R:EC
R:FC

R:BD
R:ED
R:FD

让我为你整理东西:

R:AC
R:AD *I added this one
R:BC
R:EC
R:FC
R:BD
R:ED
R:FD
R:A * this one too
R:C * this one too
A:B
B:E
E:F
C:D
F:-1
D:-1

这应该足够了。所以,你说

例如,D 到 B 可以,D 到 E 可以,B 到 F 不可以,因为它拖到自己的树中,但 F 到 B 可以,因为它拖到树上。

让我们看看,D 到 B 可以吗?我们应该从右到左检查它。D在哪里?哦,是的,你来了.. R:BD!那么,如果他们在一起就可以了吗?好的,可以。D到E?红色的!是的,没关系。B到F?一起来看看BF吧……不哈?所以,右边的 B... 有 A:B ,所以让我们检查一下 AR:A!繁荣!乙

这只是一个以不同方式看待事物的建议。它可以通过不同的观点来完成。但我真的认为使用这样的语言语法会有所帮助。它可以在空中创建,也可以根据情况创建为地图文件。但是在很多情况下的复杂性真的很低。祝您找到最佳解决方案好运!

于 2013-10-21T23:04:03.690 回答
0

次优方法是简单地计算给定文件夹的所有子文件夹,然后检查目标文件夹是否是其中的一部分。可能看起来像这样:

getSubfolders(folder):
    oneLevelDeepSubfolders = subfolders with child-of field == folder.id
    allSubfolders = []
    for subfolder in oneLevelDeepFolders:
        allSubfolders += getSubfolders(subfolder);
    return allSubfolders;

然后只需检查您的目标文件夹是否在 getSubfolders(folder) 中。

您可能希望通过创建另一个表来加速此过程,该表预先计算任意深度的子文件夹关系,这样您就不必在每次需要时递归地计算它。

于 2013-10-16T16:25:12.313 回答
0

您可以检查目标路径。如果父节点之一是源节点,则链将断开并显示错误。例如:

<?php
define("ROOT_NODE_ID",0);
function getChildOf($nodeId){
   // some code to get id value of parent node and assign it to $parentId
   return $parentId;
}

$sourceId = 2; // Node B
$destinationId = 6; // Node F
$error = false;

$tmpParentId = getChildOf( $destinationId );
while($tmpParentId != ROOT_NODE_ID){
    if($tmpParentId == $sourceId ){
        $error=true;
        break;
    }
    $tmpParentId = getChildOf( $tmpParentId );
}

此方法允许使用具有整数值的索引而不是使用字符串函数。使用 int 类型字段时,字符串函数可能会慢得多。您也可以保留此表结构而无需任何修改。

于 2013-10-23T12:10:07.667 回答
0

由于问题是用 PHP 标记的,并且您正在谈论拖放,因此您需要类来表示 PHP 和 javascript 中的树行为

对于 PHP,我建议您通过 cake-php 框架的 TreeBehavior 类的实现,它将树行为添加到任何模型中,并希望您拥有 Adam 建议的表结构。

对于 Javascript 实现,我观察到 YUI树模块

为 javascript 实现树

还有一个可能支持拖放的库, 在这里找到它

我希望这会对你有所帮助。

于 2013-10-22T13:51:41.810 回答