【问题标题】:Find pair of employees that have worked together the longest on common project找到在共同项目上合作时间最长的一对员工
【发布时间】:2021-08-02 07:31:06
【问题描述】:

有点像duplicate,但不是同时,因为它在 python 中,所以我重新发布它。对于相同的输入,输出也不同。

我终于设法解决了一个复杂的任务,但我想出的算法很慢。
我很确定在最坏的情况下它在 n^2 + n 左右。

它应该遍历一个员工列表,并返回一个在共同项目上一起工作的天数最多的员工对列表。 (s很重要)

输入格式:
EmployeeID, ProjectID, StartDate, EndDate(NULL === today)

示例输入:

1,1,2019-7-4,2020-8-14
1,2,2019-12-25,2020-12-28
1,3,2018-10-12,NULL
1,4,2019-11-16,NULL
1,5,2020-1-5,2020-12-21
2,1,2018-10-3,NULL
2,2,2019-1-16,2020-3-24
2,3,2019-5-22,2019-12-26
2,4,2020-3-7,NULL
2,5,2018-1-24,2019-1-15
3,1,2019-3-21,2020-11-26
3,5,2019-9-28,2020-12-25
4,2,2018-10-22,NULL
4,3,2018-1-27,2020-8-28
5,3,2018-2-3,2020-10-14
5,5,2018-8-4,NULL

输出格式:
员工#1、员工#2、CommonProjectID、DaysWorked

示例输出:

1,2,1,407
1,2,2,90
1,2,3,219
1,2,4,513

这是我的看法,但正如我所说,它很慢,我被要求尝试优化它。现在已经为此工作了 5 个小时,但无法提出更好的建议。

export default function getHighestPair(empl) {
  console.log(empl);
  let pairs = {};
  let daysTogether = {};
  if (empl)
    empl.forEach((el1) => {
      /*
        .slice() is used to exclude the current employee and employees before him
        from the search which slightly reduces complexity. This is because
        employee 5 + employee 13 is the same as employee 13 + employee 5
      */
      empl.slice(empl.indexOf(el1) + 1, empl.length).forEach((el2) => {
        // get start and end date of each of employee
        if (el1[0] !== el2[0]) {
          const startDate1 = new Date(el1[2]);
          const endDate1 = el1[3] === "NULL" ? new Date() : new Date(el1[3]);
          const startDate2 = new Date(el2[2]);
          const endDate2 = el2[3] === "NULL" ? new Date() : new Date(el2[3]);

          // check if they are in the same team (working on the same project)
          if (el1[1] === el2[1]) {
            if (startDate1 <= endDate2 && startDate2 <= endDate1) {
              // calculate the start and end day that we need
              const start = startDate1 <= startDate2 ? startDate2 : startDate1;
              const end = endDate1 <= endDate2 ? endDate1 : endDate2;
              if (end >= startDate2) {
                // put them inside this formula and we get the time they have worked together in days
                const diffTime = Math.abs(end - start);
                const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
                const x = `${el1[0]}${el2[0]}`;

                if (!daysTogether[x]) Object.assign(daysTogether, { [x]: 0 });
                daysTogether[x] = 1 * daysTogether[x] + diffDays;

                if (!pairs[x]) Object.assign(pairs, { [x]: [] });
                pairs[x] = [...pairs[x], [el1[0], el2[0], el1[1], diffDays]];
              }
            }
          }
        }
      });
    });
  /*
    gets the index of the pair that have worked together the longest toghether from
    "daysTogether" which keeps count of the days for each project
  */
  return pairs[
    Object.keys(daysTogether).reduce((a, b) =>
      daysTogether[a] > daysTogether[b] ? a : b
    )
  ];
}

【问题讨论】:

  • 如果之前计算一次工作时间或其他计算可能会快得多
  • @ClausBönnhoff 我怎样才能先计算工作时间?我真的不明白你想说什么。

标签: javascript performance


【解决方案1】:

这个解应该是 O(n log n)。

每条记录

  • 按项目 ID 分组记录
  • 对于该组中的每个存储记录,与当前记录进行比较
    • 比较该组的最长协同工作时间(如果更新时间更长)
  • 将当前记录添加到该组中存储的记录中

deObjectify 清理不是绝对必要的

const data = [
  [1,2,"2019-12-25","2020-12-28"],
  [1,3,"2018-10-12",null],
  [1,4,"2019-11-16",null],
  [1,5,"2020-1-5","2020-12-21"],
  [2,1,"2018-10-3",null],
  [2,2,"2019-1-16","2020-3-24"],
  [2,3,"2019-5-22","2019-12-26"],
  [2,4,"2020-3-7",null],
  [2,5,"2018-1-24","2019-1-15"],
  [3,1,"2019-3-21","2020-11-26"],
  [3,5,"2019-9-28","2020-12-25"],
  [4,2,"2018-10-22",null],
  [4,3,"2018-1-27","2020-8-28"],
  [5,3,"2018-2-3","2020-10-14"],
  [5,5,"2018-8-4",null]
];

const overlap = (e1d1, e1d2, e2d1, e2d2) => {

  const startDate1 = new Date(e1d1);
  const endDate1 = e1d2 === null ? new Date() : new Date(e1d2);
  const startDate2 = new Date(e2d1);
  const endDate2 = e2d2 === null ? new Date() : new Date(e2d2);

  const start = startDate1 < startDate2 ? startDate2 : startDate1;
  const end = endDate1 < endDate2 ? endDate1 : endDate2;

  if (end >= start) {
    const diffTime = Math.abs(end - start);
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    return diffDays;
  }
  
  return 0;
};

const result = data.reduce((acc, el) => {
  let c = acc[el[1]];
  if (!c) {
    c = acc[el[1]] = {
      overlap: 0,
      e1: 0,
      e2: 0,
      data: []
    };
  };
  
  c.data.forEach(d => {
    const o = overlap(d[2], d[3], el[2], el[3]);
    if (o > c.overlap) {
      c.overlap = o;
      c.e1 = d[0];
      c.e2 = el[0];
    }
  });
  
  c.data.push(el);
  return acc;

}, {});

const deObjectify = Object.entries(result).map(([projectId, {e1, e2, overlap}]) => ({e1, e2, projectId, overlap}));

console.log(deObjectify);

console.log("inner workings");
console.log(result);

【讨论】:

  • 非常有趣的解决方案,但我认为您没有得到我想要实现的目标。您给出的结果包含给定项目中最长的重叠。我们想要的是重叠最大的员工对(pair 意味着重叠()返回的不是 0)。当我们得到每对的重叠时,我们将天数相加,看看哪一对的天数最多,然后返回。如果这是我们在重叠后得到的最终对列表,我们只想返回前两个,因为它们总和为 300 天,最后一个只有 250 天。 1,2,1,100 1,2,2,200 2,5,3,250 I希望这是有道理的。
【解决方案2】:

所以这是我的最终代码...

结果:

[ { emA: 1, emB: 2, sum: 1230, details: [{ proj: 1, days: 407 }, { proj: 2, days:  90 }, { proj: 3, days: 219 }, { proj: 4, days: 514 }]} 
, { emA: 1, emB: 5, sum: 1084, details: [{ proj: 3, days: 733 }, { proj: 5, days: 351 }]} 
, { emA: 1, emB: 4, sum: 1055, details: [{ proj: 2, days: 369 }, { proj: 3, days: 686 }]} 
, { emA: 4, emB: 5, sum:  937, details: [{ proj: 3, days: 937 }                        ]} 
, { emA: 1, emB: 3, sum:  758, details: [{ proj: 1, days: 407 }, { proj: 5, days: 351 }]} 
, { emA: 2, emB: 4, sum:  652, details: [{ proj: 2, days: 433 }, { proj: 3, days: 219 }]} 
, { emA: 2, emB: 3, sum:  616, details: [{ proj: 1, days: 616 }                        ]} 
, { emA: 3, emB: 5, sum:  455, details: [{ proj: 5, days: 455 }                        ]} 
, { emA: 2, emB: 5, sum:  384, details: [{ proj: 3, days: 219 }, { proj: 5, days: 165 }]} 
] 

const data = 
  [ [ 1, 1, '2019-7-4',   '2020-8-14'  ]
  , [ 1, 2, '2019-12-25', '2020-12-28' ]  // EmployeeID, ProjectID, StartDate, EndDate(null === today)
  , [ 1, 3, '2018-10-12',  null        ]
  , [ 1, 4, '2019-11-16',  null        ]
  , [ 1, 5, '2020-1-5',   '2020-12-21' ]
  , [ 2, 1, '2018-10-3',   null        ]
  , [ 2, 2, '2019-1-16',  '2020-3-24'  ]
  , [ 2, 3, '2019-5-22',  '2019-12-26' ]
  , [ 2, 4, '2020-3-7',    null        ]
  , [ 2, 5, '2018-1-24',  '2019-1-15'  ]
  , [ 3, 1, '2019-3-21',  '2020-11-26' ]
  , [ 3, 5, '2019-9-28',  '2020-12-25' ]
  , [ 4, 2, '2018-10-22',  null        ]
  , [ 4, 3, '2018-1-27',  '2020-8-28'  ]
  , [ 5, 3, '2018-2-3',   '2020-10-14' ]
  , [ 5, 5, '2018-8-4',    null        ]
  ]

const
  oneDay  = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
, setDate = YMD =>
    {
    let [Y,M,D] = YMD.split('-').map(Number)
    return new Date(Y,--M,D)
    }

// group Employees by project id , change date string to JS newDate

const Proj_Emps = data.reduce( (r,[EmployeeID, ProjectID, StartDate, EndDate])=>
  {
  let stD = setDate(StartDate)
    , enD = EndDate ? setDate(EndDate) :  new Date()
  r[ProjectID] = r[ProjectID] ?? []
  r[ProjectID].push({EmployeeID,stD,enD})
  return r
  }, {})
// combination of pairs of employees per project 

let combination = {}
for (let proj in Proj_Emps) 
for (let i = 0; i < Proj_Emps[proj].length - 1; i++) 
for (let j = i + 1; j < Proj_Emps[proj].length; j++) 
  {
  let emA = Proj_Emps[proj][i]
  let emB = Proj_Emps[proj][j]

  if (( emA.enD <= emB.enD && emA.enD > emB.stD )
    ||( emB.enD <= emA.enD && emB.enD > emA.stD )
    ){
    let 
      D1   = emA.stD > emB.stD ? emA.stD : emB.stD
    , D2   = emA.enD < emB.enD ? emA.enD : emB.enD
    , days = Math.ceil((D2 - D1) / oneDay)
    , key  = `${emA.EmployeeID}-${emB.EmployeeID}`
      ;
    combination[key] = combination[key] ?? { emA: emA.EmployeeID, emB: emB.EmployeeID, sum:0, details:[] }
    combination[key].details.push({proj: Number(proj), days })
    combination[key].sum += days
    }
  } 
 
let Result  =  
  Object.entries(combination)
  .sort((a,b)=> b[1].sum - a[1].sum )
  .map(([k,v])=>v)

Result.forEach(el => console.log( JSON.stringify(el).replaceAll('"','')))
.as-console-wrapper { max-height: 100% !important; top: 0 }
.as-console-row::after { display: none !important; }

我还修复了日期计算的错误(错字)

【讨论】:

  • 没错,Em 2 和 Em 5 确实有 1094(如果你今天不计算的话,是 1093),但它们仍然不是最高的一对。那是因为 Em 1 和 Em 2 有 407 + 90 + 219 + 513 总共 1229 天,因此它们是我们正在寻找的结果。我注意到的另一件事是你没有计算所有对,Em 1 和 Em 2 在 Prj 1 上一起工作了 407 天,依此类推。
  • @IvailoHristov 我更新了我的答案......(不知道如何看待性能......)
  • 解决它的好方法,代码看起来(?)更干净,但它绝不比我的快。我想我会清理一下我的代码并提交它,只是说我无法优化它,但如果可能的话,我很想知道如何。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-06
  • 2015-08-26
  • 2013-02-25
  • 1970-01-01
  • 2022-07-19
  • 2011-03-01
  • 2013-04-12
相关资源
最近更新 更多