「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
前言
前端算法系列是我对算法学习的一个记录, 主要从常见算法、数据结构、算法思维、常用技巧几个方面剖析学习算法知识, 通过LeetCode平台实现刻意练习, 通过掘金和B站的输出来实践费曼学习法, 我会在后续不断更新优质内容并同步更新到掘金、B站和Github, 以记录学习算法的完整过程, 欢迎大家多多交流、点赞、收藏, 让我们共同进步, daydayup👊
目录地址:目录篇
相关代码地址: Github
相关视频地址: 哔哩哔哩-百日算法系列
回顾
在上一篇文章([路飞]前端算法——算法篇(一、排序算法): 初识十大排序算法)中我们对快速排序有了一个大致的了解, 明白快速排序就是根据一个基准(povit)将大于等于基准的值放右边, 将小于基准的值放左边, 然后递归执行. 但是我们实现的代码并不完美, 今天我们来探索一下快速排序的优化方法.
一、从简单的实现中寻找问题
// 本函数以数组的首个元素为基准
// 开辟出左右两个空间来存储比较后的值
function quickSort(nums) {
if (nums.length < 2) return nums
let left = [], right = []
while(nums.length > 1) {
let num = nums.pop()
if (num >= nums[0]) {
right.push(num)
} else {
left.push(num)
}
}
return [...quickSort(left), nums[0], ...quickSort(right)]
}
上述代码虽然有 基数(povit) 的概念, 也大致是快排的思路, 但也存在以下明显的问题
- 使用了额外的空间
- 基准值的选取有待加强
关于基准值得选取,假设我们每次都取到了最小值,那么会发生什么事情呢?
当基数取最小值时,我们的左侧始终没有值,而右侧始终是全部的值,每一次比较都是O(n),且需要循环n次,使得我们的时间复杂度退化到O(n²)
二、基于问题重写快排
为了不开辟单独的空间, 我们采用 双指针 的技巧,设定两个指针(L、R)分别从数组的开头和结尾开始遍历,小于基数的放在指针L的左侧,大于基数的放在指针R的右侧,当指针L大于等于指针R的时候就表明我们已经遍历结束了,接下来对大于基数的部分和小于基数的部分分别递归计算就可以了。
为了降低我们取到最小基数的概率,我们采用取中间值的办法来进行一次优化。
具体的逻辑如下:
var QuickSort = function(nums) {
// 1.设定l、r两个指针
// 2.设定好基数 povit
// 3.指针l的数据符合小于基数的条件,指针右移,
// 指针r的数据符合大于基数的条件,指针左移,
// 指针l与指针r指向的数据 都 不符合条件时,互换位置
// 4.递归处理左侧小于部分和右侧大于部分
partition(nums, 0, nums.length - 1)
}
var partition = function(nums, star, ende) {
if (star >= ende) return
let l = star, r = ende
let povit = getPovit(nums, l, r)
while(l <= r) {
while(l <= r && nums[l] < povit) l++
while(l <= r && nums[r] > povit) r--
if (l <= r) {
temp(nums[l], nums[r])
l++
r--
}
}
partition(nums, star, r)
partition(nums, l, ende)
}
var getPovit = function(nums, l, r) {
let idx = Math.floor((l + r) / 2)
return nums[idx]
}
三、从STL学习快速排序的优化
STL(C++ )是标准模板库,它实现了常用的数据结构和算法。
STL中使用的排序算法叫做 内省排序(introsort), 它是多种排序算法的组合,其中包括 堆排序、快速排序、插入排序三种排序方法,利用各个排序算法的特性来寻求最优解。
内省排序以插入排序为主体,在此基础上进行了以下优化
- 单边递归法
- 无监督 Partition
- 三点取中法
- 适时放弃快排
- 使用插入排序收尾
单边递归法
我们知道快排是将数据分成一大一小的两组数据,然后递归处理,如果我们假设每次取的基准恰好都将原数据分成了数量接近的两堆,那么最后就会出现下图的情况
因为图中是一个满二叉树,二叉树的节点数就是递归的次数,所以递归的次数是:
2的n+1次方 - 1
而在STL中采用了一种单边递归的方法,简单来说快排是将一大一小两组数据分别递归处理,单边递归则是每次只递归处理大的数据(或只处理小的数据), 然后将小的数据代入下一次的分组过程中,也就是说第一次处理一半,第二次处理一半的一半。。。
我们计算单边递归法的递归次数为:
2的n次方
我们知道递归其实利用的是系统栈,执行递归是需要占用栈空间的,通过单边递归的方法,我们可以把递归的次数缩减少接近一半。接下来我们使用代码来实现下
备注:需要注意的是,这种写法在可读性上略有不足,相比之下较难以理解,因此不适合在项目中使用,而是更多的出现在标准库中!
var partition = function(nums, star, ende) {
while(star < ende) {
let l = star, r = ende
let povit = getPovit(nums, l, r)
while(l <= r) {
while(l <= r && nums[l] < povit) l++
while(l <= r && nums[r] > povit) r--
if (l <= r) {
temp(nums[l], nums[r])
l++
r--
}
}
partition(nums, l, ende)
ende = l
}
}
无监督 Partition
所谓无监督,就是上述代码中l <= r 的判断太多了,需要减少一些,实现上使用 do{}while()替换上述代码即可,实际影响并不大,这里不过多赘述。
三点取中法
因为快排的核心是基于 基数 的比较,所以对基数的选取就相对重要,当我们在选取的过程中不慎选到了最小或者最大值,快排的时间复杂度就退化为 O(n²),所以我们需要尽可能的避免取到最小值和最大值。一般来讲, 我们有以下几种方案
1.取中间值
var getPovit = function(nums, l, r) {
let idx = Math.floor((l + r) / 2)
return nums[idx]
}
2. 三点取中
var getPovit = function(nums, l, r) {
let idx = Math.floor((l + r) / 2)
let a = nums[l], b = nums[idx], c = nums[r]
if (a > b) swap(a, b)
if (a > c) swap(a, c)
if (b < c) swap(b, c)
return b
}
var swap = function(a, b) {
a = a + b
b = a - b
a = a - b
}
3.取平均值
适时放弃快排
快排最好的情况与最坏的情况我们是知道的,当我们判断快排出现较坏的情况时,我们就放弃快速排序,改用堆排序来对剩下的元素进行排序,因为堆排序具备一定的稳定性(时间复杂度 O(nlogn)不变)。
根据前辈的经验,我们确定 当前快排的时间复杂度 大于 O(2logn) 时,我们改用堆排序。
使用插入排序收尾
当我们的快速排序递归的时候,有可能已经是一个有序的数组了,但程序并不知道这是一个有序的数组,仍旧会继续处理,为了减少这种情况,我们使用插入排序对其进行最后的处理,因为插入排序在处理相对有序的排序时的最好时间复杂度是O(n)。
同样根据前辈的经验,我们确定 单次递归的数据量小于16时,我们改用插入排序。
友情链接
五、源码鉴赏(Array.prototype.sort)
颤抖吧!凡人。
V8引擎 源码
Safari 源码
/*
* Copyright (C) 2014-2017 Apple Inc. All rights reserved.
* Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
function reduce(callback /*, initialValue */)
{
"use strict";
var array = @toObject(this, "Array.prototype.reduce requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.reduce callback must be a function");
var argumentCount = @argumentCount();
if (length === 0 && argumentCount < 2)
@throwTypeError("reduce of empty array with no initial value");
var accumulator, k = 0;
if (argumentCount > 1)
accumulator = @argument(1);
else {
while (k < length && !(k in array))
k += 1;
if (k >= length)
@throwTypeError("reduce of empty array with no initial value");
accumulator = array[k++];
}
while (k < length) {
if (k in array)
accumulator = callback.@call(@undefined, accumulator, array[k], k, array);
k += 1;
}
return accumulator;
}
function reduceRight(callback /*, initialValue */)
{
"use strict";
var array = @toObject(this, "Array.prototype.reduceRight requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.reduceRight callback must be a function");
var argumentCount = @argumentCount();
if (length === 0 && argumentCount < 2)
@throwTypeError("reduceRight of empty array with no initial value");
var accumulator, k = length - 1;
if (argumentCount > 1)
accumulator = @argument(1);
else {
while (k >= 0 && !(k in array))
k -= 1;
if (k < 0)
@throwTypeError("reduceRight of empty array with no initial value");
accumulator = array[k--];
}
while (k >= 0) {
if (k in array)
accumulator = callback.@call(@undefined, accumulator, array[k], k, array);
k -= 1;
}
return accumulator;
}
function every(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.every requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.every callback must be a function");
var thisArg = @argument(1);
for (var i = 0; i < length; i++) {
if (!(i in array))
continue;
if (!callback.@call(thisArg, array[i], i, array))
return false;
}
return true;
}
function forEach(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.forEach requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.forEach callback must be a function");
var thisArg = @argument(1);
for (var i = 0; i < length; i++) {
if (i in array)
callback.@call(thisArg, array[i], i, array);
}
}
function filter(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.filter requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.filter callback must be a function");
var thisArg = @argument(1);
var result = @arraySpeciesCreate(array, 0);
var nextIndex = 0;
for (var i = 0; i < length; i++) {
if (!(i in array))
continue;
var current = array[i]
if (callback.@call(thisArg, current, i, array)) {
@putByValDirect(result, nextIndex, current);
++nextIndex;
}
}
return result;
}
function map(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.map requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.map callback must be a function");
var thisArg = @argument(1);
var result = @arraySpeciesCreate(array, length);
for (var i = 0; i < length; i++) {
if (!(i in array))
continue;
var mappedValue = callback.@call(thisArg, array[i], i, array);
@putByValDirect(result, i, mappedValue);
}
return result;
}
function some(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.some requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.some callback must be a function");
var thisArg = @argument(1);
for (var i = 0; i < length; i++) {
if (!(i in array))
continue;
if (callback.@call(thisArg, array[i], i, array))
return true;
}
return false;
}
function fill(value /* [, start [, end]] */)
{
"use strict";
var array = @toObject(this, "Array.prototype.fill requires that |this| not be null or undefined");
var length = @toLength(array.length);
var relativeStart = @toIntegerOrInfinity(@argument(1));
var k = 0;
if (relativeStart < 0) {
k = length + relativeStart;
if (k < 0)
k = 0;
} else {
k = relativeStart;
if (k > length)
k = length;
}
var relativeEnd = length;
var end = @argument(2);
if (end !== @undefined)
relativeEnd = @toIntegerOrInfinity(end);
var final = 0;
if (relativeEnd < 0) {
final = length + relativeEnd;
if (final < 0)
final = 0;
} else {
final = relativeEnd;
if (final > length)
final = length;
}
for (; k < final; k++)
array[k] = value;
return array;
}
function find(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.find requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.find callback must be a function");
var thisArg = @argument(1);
for (var i = 0; i < length; i++) {
var kValue = array[i];
if (callback.@call(thisArg, kValue, i, array))
return kValue;
}
return @undefined;
}
function findLast(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.findLast requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.findLast callback must be a function");
var thisArg = @argument(1);
for (var i = length - 1; i >= 0; i--) {
var element = array[i];
if (callback.@call(thisArg, element, i, array))
return element;
}
return @undefined;
}
function findIndex(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.findIndex requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.findIndex callback must be a function");
var thisArg = @argument(1);
for (var i = 0; i < length; i++) {
if (callback.@call(thisArg, array[i], i, array))
return i;
}
return -1;
}
function findLastIndex(callback /*, thisArg */)
{
"use strict";
var array = @toObject(this, "Array.prototype.findLastIndex requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.findLastIndex callback must be a function");
var thisArg = @argument(1);
for (var i = length - 1; i >= 0; i--) {
if (callback.@call(thisArg, array[i], i, array))
return i;
}
return -1;
}
function includes(searchElement /*, fromIndex*/)
{
"use strict";
var array = @toObject(this, "Array.prototype.includes requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (length === 0)
return false;
var fromIndex = 0;
var from = @argument(1);
if (from !== @undefined)
fromIndex = @toIntegerOrInfinity(from);
var index;
if (fromIndex >= 0)
index = fromIndex;
else
index = length + fromIndex;
if (index < 0)
index = 0;
var currentElement;
for (; index < length; ++index) {
currentElement = array[index];
// Use SameValueZero comparison, rather than just StrictEquals.
if (searchElement === currentElement || (searchElement !== searchElement && currentElement !== currentElement))
return true;
}
return false;
}
@globalPrivate
function sortStringComparator(a, b)
{
"use strict";
var aString = a.string;
var bString = b.string;
if (aString === bString)
return 0;
return aString > bString ? 1 : -1;
}
@globalPrivate
function sortCompact(receiver, receiverLength, compacted, isStringSort)
{
"use strict";
var undefinedCount = 0;
var compactedIndex = 0;
for (var i = 0; i < receiverLength; ++i) {
if (i in receiver) {
var value = receiver[i];
if (value === @undefined)
++undefinedCount;
else {
@putByValDirect(compacted, compactedIndex,
isStringSort ? {string: @toString(value), value} : value);
++compactedIndex;
}
}
}
return undefinedCount;
}
@globalPrivate
function sortCommit(receiver, receiverLength, sorted, undefinedCount)
{
"use strict";
// Move undefineds and holes to the end of an array. Result is [values..., undefineds..., holes...].
@assert(@isJSArray(sorted));
var sortedLength = sorted.length;
@assert(sortedLength + undefinedCount <= receiverLength);
var i = 0;
if (@isJSArray(receiver) && sortedLength >= 64 && typeof sorted[0] !== "number") { // heuristic
@appendMemcpy(receiver, sorted, 0);
i = sortedLength;
} else {
for (; i < sortedLength; ++i)
receiver[i] = sorted[i];
}
for (; i < sortedLength + undefinedCount; ++i)
receiver[i] = @undefined;
for (; i < receiverLength; ++i)
delete receiver[i];
}
@globalPrivate
function sortMerge(dst, src, srcIndex, srcEnd, width, comparator)
{
"use strict";
var left = srcIndex;
var leftEnd = @min(left + width, srcEnd);
var right = leftEnd;
var rightEnd = @min(right + width, srcEnd);
for (var dstIndex = left; dstIndex < rightEnd; ++dstIndex) {
if (right < rightEnd) {
if (left >= leftEnd) {
@putByValDirect(dst, dstIndex, src[right]);
++right;
continue;
}
// See https://bugs.webkit.org/show_bug.cgi?id=47825 on boolean special-casing
var comparisonResult = comparator(src[right], src[left]);
if (comparisonResult === false || comparisonResult < 0) {
@putByValDirect(dst, dstIndex, src[right]);
++right;
continue;
}
}
@putByValDirect(dst, dstIndex, src[left]);
++left;
}
}
@globalPrivate
function sortMergeSort(array, comparator)
{
"use strict";
var valueCount = array.length;
var buffer = @newArrayWithSize(valueCount);
var dst = buffer;
var src = array;
for (var width = 1; width < valueCount; width *= 2) {
for (var srcIndex = 0; srcIndex < valueCount; srcIndex += 2 * width)
@sortMerge(dst, src, srcIndex, valueCount, width, comparator);
var tmp = src;
src = dst;
dst = tmp;
}
return src;
}
@globalPrivate
function sortBucketSort(array, dst, bucket, depth)
{
"use strict";
if (bucket.length < 32 || depth > 32) {
var sorted = @sortMergeSort(bucket, @sortStringComparator);
for (var i = 0; i < sorted.length; ++i) {
@putByValDirect(array, dst, sorted[i].value);
++dst;
}
return dst;
}
var buckets = [ ];
@setPrototypeDirect.@call(buckets, null);
for (var i = 0; i < bucket.length; ++i) {
var entry = bucket[i];
var string = entry.string;
if (string.length == depth) {
@putByValDirect(array, dst, entry.value);
++dst;
continue;
}
var c = string.@charCodeAt(depth);
var cBucket = buckets[c];
if (cBucket)
@arrayPush(cBucket, entry);
else
@putByValDirect(buckets, c, [ entry ]);
}
for (var i = 0; i < buckets.length; ++i) {
if (!buckets[i])
continue;
dst = @sortBucketSort(array, dst, buckets[i], depth + 1);
}
return dst;
}
function sort(comparator)
{
"use strict";
var isStringSort = false;
if (comparator === @undefined)
isStringSort = true;
else if (!@isCallable(comparator))
@throwTypeError("Array.prototype.sort requires the comparator argument to be a function or undefined");
var receiver = @toObject(this, "Array.prototype.sort requires that |this| not be null or undefined");
var receiverLength = @toLength(receiver.length);
// For compatibility with Firefox and Chrome, do nothing observable
// to the target array if it has 0 or 1 sortable properties.
if (receiverLength < 2)
return receiver;
var compacted = [ ];
var sorted = null;
var undefinedCount = @sortCompact(receiver, receiverLength, compacted, isStringSort);
if (isStringSort) {
sorted = @newArrayWithSize(compacted.length);
@sortBucketSort(sorted, 0, compacted, 0);
} else
sorted = @sortMergeSort(compacted, comparator);
@sortCommit(receiver, receiverLength, sorted, undefinedCount);
return receiver;
}
@globalPrivate
function concatSlowPath()
{
"use strict";
var currentElement = @toObject(this, "Array.prototype.concat requires that |this| not be null or undefined");
var argCount = arguments.length;
var result = @arraySpeciesCreate(currentElement, 0);
var resultIsArray = @isJSArray(result);
var resultIndex = 0;
var argIndex = 0;
do {
var spreadable = @isObject(currentElement) && currentElement.@@isConcatSpreadable;
if ((spreadable === @undefined && @isArray(currentElement)) || spreadable) {
var length = @toLength(currentElement.length);
if (length + resultIndex > @MAX_SAFE_INTEGER)
@throwTypeError("Length exceeded the maximum array length");
if (resultIsArray && @isJSArray(currentElement) && length + resultIndex <= @MAX_ARRAY_INDEX) {
@appendMemcpy(result, currentElement, resultIndex);
resultIndex += length;
} else {
for (var i = 0; i < length; i++) {
if (i in currentElement)
@putByValDirect(result, resultIndex, currentElement[i]);
resultIndex++;
}
}
} else {
if (resultIndex >= @MAX_SAFE_INTEGER)
@throwTypeError("Length exceeded the maximum array length");
@putByValDirect(result, resultIndex++, currentElement);
}
currentElement = arguments[argIndex];
} while (argIndex++ < argCount);
result.length = resultIndex;
return result;
}
function concat(first)
{
"use strict";
if (@argumentCount() === 1
&& @isJSArray(this)
&& @tryGetByIdWithWellKnownSymbol(this, "isConcatSpreadable") === @undefined
&& (!@isObject(first) || @tryGetByIdWithWellKnownSymbol(first, "isConcatSpreadable") === @undefined)) {
var result = @concatMemcpy(this, first);
if (result !== null)
return result;
}
return @tailCallForwardArguments(@concatSlowPath, this);
}
@globalPrivate
function maxWithPositives(a, b)
{
"use strict";
return (a < b) ? b : a;
}
@globalPrivate
function minWithMaybeNegativeZeroAndPositive(maybeNegativeZero, positive)
{
"use strict";
return (maybeNegativeZero < positive) ? maybeNegativeZero : positive;
}
function copyWithin(target, start /*, end */)
{
"use strict";
var array = @toObject(this, "Array.prototype.copyWithin requires that |this| not be null or undefined");
var length = @toLength(array.length);
var relativeTarget = @toIntegerOrInfinity(target);
var to = (relativeTarget < 0) ? @maxWithPositives(length + relativeTarget, 0) : @minWithMaybeNegativeZeroAndPositive(relativeTarget, length);
var relativeStart = @toIntegerOrInfinity(start);
var from = (relativeStart < 0) ? @maxWithPositives(length + relativeStart, 0) : @minWithMaybeNegativeZeroAndPositive(relativeStart, length);
var relativeEnd;
var end = @argument(2);
if (end === @undefined)
relativeEnd = length;
else
relativeEnd = @toIntegerOrInfinity(end);
var finalValue = (relativeEnd < 0) ? @maxWithPositives(length + relativeEnd, 0) : @minWithMaybeNegativeZeroAndPositive(relativeEnd, length);
var count = @minWithMaybeNegativeZeroAndPositive(finalValue - from, length - to);
var direction = 1;
if (from < to && to < from + count) {
direction = -1;
from = from + count - 1;
to = to + count - 1;
}
for (var i = 0; i < count; ++i, from += direction, to += direction) {
if (from in array)
array[to] = array[from];
else
delete array[to];
}
return array;
}
@globalPrivate
function flatIntoArray(target, source, sourceLength, targetIndex, depth)
{
"use strict";
for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) {
if (sourceIndex in source) {
var element = source[sourceIndex];
if (depth > 0 && @isArray(element))
targetIndex = @flatIntoArray(target, element, @toLength(element.length), targetIndex, depth - 1);
else {
if (targetIndex >= @MAX_SAFE_INTEGER)
@throwTypeError("flatten array exceeds 2**53 - 1");
@putByValDirect(target, targetIndex, element);
++targetIndex;
}
}
}
return targetIndex;
}
function flat()
{
"use strict";
var array = @toObject(this, "Array.prototype.flat requires that |this| not be null or undefined");
var length = @toLength(array.length);
var depthNum = 1;
var depth = @argument(0);
if (depth !== @undefined)
depthNum = @toIntegerOrInfinity(depth);
var result = @arraySpeciesCreate(array, 0);
@flatIntoArray(result, array, length, 0, depthNum);
return result;
}
@globalPrivate
function flatIntoArrayWithCallback(target, source, sourceLength, targetIndex, callback, thisArg)
{
"use strict";
for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) {
if (sourceIndex in source) {
var element = callback.@call(thisArg, source[sourceIndex], sourceIndex, source);
if (@isArray(element))
targetIndex = @flatIntoArray(target, element, @toLength(element.length), targetIndex, 0);
else {
if (targetIndex >= @MAX_SAFE_INTEGER)
@throwTypeError("flatten array exceeds 2**53 - 1");
@putByValDirect(target, targetIndex, element);
++targetIndex;
}
}
}
return target;
}
function flatMap(callback)
{
"use strict";
var array = @toObject(this, "Array.prototype.flatMap requires that |this| not be null or undefined");
var length = @toLength(array.length);
if (!@isCallable(callback))
@throwTypeError("Array.prototype.flatMap callback must be a function");
var thisArg = @argument(1);
var result = @arraySpeciesCreate(array, 0);
return @flatIntoArrayWithCallback(result, array, length, 0, callback, thisArg);
}
function at(index)
{
"use strict";
var array = @toObject(this, "Array.prototype.at requires that |this| not be null or undefined");
var length = @toLength(array.length);
var k = @toIntegerOrInfinity(index);
if (k < 0)
k += length;
return (k >= 0 && k < length) ? array[k] : @undefined;
}