Javascript:对齐并行数组索引,避免重叠



我有一个数组列表,其中同一项可能出现在不同的列表中(但不在同一列表中(。我正在尝试对数组进行排序,以便匹配项在所有数组中具有相同的索引。我也在尽可能地填补空白,但如果有些位置没有定义也没关系。

它的用途

这是为了管理日历中日常事件的UI。在我的案例中,myWrapper是一个,其中每个位置都是一个天。里面的每个数组都是当天可能发生的事件的列表。由于一个事件可能跨越一天以上(假设一个简单的情况,全天事件为2天(,我们需要该事件出现在相同的位置,以便以与前一天连续的方式呈现。虽然它不是真正可见的,但它变成了一个占位符,前一天的块将是原来的两倍长。

条件

  1. 所有重复数据始终是顺序的,因此,如果某个元素是重复数据,则前一个数组中肯定有一个副本
  2. 每个阵列中每个项目只能出现一次(重复数据只在不同的阵列中(
  3. foo数组可能具有不同的长度
  4. foo数组始终存在,在最坏的情况下它是空的
  5. 只要目标完成,foo数组中的顺序就不相关
  6. 无法对包装数组进行排序

输入

const myWrapper [
{ foo: [] },
{ foo: ['A', 'B', 'C', 'D'] },
{ foo: ['X', 'A', 'E', 'C'] },
{ foo: ['X', 'F', 'C', 'G', 'H'] },
{ foo: ['C'] }
];

所需输出

const myWrapper [
{ foo: [] },
{ foo: ['B', 'A', 'C', 'D'] },
{ foo: ['X', 'A', 'C', 'E'] },
{ foo: ['X', 'F', 'C', 'G', 'H'] },
{ foo: [undefined, undefined, 'C'] }
];

有没有一种巧妙的方法来实现这一点?

我的尝试,从右边排序,并使用tmp变量交换项目。这似乎不太好用,因为下面的位置有时会移动之前的项目,扰乱上一次迭代中的顺序集。

let tmp;
myWrapper.forEach((item, wrapperIndex) => {
item.foo.forEach((currentFoo, fooIndex) => {
// Search for match in the previous foo
if(myWrapper[wrapperIndex - 1]) {
const prevFoo = myWrapper[wrapperIndex - 1].foo;
const prevIndex = prevFoo.indexOf(item);
if (prevIndex >= 0 && prevIndex !== fooIndex) {
tmp = prevFoo[fooIndex];
prevFoo[fooIndex] = prevEvents[prevIndex];
prevFoo[prevIndex] = tmp;
}
}
});
});

编辑:问题还在于循环对单个项(实际上是对象,我使用字符串来简化(执行其他操作,所以我不能从右边移动(当前项(。

输出通过Angular使用2嵌套的*ngFor进行渲染,因此最终结果必须保持此数组结构。

编辑

一个更简单的解决方案,使用与我原始答案相同的重复映射,但使用简单的交换算法将重复值移动到相应的索引中。

const myWrapper = [
{ foo: [] },
{ foo: ['A', 'B', 'C', 'D'] },
{ foo: ['X', 'A', 'E', 'C'] },
{ foo: ['X', 'F', 'C', 'G', 'H'] },
{ foo: ['C'] }
];
// find dupes and create map of indexes
let
seen = new Set,
dupeMap = {}, d = 0;
for (const { foo } of myWrapper) {
for (const x of foo) {
seen.has(x)
? dupeMap[x] ??= d++
: seen.add(x);
}
}
// swap duplicates into appropriate indexes
for (const { foo } of myWrapper) {
let
i, j, temp;
for (i = 0; i < foo.length; i++) {
j = dupeMap[foo[i]];
if (j !== undefined && j !== i) {
temp = foo[i];
foo[i] = foo[j];
foo[j] = temp;
i--;
}
}
}
myWrapper.forEach(({ foo }) => console.log(`{foo: [${foo.map(e => e ?? ' ').join(', ')}]}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }


原始答案

这里有一个相当简单的解决方案,可以避免排序。它首先创建一个具有重复值的props的对象,并将该值在最终数组中的预期索引作为值。

然后,它对每个foo数组进行迭代,通过重复映射中的索引将重复值放置在临时数组中,并将唯一值推送到第二个临时数组。

在退出之前,它尝试从唯一数组中的任何值回填临时重复数组中的孔。

这会使myWrapper阵列发生突变。

const myWrapper = [
{ foo: [] },
{ foo: ['A', 'B', 'C', 'D'] },
{ foo: ['X', 'A', 'E', 'C'] },
{ foo: ['X', 'F', 'C', 'G', 'H'] },
{ foo: ['C'] }
];
// find dupes and create map of indexes
let
seen = new Set,
dupeMap = {}, d = 0;
for (const { foo } of myWrapper) {
for (const x of foo) {
seen.has(x)
? dupeMap[x] ??= d++
: seen.add(x);
}
}
for (const obj of myWrapper) {
// collect elements into dupe/unique temp arrays
const
{ foo } = obj,
dTemp = [], uTemp = [];
for (const e of foo) {
(e in dupeMap)
? dTemp[dupeMap[e]] = e
: uTemp.push(e);
}
// backfill empty indexes in dupe temp array
for (const [i, d] of dTemp.entries()) {
if (d === undefined && uTemp.length) {
dTemp.splice(i, 1, uTemp.shift());
}
}
// concat back into foo
obj.foo = [...dTemp, ...uTemp];
}
myWrapper.forEach(({ foo }) => console.log(`{foo: [${foo.map(e => e ?? ' ').join(', ')}]}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }

您的重构依赖于重复项从一个数组到下一个数组的连续性,以保持一致的索引(因为它只关注一个数组后面(。如果打破这种连续性,索引将重置。

我复制了你的代码片段,并添加了一个"突破性"短数组。

function arrangeParallelItems(wrapper) {
// start from the second element, so we're sure we can backlook
wrapper.slice(1).forEach((item, itemIndex) => {
let i = -1,
dupeIndex,
tmp;
while (++i < item.foo.length) {
if (!item.foo[i]) {
// Skip empty values
continue;
}
dupeIndex = wrapper[itemIndex].foo.indexOf(item.foo[i]);
if (dupeIndex > -1 && dupeIndex !== i) {
tmp = item.foo[i];
item.foo[i] = item.foo[dupeIndex];
item.foo[dupeIndex] = tmp;
i--;
}
}
});
}
const myWrapper = [
{ foo: [] },
{ foo: ['A', 'B', 'C', 'D'] },
{ foo: ['H'] },
{ foo: ['X', 'A', 'E', 'C'] },
{ foo: ['A', 'B', 'C', 'D'] },
{ foo: ['F', 'C', 'G', 'H', 'X'] },
{ foo: ['C'] }
];
arrangeParallelItems(myWrapper);
myWrapper.forEach(({ foo }) =>
console.log(`{foo: [${foo.map(e => e ?? ' ').join(', ')}]}`)
)
.as-console-wrapper { max-height: 100% !important; top: 0; }

最后,我从@pilchard的工作开始,提出了一个更简单的解决方案。

使用简单字符串

function arrangeParallelItems(wrapper) {
// start from the second element, so we're sure we can backlook
wrapper.slice(1).forEach((item, itemIndex) => {
let i = -1,
dupeIndex,
tmp;
while (++i < item.foo.length) {
if (!item.foo[i]) {
// Skip empty values
continue;
}
dupeIndex = wrapper[itemIndex].foo.indexOf(item.foo[i]);
if (dupeIndex > -1 && dupeIndex !== i) {
tmp = item.foo[i];
item.foo[i] = item.foo[dupeIndex];
item.foo[dupeIndex] = tmp;
i--;
}
}
});
}
const myWrapper = [
{ foo: [] },
{ foo: ['A', 'B', 'C', 'D'] },
{ foo: ['X', 'A', 'E', 'C'] },
{ foo: ['X', 'F', 'C', 'G', 'H'] },
{ foo: ['C'] }
];
arrangeParallelItems(myWrapper);
myWrapper.forEach(({ foo }) =>
console.log(`{foo: [${foo.map(e => e ?? ' ').join(', ')}]}`)
)

使用对象(此处的id是匹配项目的唯一密钥(

function arrangeParallelItems(wrapper) {
// start from the second element, so we're sure we can backlook
wrapper.slice(1).forEach((item, itemIndex) => {
let i = -1, dupeIndex, tmp;
while (++i < item.foo.length) {
if (!item.foo[i]) {
// Skip empty values
continue;
}
dupeIndex = wrapper[itemIndex].foo.findIndex((e) => e.id === item.foo[i].id);
if (dupeIndex > -1 && dupeIndex !== i) {
tmp = item.foo[i];
item.foo[i] = item.foo[dupeIndex];
item.foo[dupeIndex] = tmp;
i--;
}
}
});
}

这里有更多案例的闪电战

最新更新