需求:一个文件夹被复制,要求新文件夹的名字合适且不重复。
在 nodejs后端,我在实现文件夹复制的功能时,发现简单的给原名子添加 “的副本” 作为新名字,复制多次的话得到的“...的副本的副本...”特别长,且没有什么意义,于是决定模仿 mac 的文件复制的命名的表现,写了个命名算法。
关于mac文件重命名的规则
- 每次复制文件,如果不是“的副本”结尾的文件,复制的新文件会加上“的副本( n)”,这里的n是自然数字,不含前导零。括号是指可能有,也可能没有空格和数字
- 如果原文件名是“的副本”结尾,复制出的新文件的名字会加上“ n”。
- 如果原文件名是“的副本 n”,复制出的新文件名结尾的数字会比原名的索引大且向上最接近原名且不和其他文件重名。
- 复制时,如果原文件夹是“的副本”加上有前导0时,会去掉前导0,并应用上一条规则。
- 文件夹视为文件,即创建的新文件名不能和当前文件夹下的文件重名
只要明确了重命名的详细规则,我们就很容易明确如何算法的实现细节。
实现
下面是具体的实现,不过是针对文件夹的,其实换成文件也完全没问题。
确定原文件夹的索引
首先获取原文件夹名字,然后定义一个 index,用于确定原文件“xx的副本 n”的 n 的取值。
let regExpName = oFolder.name;
let index;
接着我们确定原文件夹名称的 n 到底是哪个,n 的获取方式如下表。
原文件夹名 | n |
---|---|
xx | 0 |
xx的副本 | 1 |
xx的副本 2 | 2 |
xx的副本 002 | 2 |
... | ... |
注:不会复制出名为“xx的副本 1”的文件夹。
为了读取名字结尾的数字,使用了正则表达式。
if (/的副本( \d+)?$/g.test(oFolder.name)) {
let r = / \d+$/g.exec(oFolder.name);
if (r == null) {
// 文件夹名格式: xx的副本
index = 1;
} else {
// 格式:xx的副本 n (n可以包含前导0)
index = parseInt(r[0]);
regExpName = oFolder.name.slice(0, r.index);
}
} else {
// 格式:xx
regExpName += '的副本';
index = 0;
}
通过正则表达式,已经知道了原文件夹的名字符合的是哪一种情况,并给 index 赋予了正确的值。这里还对 regExpName 进行操作,使其为“的副本”结尾,以方便接下来的查询其他文件夹的操作作为正则表达式的一部分。
得到符合“xx的副本 n”的所有文件夹名字
接下来是找出原文件夹所在文件夹系的所有文件夹中,符合 /^${regExpName}的副本( [1-9][0-9]*)?$/
的所有名字,正则表达式的意思是要求符合“xx的副本”或者“xx的副本 n”(注意这个 n 是不含前导0的数字)。
let checkedNameFolders = await models.Folder.findAll({
where: {
parentId: oFolder.parentId,
name: {
$regexp: `^${regExpName}的副本( [1-9][0-9]*)?$`
}
}
});
let checkedName = checkedNameFolders.map(item => {
let name = item.name;
let e = /\d+$/g.exec( name );
if(e == null) return 1;
return parseInt( e[0] );
});
// 去重并排序
checkedName = [...new Set(checkedName)].sort((a, b) => a - b);
这里我是用了 sequelize 获取了 数据库中所有名字符合该正则表达式的文件夹对象,取得了文件夹对象的 name,并判断 name 的结尾是否有数字,有数字的话,就提取这个成数字,放到 checkedName 数组内,没有的话就返回1。最后我们对这个数组进行去重和升序,理论上去重操作是不需要的,但谁知道数据库里面会发生什么事情呢。不管怎样,稳妥起见做个去重。
循环找出可以使用的索引
let newIndex = 1;
for (let i = index + 1; ;i++) {
if (!checkedName.includes(i)) {
newIndex = i;
break;
}
}
我们会从原文件夹的索引+1后,进行递增并判断该数组里时候含有这个值,一旦发现没有,就确定了我们的复制文件夹索引,停止循环。(理论上这里的算法是可以优化的,因为我们之前已经给数组排序了,而inclues方法每次都要遍历数组效率并不高,在文件数量很多的情况下可能不好使了。)
根据确定后的可用索引映射回文件名
根据我之前给出的表格,从索引得出最终的名字。
let newName;
switch (newIndex) {
case 1:
newName = `${regExpName}的副本`;
break;
default:
newName = `${regExpName}的副本 ${newIndex}`;
break;
}
到了这里,我们就获得了想要的新文件夹的名字了。
网友评论