js获取日期数组并集

比如:a = [
['2022-5-1', '2022-5-5'],
['2022-5-3', '2022-5-10'],
['2022-6-1', '2022-6-5'],
]

那么最终并集应该是:
a = [
['2022-5-1', '2022-5-10'],
['2022-6-1', '2022-6-5'],
]

阅读 2.6k
4 个回答

你这个用原生JS处理会很麻烦,建议借助第三方JS工具库来处理,如day.js、moment.js、XDate等专门处理日期时间的。

function mergeDates (dates = []) {
    return Array.from(dates)
        .sort(([m], [n]) => Date.parse(m) - Date.parse(n))
        .reduce(
        (merged, date) => {
            const last = merged[merged.length - 1] ?? [];
            const [, l1] = last.map(Date.parse), [d0, d1] = date.map(Date.parse);

            if (!(l1 >= d0)) {
                merged.push(date);
            }
            else
            if (l1 <= d1) {
                last[1] = date[1];
            }

            return merged;
        },
        [],
    );
}
const unionData = (arr)=>{
    if(arr.length<2) return arr;
    arr = [...arr].sort((a,b)=>new Date(a[0]) - new Date(b[0]))
    return arr.slice(1).reduce((ar, period)=>{
        const lastItemEnd = ar[ar.length-1][1]
        if(new Date(period[0])<new Date(lastItemEnd)) {
            if(new Date(period[1])>new Date(lastItemEnd)) {
                ar[ar.length-1][1] = period[1]
            }
        } else {
            ar.push(period)
        }
        return ar
    }, arr.slice(0,1))
}

这是先排序的处理办法

function mergeDate(list) {
    return list
        // 用 Date.parse 把字符串计算成可以比较的数值
        // 但为了能保留原值,所以分别用 origin 和 value 来保存原值和解析后的值
        .map(([begin, end]) => ({
            origin: [begin, end],
            value: [Date.parse(begin), Date.parse(end)]
        }))
        // 按起始时间排序,排序之后合并过程会更简单
        .sort((a, b) => (a.value[0] - b.value[0]))
        // 合并过程,
        .reduce((acc, it) => {
            // 在已处理的列表中查找可以合并的数据,只有最后 1 顶才存在合并的可能
            // 证明:如果倒数第 2 项(设为 t2)也可以合并,则新项(设为 n)与之关系:n.begin <= t2.end,
            //     因为已经排过序,所以最后一顶(设为 t1),有 t1.begin < n.begin,
            //     可得 t1.bgin < t2.end。如果是这样,那么 t1 已经合并入 t2,不会存在 t1
            const last = acc[acc.length - 1];

            // 上面的证明也说明了比较逻辑,即 n.begin <= t1.end 的时候,存在交集,可合并
            if (last && last.value[1] >= it.value[0]) {
                last.origin[1] = it.origin[1];
                last.value[1] = it.value[1];
            } else {
                // 不可合并的时候,作为一个新项加入结果集
                acc.push(it);
            }
            return acc;
        }, [])
        // 最后把原值数组拿出来用,其他的辅助数据不再需要
        .map(({ origin }) => origin);
}

为了保留之前的顺序,这里有一个不排序的办法(顺便给个用例)

const a = [
    ["2022-5-1", "2022-5-5"],
    ["2022-5-3", "2022-5-10"],
    ["2022-4-3", "2022-4-5"],
    ["2022-4-7", "2022-4-10"],
    ["2022-4-5", "2022-4-7"],
    ["2022-6-1", "2022-6-5"],
];

function mergeDate(list) {
    // 还是先把值解析出来用于计算,原值和解析值都保留着
    list = list.map(([begin, end]) => ({ origin: [begin, end], value: [begin, end].map(Date.parse) }));

    // 从第 1 项开始,与之前的所有项进行比较
    for (let i = 1; i < list.length; i++) {
        const next = list[i];
        // 与之前的所有项进行比较
        for (let j = 0; j < i; j++) {
            const it = list[j];
            // 比较就是简单的判断是否存在交集
            if (next.value[0] >= it.value[0] && next.value[0] <= it.value[1]) {
                // 如果存在交集,合并。即更新找到的那个节点的结束时间
                it.origin[1] = next.origin[1];
                it.value[1] = next.value[1];
                // 再将当前节点 (next) 删除掉(因为已经合并了)
                list.splice(i, 1);
                // 由于合并目标节点的结时间变了,要从这个节点之后重新判断是否有交集
                // 如果把 i 重置到目标节点,这样下次循环会从目标节点的下一个开始
                i = j;
                // 当然合并之后节点在子循环中不需要再去处理了
                break;
            }
        }
    }
    return list.map(({ origin }) => origin);
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题