首页 > 解决方案 > 将路径数组转换为数据结构

问题描述

我有一个这样的路径数组:

/doc/data/main.js
/doc/data/xl.js
/doc/data/dandu/sdasa.js
/mnt/data/la.js

我正在尝试构建以下结构:

{
  "directories": {
    "/doc/data": {
      "directories": {
        "dandu": {
          "files": {
            "sdasa.js": 1
          }
        }
      },
      "files": {
        "main.js": 1,
        "xl.js": 1
      }
    },
    "/mnt/data": {
      "directories": {},
      "files": {
        "la.js": 1
      }
    }
  },
  "files": {}
}

请忽略该示例中文件的值。将来我会为此分配更复杂的数据。当前值为 1。

从上一个主题中,我发现我可以使用以下函数来获得类似的东西:

var parsePathArray = function() {
    var parsed = {};
    for(var i = 0; i < paths.length; i++) {
        var position = parsed;
        var split = paths[i].split('/');
        for(var j = 0; j < split.length; j++) {
            if(split[j] !== "") {
                if(typeof position[split[j]] === 'undefined')
                    position[split[j]] = {};
                position = position[split[j]];
            }
        }
    }
    return parsed;
}

该解决方案的主要问题是它拆分了每个目录。但我不想拆分每个目录,而是获取至少包含一个文件的目录。例如,/doc在我的示例中没有文件(只有目录 - /data),所以我们继续它。我尝试稍微改变一下功能,但没有奏效:

var str = '';
for (var j = 0; j < split.length; j++) {
    if (j < split.length - 1 && typeof this.files[str] === 'undefined') {
        str += '/' + split[j];
        continue;
    }
    if (str !== '') {
        if (typeof this.files[str] === 'undefined')
            this.files[str] = {};
        this.files = this.files[str];
    }
}

将这些字符串转换为该数据结构的最佳方法是什么?

标签: javascriptarraysalgorithm

解决方案


这是我想出的解决方案。它通过一次构建一条路径并将其与现有数据结构进行比较来工作。它还应该自己处理文件,因为您的原始帖子似乎暗示这是必要的。最后我决定把它分成两个函数,因为这样可能更容易解释。

编码:

const paths = [
    '/doc/data/main.js',
    'doc/data/xl.js',
    '/etc/further/owy.js',
    '/etc/further/abc.js',
    'etc/mma.js',
    '/mnt/data/it.js',
    '/mnt/data/path/is/long/la.js',
    'mnt/data/path/is/la.js',
    '/doc/data/dandu/sdasa.js',
    '/etc/i/j/k/l/thing.js',
    '/etc/i/j/areallylongname.js',
    'thing.js'
];

function buildStructure(paths) {
    let structure = {
        directories: {},
        files: {}
    };

    const compare = (a, b) => {
        return a.split('/').length - b.split('/').length;
    };

    [...paths]
    .map(path => path = path.charAt(0) === '/' ? path : `/${path}`)
    .sort((a, b) => compare(a, b)).forEach(path => {
        const nodes = path.split('/').slice(1);
        const file = nodes.pop();
        
        let pointer = findDirectory(nodes[0] ? structure.directories : structure, '', [...nodes]);

        pointer.files = pointer.files || {};
        pointer.files = {
            ...pointer.files,
            [file]: 1
        };
    });

    return structure;
};

function findDirectory(pointer, subPath, nodes) {
    if (nodes.length === 0) {
        if (subPath) {
            pointer[subPath] = {};
            pointer = pointer[subPath];
        };
        return pointer;
    };

    let newPath = `${subPath}/${nodes[0]}`;
    nodes.shift();

    if (pointer[newPath]) {
        pointer = pointer[newPath];

        if (nodes.length >= 1) {
            pointer.directories = pointer.directories || {};
            pointer = pointer.directories;
        };

        newPath = '';
    };

    return findDirectory(pointer, newPath, nodes);
};

const structure = buildStructure(paths);
console.log(structure);
.as-console-wrapper { min-height: 100%!important; top: 0; }

说明:

这最终比我开始研究它时想象的要复杂得多(也更有趣)。一旦开始连接目录,操作顺序就很重要。

从 开始buildStructure,我们映射路径数组以捕获没有前导斜杠的任何条目。然后,根据它们引用的目录数量对它们进行排序。这样我们就可以确定我们是从结构的顶部向底部工作的。

将每个路径分成一个节点数组,然后弹出文件字符串。你留下了这样的东西:

const nodes = ['doc', 'data'];
const file = 'main.js';

现在我们必须通过这些节点findDirectory来查找/创建文件的位置。该变量pointer用于跟踪我们在structure对象中的位置,并且我们对指针所做的任何更改都将在结构中复制,因为它们共享引用相等。

findDirectory函数递归地处理每个节点以逐渐建立路径回到其全长。每当我们创建一个已经存在于structures 目录中的路径时,我们就会在其中移动并重新开始构建路径以尝试找到下一个路径。如果我们找不到它,那么我们就有了一个全新的目录。目的是当我们退出函数时总是在正确的目录中结束 - 如果需要,可以在此过程中创建它。

为简化起见,假设我们只有两条记录路径:

const paths = [
  'doc/data/main.js',
  'doc/data/dandu/sdasa.js'
];

对于第一条路径,findDirectory将进行三遍。这些是每次传递时将提供给它的参数:

pointer = structure.directories > same > same

subPath = '' > '/doc' > '/doc/data'

nodes = ['doc', 'data'] > ['data'] > []

我们从来没有匹配过,所以当函数退出时,它会在structure.directories. 现在,第二条路径将进行四次传递:

pointer = 
  structure.directories > 
  same > 
  structure.directories./doc/data.directories > 
  same

subPath = '' > '/doc' > '' > '/dandu' 

nodes = ['doc', 'data', 'dandu'] > ['data', 'dandu'] > ['dandu'] > []

如您所见,在第二遍中,我们创建了/doc/data确实存在于structure.directories. 所以我们进入它,因为有更多的节点要处理,我们在那里创建一个新的目录对象并输入它。如果没有更多节点要处理,我们就知道我们已经到达了正确的级别,这将是不必要的。从这里开始,只需重新构建路径并重复该过程即可。

一旦我们在正确的目录中,我们可以将文件直接放在指针上,它将在结构上注册。一旦我们移动到下一条路径,指针将再次指向structure.directories

如果没有要处理的节点(仅文件名) -findDirectory而是传递整个结构对象,文件将进入对象的顶层。


希望这可以很好地解释事情并对您有用。我很享受这方面的工作,并且很高兴收到有关如何改进它的任何建议。


推荐阅读