| 1632 | } |
| 1633 | |
| 1634 | function getDuplicateErrorFrameRanges(frames) { |
| 1635 | // Build a map: frame line -> sorted list of indices where it occurs |
| 1636 | const result = []; |
| 1637 | const lineToPositions = new SafeMap(); |
| 1638 | |
| 1639 | for (let i = 0; i < frames.length; i++) { |
| 1640 | const positions = lineToPositions.get(frames[i]); |
| 1641 | if (positions === undefined) { |
| 1642 | lineToPositions.set(frames[i], [i]); |
| 1643 | } else { |
| 1644 | positions[positions.length] = i; |
| 1645 | } |
| 1646 | } |
| 1647 | |
| 1648 | const minimumDuplicateRange = 3; |
| 1649 | // Not enough duplicate lines to consider collapsing |
| 1650 | if (frames.length - lineToPositions.size <= minimumDuplicateRange) { |
| 1651 | return result; |
| 1652 | } |
| 1653 | |
| 1654 | for (let i = 0; i < frames.length - minimumDuplicateRange; i++) { |
| 1655 | const positions = lineToPositions.get(frames[i]); |
| 1656 | // Find the next occurrence of the same line after i, if any |
| 1657 | if (positions.length === 1 || positions[positions.length - 1] === i) { |
| 1658 | continue; |
| 1659 | } |
| 1660 | |
| 1661 | const current = positions.indexOf(i) + 1; |
| 1662 | if (current === positions.length) { |
| 1663 | continue; |
| 1664 | } |
| 1665 | |
| 1666 | // Theoretical maximum range, adjusted while iterating |
| 1667 | let range = positions[positions.length - 1] - i; |
| 1668 | if (range < minimumDuplicateRange) { |
| 1669 | continue; |
| 1670 | } |
| 1671 | let extraSteps; |
| 1672 | if (current + 1 < positions.length) { |
| 1673 | // Optimize initial step size by choosing the greatest common divisor (GCD) |
| 1674 | // of all candidate distances to the same frame line. This tends to match |
| 1675 | // the true repeating block size and minimizes fallback iterations. |
| 1676 | let gcdRange = 0; |
| 1677 | for (let j = current; j < positions.length; j++) { |
| 1678 | let distance = positions[j] - i; |
| 1679 | while (distance !== 0) { |
| 1680 | const remainder = gcdRange % distance; |
| 1681 | if (gcdRange !== 0) { |
| 1682 | // Add other possible ranges as fallback |
| 1683 | extraSteps ??= new SafeSet(); |
| 1684 | extraSteps.add(gcdRange); |
| 1685 | } |
| 1686 | gcdRange = distance; |
| 1687 | distance = remainder; |
| 1688 | } |
| 1689 | if (gcdRange === 1) break; |
| 1690 | } |
| 1691 | range = gcdRange; |