文件夹复制的命名算法(仿mac)

需求:一个文件夹被复制,要求新文件夹的名字合适且不重复。

在 nodejs后端,我在实现文件夹复制的功能时,发现简单的给原名子添加 “的副本” 作为新名字,复制多次的话得到的“...的副本的副本...”特别长,且没有什么意义,于是决定模仿 mac 的文件复制的命名的表现,写了个命名算法。 这个算法的代码实现很简单,难的是理解需求和详细规则(即为什么要这么做)。

关于mac文件重命名的规则

  • 每次复制文件,如果不是“的副本”结尾的文件,复制的新文件会加上“的副本( n)”,这里的n是自然数字,不含前导零。括号是指可能有,也可能没有空格和数字
  • 如果原文件名是“的副本”结尾,复制出的新文件的名字会加上“ n”。
  • 如果原文件名是“的副本 n”,复制出的新文件名结尾的数字会比原名的索引大且向上最接近原名且不和其他文件重名
  • 复制时,如果原文件夹是“的副本”加上有前导0时,会去掉前导0,并应用上一条规则。
  • 文件夹视为文件,即创建的新文件名不能和当前文件夹下的文件重名

只要明确了重命名的详细规则,我们就很容易明确如何算法的实现细节。

2019.3.25更新

优化 重写了代码,包装成一个方法,可以自定义后缀名。

/**
 * 文件复制的命名算法
 * @param {string} oName 被复制的文件的名称
 * @param {Array} filenames 目录下的所有文件名数组
 * @param {string} suffix 后缀(默认为'的副本')
 */
const getCopyedName = (oName, filenames, suffix = '的副本') => {
    let index;
    let root;   // 词根
    let match = new RegExp(`${suffix}( \\d+)?$`).exec(oName);
    console.log(match);

    // 1. 求出 oName 的 索引值 和 词根(不含后缀和索引的源文件名 )
    if (match) {
        if (match[1]) index = parseInt(match[1]);
        else index = 0;
        
        root = oName.slice(0, match.index);
    } else {
        index = 0;
        root = oName;
    }

    console.log('被复制文件的词根:', root);
    console.log('被复制文件的索引:', index);

    // 2. 根据“词根”,获得当前目录下的相同词根 索引列表。纯词根(没有后缀)不在范围内,因为复制的新名字必然有后缀。
    const reg = new RegExp(`^${root}${suffix}( [1-9][0-9]*)?$`);   // 注意这里要求为非0开始的数字
    let indexs = [];
    filenames.forEach((item) => {
        match = reg.exec(item);
        if (match) {
            const i = match[1] ? parseInt(match[1]) : 1;
            indexs.push(i);
        }
    })
    // indexs 理论上不会有重复的值。如果你的目录可能会有重名的文件,请做“去重”处理。

    // 3. 寻找可用的 index
    // 从 indexs 找出与 index + 1 相等的值,如果不存在,新的文件名即为 root + suffix + (index+1)
    // 如果存在,继续找出 index + 2 的值,直到发现一个 index + n 在 数组中不存在。
    indexs.sort((a,b)=> a - b);
    console.log('目录下的同词根的文件index(排序):', indexs);
    index++; 
    for (let i = 0, len = indexs.length; i < len; i++) {
        if (indexs[i] == index) index++;
        else if (indexs[i] > index) break;
    }

    console.log('复制后的文件名:', root + suffix + ' ' + index);

    return root + suffix + ' ' + index;
}

// 测试
const filenames = [
    'aa的副本 23', 'aa的副本 003', 'bb', 'aa', 'aa的副本', '的副本', 'aa的副本 12',
    'aa的副本 13', 'aa的副本 14'
];
getCopyedName('aa的副本 12', filenames);

下面的内容是很久以前写的,看思路就行了,旧版的代码(不优雅)不要太过在意。

实现

下面是具体的实现,不过是针对文件夹的,其实换成文件也完全没问题。

确定原文件夹的索引

首先获取原文件夹名字,然后定义一个 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 = 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;
}

到了这里,我们就获得了想要的新文件夹的名字了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,917评论 2 89
  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,175评论 1 44
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 【日文】人学ばざれば道を知らぬ (ひとまなばざればみちをしらぬ) 【中文】人不学,不知道 【感悟】不...
    行禅者阅读 313评论 0 0
  • 【柳梢青·上海】(新韵) 座落江东。中西交汇,今古通融。不夜申城,明珠璀璨,指向苍穹。 恰明月共潮生,看此地,腾飞...
    冷枫叶阅读 509评论 2 4