Python has by far the best slice implementation I've ever used. As a quick evening project I felt like trying to implement Python's slice construct in a JavaScript function (thinking about sending a pr to a certain library with Python's slice semantics). I came up with the code below, and I'm trying to improve my code. I would love feedback or alternative implementations.
I think I hit all the edge cases, but I only briefly skimmed the Python source, so I could easily be wrong.
/**
* Implementation of Python's `slice` function... Get a cloned subsequence
* of an iterable (collection with length property and array like indexs).
* Will handle both strings and array(likes).
*
* @param {Array|String} collection
* @param {None|Integer} start First index to include. If negative it will be indicies from end
(i.e. -1 is last item). Omit or pass 0/null/undefined for 0.
* @param {None|Integer} end Last index to include. If negative it will be indicies from end
(i.e. -1 is last item). Omit or pass null/undefined for end.
* @param {None|Intger} step Increments to increase by (non-1 will skip indicies). Negative values
will reverse the output.
* @returns {Array|String} sliced array
*
* @example
* var list = [1, 2, 3, 4, 5]
* slice(list) // => [1, 2, 3, 4, 5]
* slice(list, 2) // => [3, 4, 5]
* slice(list, 2, 4) // => [3, 4]
* slice(list, -2) // => [4, 5]
* slice(list, null, -1) // => [1, 2, 3, 4]
* slice(list, null, null, 2) // => [1, 3, 5]
* slice(list, null, null, -2) // => [5, 3, 1]
* slice("kids a devil I tell ya", 7, -10, -1) // => "lived"
*/
function slice(collection, start, end, step) {
var length = collection.length,
isString = typeof collection == "string", // IE<9 have issues with accessing strings by indicies ("str"[0] === undefined)
result = [];
if (isString) {
collection = collection.split("");
}
if (start == null) {
start = 0;
} else if (start < 0) {
start = length + start;
}
if (end == null || end > length) {
end = length;
} else if (end < 0) {
end = length + end;
}
if (step == null) {
step = 1;
} else if (step === 0) {
throw "Slice step cannot be zero";
}
if (step > 0) {
for (; start < end; start += step) {
result.push(collection[start]);
}
} else {
for (end -= 1; start <= end; end += step) {
result.push(collection[end]);
}
}
// Return a string for input strings otherwise an array
return isString ? result.join("") : result;
}
start == null
might confuse people. It confused me (and I'm not normally one of the "always use ===" pundits either). – Dagg Jul 17 '14 at 4:38push
instead of precomputing the length:length = Math.ceil(Math.max(end - start, 0) / Math.abs(step)), result = Array(length);
The I can iterate like you! – megawac Jul 17 '14 at 12:44