如何正确地替换数组中对象的值并按该值进行排序

huangapple go评论73阅读模式
英文:

How to properly replace value of an object in an array and sort it by that value

问题

我想要更改draggedItempriority,使其与targetItempriority相同。

如果它们的priority相同,它们应该正确更新(draggedItempriority应该变为targetItempriority,而targetItempriority应该增加1,因为它与draggedItempriority相同)。

所有其他项的priority也应该正确更新(基于新的draggedItemtargetItempriority),最终结果应该是一个包含所有对象的数组,它们的priority各不相同。

因此,我有一个如下的函数:

const updatePriorities = (data, draggedItem, targetItem) => {
  const draggedItemPriority = draggedItem?.priority;
  const targetItemPriority = targetItem?.priority;

  if (draggedItemPriority === undefined || targetItemPriority === undefined || draggedItemPriority === null || targetItemPriority === null) {
    return data;
  }

  const minPriority = Math.min(draggedItemPriority, targetItemPriority);
  const maxPriority = Math.max(draggedItemPriority, targetItemPriority);

  const newData = map(data, (item) => {
    if (item.priority === draggedItemPriority) {
      return { ...item, priority: targetItemPriority };
    } else if (item.priority >= minPriority && item.priority <= maxPriority) {
      const priorityOffset = targetItemPriority < draggedItemPriority ? 1 : -1;
      return { ...item, priority: item.priority + priorityOffset };
    } 
    return item;
  });

  return orderBy(newData, 'priority');
};

因此,如果数组中的所有对象的priority都不同,它可以正常工作。例如,对于以下数据:

const mockedData = [
  { name: 'Item 0', priority: 0 },
  { name: 'Item 1', priority: 1 },
  { name: 'Item 2', priority: 2 },
  { name: 'Item 3', priority: 3 },
  { name: 'Item 4', priority: 4 },
  { name: 'Item 5', priority: 5 }
];
const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];
const result = updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1);
// 结果
[
  { name: 'Item 0', priority: 0 },
  { name: 'Item 2', priority: 1 },
  { name: 'Item 1', priority: 2 },
  { name: 'Item 3', priority: 3 },
  { name: 'Item 4', priority: 4 },
  { name: 'Item 5', priority: 5 }
];

我得到了正确的结果,priority被正确更新,Item 2移到了第二个位置(得到了priority为1),Item 1移到了第三个位置(得到了priority为2)。这一切都很好。

但是,当数组中有两个具有相同priority的对象时,例如:

const mockedData = [
  { name: 'Item 0', priority: 0 },
  { name: 'Item 1', priority: 1 },
  { name: 'Item 2', priority: 1 },
  { name: 'Item 3', priority: 3 },
  { name: 'Item 4', priority: 4 },
  { name: 'Item 5', priority: 5 }
];
const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

那么它的行为就不符合期望,draggedItemtargetItempriority不会更新。

您想要正确更新它该怎么做呢?

提前感谢您的帮助。

英文:

I want to change priority of a draggedItem with a priority of a targetItem.

If they are the same, they should be updated correctly (draggedItem should get priority of targetItem and targetItem priority should be increased by one, because it is the same as priority of draggedItem).

The priority of all other items should also be updated correctly (based on new draggedItem and targetItem priority) and in the end result I should have an array of objects where all objects have different priority.

Thus, I have a function as follows:

const updatePriorities = (data, draggedItem, targetItem) =&gt; {
  const draggedItemPriority = draggedItem?.priority;
  const targetItemPriority = targetItem?.priority;

  if (draggedItemPriority === undefined || targetItemPriority === undefined || draggedItemPriority === null || targetItemPriority === null) {
    return data;
  }

  const minPriority = Math.min(draggedItemPriority, targetItemPriority);
  const maxPriority = Math.max(draggedItemPriority, targetItemPriority);

  const newData = map(data, (item) =&gt; {
    if (item.priority === draggedItemPriority) {
      return { ...item, priority: targetItemPriority };
    } else if (item.priority &gt;= minPriority &amp;&amp; item.priority &lt;= maxPriority) {
      const priorityOffset = targetItemPriority &lt; draggedItemPriority ? 1 : -1;
      return { ...item, priority: item.priority + priorityOffset };
    } 
    return item;
  });

  return orderBy(newData, &#39;priority&#39;);
};

Thus if all objects in the array have different priority, it works fine. Thus with data as follows:

const mockedData = [
  { name: &#39;Item 0&#39;, priority: 0 },
  { name: &#39;Item 1&#39;, priority: 1 },
  { name: &#39;Item 2&#39;, priority: 2 },
  { name: &#39;Item 3&#39;, priority: 3 },
  { name: &#39;Item 4&#39;, priority: 4 },
  { name: &#39;Item 5&#39;, priority: 5 }
];
const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];
const result = updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1)
// The result
[
  { name: &#39;Item 0&#39;, priority: 0 },
  { name: &#39;Item 2&#39;, priority: 1 },
  { name: &#39;Item 1&#39;, priority: 2 },
  { name: &#39;Item 3&#39;, priority: 3 },
  { name: &#39;Item 4&#39;, priority: 4 },
  { name: &#39;Item 5&#39;, priority: 5 }
];

I get correct result, the priority is updated correctly and Item 2 came to second place (got priority 1) and Item 1 went to third place (got priority 2). That is all fine.

But when I have two objects with the same priority in the array. For example:

const mockedData = [
  { name: &#39;Item 0&#39;, priority: 0 },
  { name: &#39;Item 1&#39;, priority: 1 },
  { name: &#39;Item 2&#39;, priority: 1 },
  { name: &#39;Item 3&#39;, priority: 3 },
  { name: &#39;Item 4&#39;, priority: 4 },
  { name: &#39;Item 5&#39;, priority: 5 }
];
const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

Then it does not work as expected, want the priority of draggedItem and targetItem is not updated.

I should get next result:

[
  { name: &#39;Item 0&#39;, priority: 0 },
  { name: &#39;Item 2&#39;, priority: 1 },
  { name: &#39;Item 1&#39;, priority: 2 },
  { name: &#39;Item 3&#39;, priority: 3 },
  { name: &#39;Item 4&#39;, priority: 4 },
  { name: &#39;Item 5&#39;, priority: 5 }
];

What should I do to update it correctly?

Thanks in advance.

答案1

得分: 1

找到draggedItem的索引并使用Array.toSplice()(请查看链接中的支持),或者使用Array.filter()来删除它。然后在新的withoutDragged数组中找到targetItem的索引,并将draggedItem插入到该索引位置。映射数组,并使用索引作为所有项目的新priority

const updatePriorities = (data, draggedItem, targetItem) => {
  const draggedIndex = data.indexOf(draggedItem);
  
  const withoutDragged = data.toSpliced(draggedIndex, 1);
  
  const targetIndex = withoutDragged.indexOf(targetItem);
  
  const withDragged = withoutDragged.toSpliced(targetIndex > draggedIndex ? targetIndex + 1 : targetIndex, 0, draggedItem);
  
  return withDragged.map((o, priority) => ({
    ...o,
    priority
  }));
}

const mockedData = [
  { name: 'Item 0', priority: 0 },
  { name: 'Item 1', priority: 1 },
  { name: 'Item 2', priority: 1 },
  { name: 'Item 3', priority: 3 },
  { name: 'Item 4', priority: 4 },
  { name: 'Item 5', priority: 5 }
];

const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

const result = updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1)

console.log(result);

rozsazoltan的评论指出两者之间的顺序应该取决于原始优先级的差异:

如果将移动元素放在优先级较高的元素旁边,它应该占据该元素的位置。在当前代码中,它被移动到前一个位置(-1)。 (尝试将Item-2与Item-5交换位置。)我对您的代码进行了修改,以便在这种情况下也能正常工作。

如果是这样,这是由rozsazoltan更新的代码:

const updatePriorities = (data, draggedItem, targetItem) => {
  const draggedIndex = data.indexOf(draggedItem);
  
  const withoutDragged = data.toSpliced(draggedIndex, 1);
  
  const targetIndex = withoutDragged.indexOf(targetItem);
  
  const withDragged = withoutDragged.toSpliced(targetIndex > draggedIndex ? targetIndex + 1 : targetIndex, 0, draggedItem);
  
  return withDragged.map((o, priority) => ({
    ...o,
    priority
  }));
}

const mockedData = [
  { name: 'Item 0', priority: 0 },
  { name: 'Item 1', priority: 1 },
  { name: 'Item 2', priority: 1 },
  { name: 'Item 3', priority: 3 },
  { name: 'Item 4', priority: 4 },
  { name: 'Item 5', priority: 5 }
];

const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

const result = updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1)

console.log(result);
英文:

Find the index of the draggedItem and remove it using Array.toSplice() (see support in link), or Array.filter(). Then find the index of targetItem in the new withoutDragged array, and splice the draggedItem to that index. Map the array, and use the index as the new priority for all items.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const updatePriorities = (data, draggedItem, targetItem) =&gt; {
  const draggedIndex = data.indexOf(draggedItem);
  
  const withoutDragged = data.toSpliced(draggedIndex, 1);
  
  const targetIndex = withoutDragged.indexOf(targetItem);
  
  const withDragged = withoutDragged.toSpliced(targetIndex, 0, draggedItem);
  
  return withDragged.map((o, priority) =&gt; ({
    ...o,
    priority
  }));
}

const mockedData = [
  { name: &#39;Item 0&#39;, priority: 0 },
  { name: &#39;Item 1&#39;, priority: 1 },
  { name: &#39;Item 2&#39;, priority: 1 },
  { name: &#39;Item 3&#39;, priority: 3 },
  { name: &#39;Item 4&#39;, priority: 4 },
  { name: &#39;Item 5&#39;, priority: 5 }
];

const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

const result = updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1)

console.log(result);

<!-- end snippet -->

rozsazoltan's comment states that the order between the two should be a result of the original priority difference:

> If you place the moving element next to a higher-priority element, it
> should take the position of that element. In the current code, it is
> being moved to one position before (-1). (Try swapping Item-2 with
> Item-5.) I have made a modification to your code so that it will work
> properly in this case as well.

If that is so, this is the code updated by rozsazoltan:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

const updatePriorities = (data, draggedItem, targetItem) =&gt; {
  const draggedIndex = data.indexOf(draggedItem);
  
  const withoutDragged = data.toSpliced(draggedIndex, 1);
  
  const targetIndex = withoutDragged.indexOf(targetItem);
  
  const withDragged = withoutDragged.toSpliced(targetIndex &gt; draggedIndex ? targetIndex + 1 : targetIndex, 0, draggedItem);
  
  return withDragged.map((o, priority) =&gt; ({
    ...o,
    priority
  }));
}

const mockedData = [
  { name: &#39;Item 0&#39;, priority: 0 },
  { name: &#39;Item 1&#39;, priority: 1 },
  { name: &#39;Item 2&#39;, priority: 1 },
  { name: &#39;Item 3&#39;, priority: 3 },
  { name: &#39;Item 4&#39;, priority: 4 },
  { name: &#39;Item 5&#39;, priority: 5 }
];

const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

const result = updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1)

console.log(result);

<!-- end snippet -->

答案2

得分: 0

如果您希望即使具有相同的优先级也进行排序,那么不需要考虑优先级,而是需要检查整个元素。我已经相应地修改并注释了您的函数。需要注意的是,在这种情况下,数组中不应该有两个完全相同的元素,尽管这也可以处理,但我现在没有处理它。

解决方案 #1

  • 实际上,这不是一个完美的解决方案。当存在具有相同优先级的元素并且它们恰好位于移动的元素和目标元素之间时,会发生逻辑错误。
/**
 ** 为了与 Lodash 兼容
 */
const { map, orderBy } = _

/**
 ** 函数
 */
const updatePriorities = (data, draggedItem, targetItem) => {
  const draggedItemPriority = draggedItem?.priority;
  const targetItemPriority = targetItem?.priority;

  if (
    draggedItemPriority === undefined ||
    targetItemPriority === undefined ||
    draggedItemPriority === null ||
    targetItemPriority === null
  ) {
    return data;
  }

  // 计算拖动元素在新位置的优先级
  // - 例如 draggedItemPriority = 3, targetItemPriority = 1
  //   那么 3 优先级在新的位置前面移动到 1 之前
  //   旧的 1 改为 2
  //   因此 maxPriority = 2
  const maxPriority =
    Math.max(draggedItemPriority, targetItemPriority) -
    Math.abs(draggedItemPriority - targetItemPriority);

  const newData = map(data, (item) => {
    // 当前项是被拖动的项
    if (item === draggedItem) {
      return { ...item, priority: targetItemPriority };
    }
    // 当前项是目标项(需要 +1 或 -1)
    else if (item === targetItem) {
      const priorityOffset =
        targetItemPriority < draggedItemPriority ||
        targetItemPriority === draggedItemPriority
          ? 1
          : -1;
      return { ...item, priority: item.priority + priorityOffset };
    }
    // 当前项的优先级大于等于 maxPriority(需要 +1)
    else if (item.priority >= maxPriority) {
      return { ...item, priority: item.priority + 1 };
    }
    return item;
  });

  // 按优先级排序
  return orderBy(newData, 'priority');
};

/**
 ** 数据
 */
const mockedData = [
  { name: 'Item 0', priority: 0 },
  { name: 'Item 1', priority: 1 },
  { name: 'Item 2', priority: 1 },
  { name: 'Item 3', priority: 2 },
  { name: 'Item 4', priority: 3 },
  { name: 'Item 5', priority: 4 },
];
const mockedDraggedItem1 = mockedData[2];
const mockedTargetItem1 = mockedData[1];

/**
 ** 测试
 */
console.log(updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1));

解决方案 #2

  • 相反,我提出了一种解决方案,我们将要移动的元素的优先级调整为逻辑上合适的值,以便将其放在目标元素的前面(如果向前移动)或目标元素的后面(如果向后移动) - 为了实现这一点,我只需添加或减去 0.5。然后,使用 orderBy() 函数,我们根据结果数组中的索引对数组进行排序并重新排列优先级
  • 但是,我不同意对相似优先级的元素进行排序,因为它假定我们根据数组中的顺序来显示它们。因此,在处理相似元素时,我们只能根据它们在数组中的索引对它们进行排序。
  • 使用这种解决方案,您始终会重新计算相同元素的优先级,这可能不适合您。如果要保持未移动和非目标元素的优先级,可以修改 orderBy 后的 map 的逻辑。
/**
 ** 为了与 Lodash 兼容
 */
const { map, orderBy } = _

/**
 ** 函数
 */
const updatePriorities = (data, draggedItem, targetItem) => {
  const draggedItemPriority = draggedItem?.priority;
  const targetItemPriority = targetItem?.priority;

  if (
    draggedItemPriority === undefined ||
    targetItemPriority === undefined ||
    draggedItemPriority === null ||
    targetItemPriority === null
  ) {
    return data;
  }
  
  // 获取拖动项和目标项的索引(以便在具有相同优先级时进行检查)
  const draggedItemIndex = data.findIndex((item) => item === draggedItem);
  const targetItemIndex = data.findIndex((item) => item === targetItem);
  
  // 为拖动项设置新的优先级
  // - 如果拖动项向前移动,则新的优先级大于目标优先级
  //   - 或者具有相同优先级并且 targetItemIndex 大于 draggedItemIndex
  // - 否则,拖动项向后移动,所以新的优先级小于目标优先级
  data[draggedItemIndex].priority =
    targetItemPriority > draggedItemPriority ||
    (targetItemPriority === draggedItemPriority && targetItemIndex > draggedItemIndex)
      ? targetItemPriority + 0.5
      : targetItemPriority - 0.5
  
  // 按优先级排序,并根据索引设置新的优先级值
  return map(orderBy(data, 'priority'), (item, index) => {
    item.priority = index
    return item
  })
}

/**
 ** 数据
 */
const mockedData = [
  { name: 'Item 0', priority: 0 },
  { name: 'Item 1', priority: 1 },
  { name: 'Item 2', priority: 1 },
  { name: 'Item 3', priority: 2 },
  { name: 'Item 4', priority: 3 },
  { name: 'Item 5', priority: 4 },
]
const mockedDraggedItem1 = mockedData[2]
const mockedTargetItem

<details>
<summary>英文:</summary>

If you want to sort even with equal priorities, then instead of considering priority, you need to examine the entire element. I have modified and commented your function accordingly. It is important to note that in this case, there should not be two completely identical elements in the array, although this could also be handled, but I did not address it now.

### Solution # 1

- Actually, it&#39;s not a perfect solution. A logical error occurs when there are elements of the same priority and they happen to be between the moved and target elements.

&lt;!-- begin snippet: js hide: true console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    /**
     ** For Lodash Compatibility
     */
    const { map, orderBy } = _

    /**
     ** Function
     */
    const updatePriorities = (data, draggedItem, targetItem) =&gt; {
      const draggedItemPriority = draggedItem?.priority;
      const targetItemPriority = targetItem?.priority;

      if (draggedItemPriority === undefined || targetItemPriority === undefined || draggedItemPriority === null || targetItemPriority === null) {
        return data;
      }

      // Calculate priority of dragged elements on new place
      // - example draggedItemPriority = 3, targetItemPriority = 1
      //   then 3 priority moved before 1 as new 1
      //   and old 1 changed to 2
      //   so maxPriority = 2
      const maxPriority = Math.max(draggedItemPriority, targetItemPriority) - Math.abs(draggedItemPriority - targetItemPriority);

      const newData = map(data, (item) =&gt; {
        // Current item is dragged
        if (item === draggedItem) {
          return { ...item, priority: targetItemPriority };
        }
        // Current item is target (need +1 or -1)
        else if (item === targetItem) {
          const priorityOffset = targetItemPriority &lt; draggedItemPriority || targetItemPriority === draggedItemPriority ? 1 : -1;
          return { ...item, priority: item.priority + priorityOffset };
        }
        // Priority of Current item greater than maxPriority (need +1)
        else if (item.priority &gt;= maxPriority) {
          return { ...item, priority: item.priority + 1 };
        }
        return item;
      });
      
      // Order By Priority
      return orderBy(newData, &#39;priority&#39;);
    };

    /**
     ** Data
     */
    const mockedData = [
      { name: &#39;Item 0&#39;, priority: 0 },
      { name: &#39;Item 1&#39;, priority: 1 },
      { name: &#39;Item 2&#39;, priority: 1 },
      { name: &#39;Item 3&#39;, priority: 2 },
      { name: &#39;Item 4&#39;, priority: 3 },
      { name: &#39;Item 5&#39;, priority: 4 }
    ];
    const mockedDraggedItem1 = mockedData[2];
    const mockedTargetItem1 = mockedData[1];

    /**
     ** Test
     */
    console.log(updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1))

&lt;!-- language: lang-html --&gt;

    &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js&quot; integrity=&quot;sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/script&gt;

&lt;!-- end snippet --&gt;


### Solution # 2

- Instead, I came up with a solution where **we adjust the priority of the element to be moved to a logically appropriate value**, so that it is placed in front of the target element (if moving forward) or behind the target element (if moving backward) - to achieve this, I simply add or subtract 0.5. **Then, using the `orderBy()` function, we sort the array and rearrange the priorities based on the indexes in the resulting array**.
- However, I disagree with sorting elements of similar priority because it assumes that we are displaying them based on their order in the array. Therefore, when dealing with similar elements, we can only sort them based on their index within the array.
- With this solution, you always recalculate the priority of the same elements, which may not be suitable for you. You can modify the logic of the map following orderBy if you want to maintain the priority of non-moved and non-target elements.

&lt;!-- begin snippet: js hide: false console: true babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    /**
     ** For Lodash Compatibility
     */
    const { map, orderBy } = _

    /**
     ** Function
     */
    const updatePriorities = (data, draggedItem, targetItem) =&gt; {
      const draggedItemPriority = draggedItem?.priority
      const targetItemPriority = targetItem?.priority

      if (draggedItemPriority === undefined || targetItemPriority === undefined || draggedItemPriority === null || targetItemPriority === null) {
        return data
      }
      
      // Get index of draggedItem and targetItem (to can check when have same priorities)
      const draggedItemIndex = data.findIndex((item) =&gt; item === draggedItem)
      const targetItemIndex = data.findIndex((item) =&gt; item === targetItem)
      
      // Set new priority for draggedItem
      // - if draggedItem move to forward, then new priority greater than targetPriority
      //   - OR have same priorities and targetItemIndex greater than draggedItemIndex
      // - else draggedItem move to backward, so new prioirty smaller than targetPriority
      data[draggedItemIndex].priority = targetItemPriority &gt; draggedItemPriority || (targetItemPriority === draggedItemPriority &amp;&amp; targetItemIndex &gt; draggedItemIndex)
        ? targetItemPriority + 0.5
        : targetItemPriority - 0.5
      
      // Order By Priority, and set new priority values by index
      return map(orderBy(data, &#39;priority&#39;), (item, index) =&gt; {
        item.priority = index
        return item
      })
    }

    /**
     ** Data
     */
    const mockedData = [
      { name: &#39;Item 0&#39;, priority: 0 },
      { name: &#39;Item 1&#39;, priority: 1 },
      { name: &#39;Item 2&#39;, priority: 1 },
      { name: &#39;Item 3&#39;, priority: 2 },
      { name: &#39;Item 4&#39;, priority: 3 },
      { name: &#39;Item 5&#39;, priority: 4 }
    ]
    const mockedDraggedItem1 = mockedData[2]
    const mockedTargetItem1 = mockedData[1]

    /**
     ** Test
     */
    console.log(updatePriorities(mockedData, mockedDraggedItem1, mockedTargetItem1))

&lt;!-- language: lang-html --&gt;

    &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js&quot; integrity=&quot;sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/script&gt;

&lt;!-- end snippet --&gt;




</details>



huangapple
  • 本文由 发表于 2023年6月29日 16:26:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/76579293.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定