【问题标题】:Make sure no duplicate directory paths exist确保不存在重复的目录路径
【发布时间】:2020-02-21 11:04:05
【问题描述】:

我正在编写一个脚本,其中用户选择目录,然后将其存储在数组属性中,以便可以递归地抓取它们。

{
  "archives": [
    "C:\\AMD\\Packages",
    "C:\\Users",
    "C:\\Windows",
    "D:\\",
    "E:\\Pictures\\Birthday"
  ]
}

我显然不想存储重复路径或其他路径包含的路径。例如,如果用户要选择一个新文件夹来添加到数组中,E:\\Pictures,那么E:\\Pictures\\Birthday 将被丢弃并被它替换,因为E:\\Pictures 包含E:\\Pictures\\Birthday

{
  "archives": [
    "C:\\AMD\\Packages",
    "C:\\Users",
    "C:\\Windows",
    "D:\\",
    "E:\\Pictures"
  ]
}

我知道这可以通过解析所有正在考虑的值(即['C:', 'AMD', 'Packages'], [...], ... 等)然后将它们相互比较来完成。但是,这似乎非常密集,尤其是当路径数组变得更大并且目录路径更长时。


您也可以通过将字符串与includes 进行比较来做到这一点。例如,如果 A 包含 B 或 B 包含 A,则将它们拆分,并丢弃长度较长的那个。

for (const dir of dirs){
  if (newPath.includes(dir) || dir.includes(newPath)){
    if (newPath.split('\\') < dir.split('\\')){
      // remove dir from json object and replace it with newPath
    }
  } else {
    pathArray.push(dir)
  }
}

阅读下面的答案之一后,我才意识到includes 方法遇到了比较相似但独特的路径的问题,即C:\UsersC:\User


虽然必须有更好的方法来做到这一点??

【问题讨论】:

  • 在您的示例中,数组archives 按字母顺序排序。这个假设正确吗?
  • @user753642 是的,在示例中它是按字母顺序排列的,但不一定非要如此。我只关心不添加任何“重复”路径

标签: javascript arrays json directory data-manipulation


【解决方案1】:

此功能将为您提供所需的结果。它首先查看路径的父路径是否存在于档案中,如果存在,则不执行任何操作。如果没有,则删除路径的所有子路径,然后插入新路径。

更新

我在函数中添加了delim 输入,使其也可用于 unix/MacOS 样式的文件名。

let data = {
  "archives": [
    "C:\\AMD\\Packages",
    "C:\\Users",
    "C:\\Windows",
    "D:\\",
    "E:\\Pictures"
  ]
};

const add_to_archives = (path, data, delim) => {
  // does the parent of this path already exist? if so, nothing to do
  if (data.archives.reduce((c, v) =>
      c || path.indexOf(v.slice(-1) == delim ? v : (v + delim)) === 0, false)) return data;
  // not found. remove any children of this path
  data.archives = data.archives.filter(v => v.indexOf(path.slice(-1) == delim ? path : (path + delim)) !== 0);
  // and add the new path
  data.archives.push(path);
  return data;
}

add_to_archives("E:\\Pictures\\Too", data, "\\");
console.log(data);
add_to_archives("E:\\PicturesToo", data, "\\");
console.log(data);
add_to_archives("D:\\Documents", data, "\\");
console.log(data);
add_to_archives("C:\\AMD", data, "\\");
console.log(data);

data = {
  "archives": [
    "/var/www/html/site",
    "/etc",
    "/usr/tim",
    "/bin"
  ]
};

add_to_archives("/var/www/html/site2", data, "/");
console.log(data);
add_to_archives("/etc/conf.d", data, "/");
console.log(data);
add_to_archives("/usr", data, "/");
console.log(data);
add_to_archives("/var/www/html", data, "/");
console.log(data);
.as-console-wrapper {
  max-height: 100% !important;
}

【讨论】:

  • 我只是去睡觉,所以我早上看一下这个 tomo。我很好奇,你对我刚刚添加到我的问题中的代码示例有何看法?谈心
  • @BugWhisperer 只要路径始终以驱动器说明符开头,您发布的内容似乎是合理的。请注意,我在奥兹国,所以当你起床时不会醒来,所以我会提前为任何延迟回复你而道歉。
  • 浏览下面的另一个答案后,我意识到includes 方法存在问题,因为仍然会丢弃或忽略唯一路径,即C:\UsersC:\Users。这很糟糕,因为似乎任何有效的方法都是超级密集的。 FWIW,我最终想要一种适用于 Linux、MacOS 和 Windows 的方法,我什至不确定 MacOS 如何处理这些事情。我将在今天晚些时候彻底检查和比较所有答案,因为我的日程安排中突然出现了一些随机的东西。
  • @BugWhisperer 我已经对我的解决方案进行了编辑,以使其也可以使用 Unix/MaxOS 样式的文件名(基本上是在函数中添加分隔符输入)
  • k,我要花点时间来解开你的代码:) if 条件中的 , false 究竟完成了什么?我在 o 之前从未见过:哦,等等,它是数据缩减功能的一部分
【解决方案2】:

我们可以使用prefix tree来解决这个问题

目的是限制我们检查包含或“包含”的路径数量。

如果您有很多兄弟姐妹(树遍历 + 查找作为每个文件夹的键),这种方法可能会很有用。 如果您经常在archives 中指定根文件夹,那就大材小用了

算法

tree = {}
foreach path
    split the path in folders (one may iterate with substring but it is worth it?)
    try to match folders of that path while traversing the tree
    if you encounter a stop node, skip to next path
    if not, 
        if your path end on an existing node
            mark that node as a stop node
            drop the children of that node (you can let them be, though)
        else
            include the remaining folders of the path as node in tree
            mark the last node as a stop node

实施

请注意,如果路径包含名为“stop”的文件夹,则下面的实现将失败。按主观偏好排序

  • 使用MapSymbol('stop')
  • 或一棵真正的树(至少不要将文件夹存储在布尔值 stop 旁边)
  • 不要假设任何停止节点,如果您设法到达路径的尽头,请始终丢弃子节点
  • 希望没有人试图比你聪明并重命名stop,因为某些不起眼的文件夹将不存在-lolol_xxzz9_stop

function nodupes(archives){
    tree = {};
    archives.forEach(arch=>{
        const folders = arch.split('\\');
        folders.splice(1,1);
        //case of empty string such as D:\\\
        if(folders[folders.length-1].length==0){folders.pop();}
        let cur = tree;

        let dropped = false;
        let lastFolderIndex = 0;
        let ok = folders.every((folder,i)=>{
            if(cur[folder]){
                if(cur[folder].stop){
                    dropped = true;
                    return false;
                }
                cur = cur[folder];
                return true;
            }
            cur[folder] = {}
            cur = cur[folder];
            lastFolderIndex = i;
            return true;
        });
        if(ok){
            cur.stop = true;
            //delete (facultatively) the subfolders
            if(lastFolderIndex < folders.length-1){
                console.log('cleanup', folders, 'on node', cur)
                Object.keys(cur).forEach(k=>{
                    if(k != 'stop'){
                        delete cur[k];
                    }
                })
            }
            
        }
    });
    //console.log('tree', JSON.stringify(tree,null,1));
    //dfs on the tree to get all the paths to... the leaves
    let dfs = function(tree,paths,p){
        if(tree.stop){
            return paths.push(p.join('\\\\'));
        }
        Object.keys(tree).forEach(k=>{
            dfs(tree[k], paths, p.concat(k));
        });
    }
    let paths = [];
    dfs(tree, paths,[]);
    return paths;
}
let archives = [
    'C:\\\\ab',
    'D:\\\\', //just some root
    'D:\\\\ab',//be dropped
    'D:\\\\abc',//dropped as well
    'F:\\\\abc\\\\e',//two folder creation
    'C:\\\\ab\\c',
    'B:\\\\ab\\c',
    'B:\\\\ab',//expect B\\\\ab\\c to be dropped
]
console.log(nodupes(archives))

【讨论】:

  • 对自己发表评论。实际上,在遇到文件夹结尾时删除子级可能更可取。我没有看到任何缺点
  • 我现在只是想彻底看看你的答案
  • 对不起,我认为我的问题并不完全清楚。 archives 属性将作为一个空数组开始,当用户选择文件夹时,只要它们或其父级不存在于数组中,它们就会被添加到数组中。尽管如此,我还是给了你一票。在发布这个问题后,我有了一个类似于树想法的想法。
  • @BugWhisperer 但正如建议的那样,您可以在迭代地将所有内容添加到树后恢复数组。在插入过程中是否需要维护数组? (这意味着当删除路径时,数组必须被缩小和“压缩”,因为可能有多个洞?)
  • 我会这样做的。
【解决方案3】:

试试这个

console.log([
    "C:\\AMD\\Packages", 
    "C:\\Users", 
    "C:\\User", 
    "E:\\Pictures", 
    "E:\\Pictures\\Birthday", 
    "C:\\Windows", 
    "D:\\", 
    "D:\\aabbcc", 
    "E:\\Pictures\\Birthday"
].sort().reduce(
    (acc, cur) => 
        acc.length > 0 
        && cur.startsWith(acc[acc.length - 1]) 
        && ( cur.indexOf("\\", acc[acc.length - 1].replace(/\\$/,"").length) !== -1  )
        && acc || acc.concat(cur)
    , []
))

【讨论】:

  • archives 数组一开始就永远不会有重复的值。问题都是关于防止添加“重复”路径或始终丢弃特定路径以获得已经包含特定路径的更通用路径。抱歉,如果不清楚。当我今天晚些时候有更多空闲时间时,我会彻底检查你的答案。
  • 是的,看看你的答案,这甚至不接受用户输入,所以它真的没有解决我的问题
猜你喜欢
  • 2017-10-12
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 2013-08-30
  • 2017-11-25
  • 2023-02-12
相关资源
最近更新 更多