Поворот массива JavaScript()
мне было интересно, какой самый эффективный способ, чтобы повернуть на JavaScript массив.
Я придумал это решение, где положительный n поворачивает массив вправо, и отрицательный n влево (-length < n < length):
Array.prototype.rotateRight = function( n ) {
this.unshift( this.splice( n, this.length ) )
}
который затем может быть использован следующим образом:
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
months.rotate( new Date().getMonth() )
моя первоначальная версия выше, имеет недостаток, как указал Кристоф в комментариях ниже, правильная версия (дополнительный возврат позволяет цепочку):
Array.prototype.rotateRight = function( n ) {
this.unshift.apply( this, this.splice( n, this.length ) )
return this;
}
есть ли более компактное и / или более быстрое решение, возможно, в контексте структуры JavaScript? (ни одна из предложенных ниже версий не является ни более компактной, ни более быстрой)
есть ли какой-либо JavaScript-фреймворк с встроенным вращением массива? (До сих пор никто не ответил)
17 ответов:
тип-безопасный, универсальный вариант, который мутирует массива:
Array.prototype.rotate = (function() { // save references to array functions to make lookup faster var push = Array.prototype.push, splice = Array.prototype.splice; return function(count) { var len = this.length >>> 0, // convert to uint count = count >> 0; // convert to int // convert count to value in range [0, len) count = ((count % len) + len) % len; // use splice.call() instead of this.splice() to make function generic push.apply(this, splice.call(this, 0, count)); return this; }; })();в комментариях, Жан поднял вопрос о том, что код не поддерживает перегрузку
push()иsplice(). Я не думаю, что это действительно полезно (см. комментарии), но быстрое решение (несколько Хак, хотя) было бы заменить строкуpush.apply(this, splice.call(this, 0, count));С этим:
(this.push || push).apply(this, (this.splice || splice).call(this, 0, count));
используя
unshift()вместоpush()почти в два раза быстрее в Opera 10, в то время как различия в FF были незначительны; код:Array.prototype.rotate = (function() { var unshift = Array.prototype.unshift, splice = Array.prototype.splice; return function(count) { var len = this.length >>> 0, count = count >> 0; unshift.apply(this, splice.call(this, count % len, len)); return this; }; })();
EDIT: Смотрите также мой новый ответ с
countаргумент:https://stackoverflow.com/a/33451102можно использовать
push(),pop(),shift()иunshift()функции:function arrayRotateOne(arr, reverse){ if(reverse) arr.unshift(arr.pop()) else arr.push(arr.shift()) return arr }использование:
arrayRotate(['h','e','l','l','o']); // ['e','l','l','o','h']; arrayRotate(['h','e','l','l','o'], true); // ['o','h','e','l','l'];
Я бы, наверное, сделать что-то вроде этого:
Array.prototype.rotate = function(n) { return this.slice(n, this.length).concat(this.slice(0, n)); }
Edit вот версия мутатора:
Array.prototype.rotate = function(n) { while (this.length && n < 0) n += this.length; this.push.apply(this, this.splice(0, n)); return this; }
эта функция работает в обоих направлениях и работает с любым числом (даже с числом больше длины массива):
function arrayRotate(arr, count) { count -= arr.length * Math.floor(count / arr.length) arr.push.apply(arr, arr.splice(0, count)) return arr }пример:
for(let i = -6 ; i <= 6 ; i++) console.log( arrayRotate( ["H","e","l","l","o"], i).join(''), i )результат:
"oHell", -6 "Hello", -5 "elloH", -4 "lloHe", -3 "loHel", -2 "oHell", -1 "Hello", 0 "elloH", 1 "lloHe", 2 "loHel", 3 "oHell", 4 "Hello", 5 "elloH", 6
многие из этих ответов кажутся слишком сложными и трудными для чтения. Я не думаю, что видел, чтобы кто-то использовал splice с concat...
function rotateCalendar(){ var cal=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"], cal=cal.concat(cal.splice(0,new Date().getMonth())); console.log(cal); // return cal; }
@Christoph, вы сделали чистый код, но 60% медленных чем этот я нашел. Посмотрите на результат на jsPerf:http://jsperf.com/js-rotate-array/2 [Edit] ОК теперь есть больше браузеров, что не очевидные методы ведьмы лучшие
var rotateArray = function(a, inc) { for (var l = a.length, inc = (Math.abs(inc) >= l && (inc %= l), inc < 0 && (inc += l), inc), i, x; inc; inc = (Math.ceil(l / inc) - 1) * inc - l + (l = inc)) for (i = l; i > inc; x = a[--i], a[i] = a[i - inc], a[i - inc] = x); return a; }; var array = ['a','b','c','d','e','f','g','h','i']; console.log(array); console.log(rotateArray(array.slice(), -1)); // Clone array with slice() to keep original
см.http://jsperf.com/js-rotate-array/8
function reverse(a, from, to) { --from; while (++from < --to) { var tmp = a[from]; a[from] = a[to]; a[to] = tmp; } } function rotate(a, from, to, k) { var n = to - from; k = (k % n + n) % n; if (k > 0) { reverse(a, from, from + k); reverse(a, from + k, to); reverse(a, from, to); } }
принятый ответ имеет недостаток в том, что он не может обрабатывать массивы размером больше размера стека вызовов, который зависит от сеанса, но должен быть около 100~300K элементов. Например, в текущем сеансе Chrome, который я пробовал, это было 250891. Во многих случаях вы можете даже не знать, что размер массива может динамически расти. Так что это серьезная проблема.
чтобы преодолеть это ограничение, я думаю, что один интересный метод использует
Array.prototype.map()и сопоставление элементов по перестановка индексов по кругу. Этот метод принимает один целочисленный аргумент. Если этот аргумент положителен, он будет вращаться по возрастающим индексам, а если отрицателен по направлению убывания индексов. Это имеет только o (n) временную сложность и вернет новый массив без изменения того, к которому он призван, при обработке миллионов элементов без каких-либо проблем. Давайте посмотрим, как это работает;Array.prototype.rotate = function(n) { var len = this.length; return !(n % len) ? this : n > 0 ? this.map((e,i,a) => a[(i + n) % len]) : this.map((e,i,a) => a[(len - (len - i - n) % len) % len]); }; var a = [1,2,3,4,5,6,7,8,9], b = a.rotate(2); console.log(JSON.stringify(b)); b = a.rotate(-1); console.log(JSON.stringify(b));на самом деле после того, как я подвергся критике по двум вопросам, как следует;
- нет необходимости в условном для положительного или отрицательного ввода, так как он показывает нарушение сухого .вы можете сделать это с одной картой, потому что каждый отрицательный n имеет положительный эквивалент (Совершенно верно..)
- функция массива должна либо изменить текущий массив, либо создать новый массив, ваша функция может сделать это в зависимости от того, необходим ли сдвиг или нет (совершенно верно..)
я решил изменить код как следует;
Array.prototype.rotate = function(n) { var len = this.length; return !(n % len) ? this.slice() : this.map((e,i,a) => a[(i + (len + n % len)) % len]); }; var a = [1,2,3,4,5,6,7,8,9], b = a.rotate(10); console.log(JSON.stringify(b)); b = a.rotate(-10); console.log(JSON.stringify(b));затем снова; конечно, функторы JS любят
Array.prototype.map()медленны по сравнению с их эквивалентами, закодированными в простом JS. Для того, чтобы получить более чем 100% повышение производительности следующее, вероятно, будет мой выборArray.prototype.rotate()если мне когда-нибудь понадобится повернуть массив в производственном коде, как тот, который я использовал в своей попытке наString.prototype.diff()Array.prototype.rotate = function(n){ var len = this.length, res = new Array(this.length); if (n % len === 0) return this.slice(); else for (var i = 0; i < len; i++) res[i] = this[(i + (len + n % len)) % len]; return res; };
вот очень простой способ перемещения элементов в массиве:
function rotate(array, stepsToShift) { for (var i = 0; i < stepsToShift; i++) { array.unshift(array.pop()); } return array; }
эта функция немного быстрее, чем принятый ответ для небольших массивов, но гораздо быстрее для больших массивов. Эта функция также допускает произвольное число поворотов, превышающее длину массива, что является ограничением исходной функции.
наконец, принятый ответ вращается в противоположном направлении, как описано.
const rotateForEach = (a, n) => { const l = a.length; a.slice(0, -n % l).forEach(item => a.push( item )); return a.splice(n % l > 0 ? (-n % l) : l + (-n % l)); }и функциональный эквивалент (который, кажется, также имеет некоторую производительность преимущества):
const rotateReduce = (arr, n) => { const l = arr.length; return arr.slice(0, -n % l).reduce((a,b) => { a.push( b ); return a; }, arr).splice(n % l> 0 ? l + (-n % l) : -n % l); };Вы можете проверить разбивка производительности здесь.
когда я не смог найти готовый фрагмент, чтобы начать список дней с "сегодня", я сделал это так (не совсем общий, вероятно, гораздо менее изысканный, чем в приведенных выше примерах, но выполнил эту работу):
//returns 7 day names with today first function startday() { const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; let today = new Date(); let start = today.getDay(); //gets day number if (start == 0) { //if Sunday, days are in order return days } else { //if not Sunday, start days with today return days.slice(start).concat(days.slice(0,start)) } }благодаря небольшому рефактору лучшим программистом, чем я, это строка или две короче, чем моя первоначальная попытка, но любые дальнейшие комментарии по эффективности приветствуются.
Как насчет увеличения счетчика, а затем получения оставшейся части деления на длину массива, чтобы получить, где вы должны быть.
var i = 0; while (true); { var position = i % months.length; alert(months[position]); ++i; }синтаксис языка в сторону это должно работать нормально.
Если Ваш массив будет большим и / или вы собираетесь много вращаться, вы можете рассмотреть возможность использования связанного списка вместо массива.
@molokoloco мне нужна была функция, которую я мог бы настроить для поворота в направлении - true для вперед и false для назад. Я создал фрагмент кода, который принимает направление, счетчик и массив и выводит объект со счетчиком, увеличенным в соответствующем направлении, а также предыдущие, текущие и следующие значения. Он не изменяет исходный массив.
Я также синхронизировал его с вашим фрагментом, и хотя он не быстрее, он быстрее, чем те, которые вы сравниваете с-21% медленнее http://jsperf.com/js-rotate-array/7 .
function directionalRotate(direction, counter, arr) { counter = direction ? (counter < arr.length - 1 ? counter + 1 : 0) : (counter > 0 ? counter - 1 : arr.length - 1) var currentItem = arr[counter] var priorItem = arr[counter - 1] ? arr[counter - 1] : arr[arr.length - 1] var nextItem = arr[counter + 1] ? arr[counter + 1] : arr[0] return { "counter": counter, "current": currentItem, "prior": priorItem, "next": nextItem } } var direction = true // forward var counter = 0 var arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']; directionalRotate(direction, counter, arr)
Я опаздываю, но у меня есть кирпич, чтобы добавить к этим хорошим ответам. Меня попросили закодировать такую функцию, и я сначала сделал:
Array.prototype.rotate = function(n) { for (var i = 0; i < n; i++) { this.push(this.shift()); } return this; }но это оказалось менее эффективным, чем следовать, когда
nбольшой:Array.prototype.rotate = function(n) { var l = this.length;// Caching array length before map loop. return this.map(function(num, index) { return this[(index + n) % l] }); }
EDIT:: Так получается слишком много итераций происходит. Ни петель, ни разветвлений.
все еще работает с отрицательным n для правого вращения и положительным n для левого вращения для любого размера n, Мутации бесплатно
function rotate(A,n,l=A.length) { const offset = (((n % l) + l) %l) return A.slice(offset).concat(A.slice(0,offset)) }вот код гольф версия для хихиканья
const r = (A,n,l=A.length,i=((n%l)+l)%l)=>A.slice(i).concat(A.slice(0,i))EDIT1::* Внеофисный, реализации mutationless.
function r(A,n,l=A.length) { return A.map((x,i,a) => A[(((n+i)%l) + l) % l]) }уравнение
((n%l) + l) % lкарты именно положительные и отрицательные числа любых, сколь угодно больших значений nоригинал
поворот влево и вправо. Поверните влево с положительным
n, поворот вправо с отрицательнымn.работает для неприлично больших входов
n.нет режиме мутации. Слишком мутация в этих ответах.
кроме того, меньше операций, чем большинство ответов. Ни хлопка, ни толчка, ни сращивания, ни сдвига.
const rotate = (A, num ) => { return A.map((x,i,a) => { const n = num + i return n < 0 ? A[(((n % A.length) + A.length) % A.length)] : n < A.length ? A[n] : A[n % A.length] }) }или
const rotate = (A, num) => A.map((x,i,a, n = num + i) => n < 0 ? A[(((n % A.length) + A.length) % A.length)] : n < A.length ? A[n] : A[n % A.length]) //test rotate([...Array(5000).keys()],4101) //left rotation rotate([...Array(5000).keys()],-4101000) //right rotation, num is negative // will print the first index of the array having been rotated by -i // demonstrating that the rotation works as intended [...Array(5000).keys()].forEach((x,i,a) => { console.log(rotate(a,-i)[0]) }) // prints even numbers twice by rotating the array by i * 2 and getting the first value //demonstrates the propper mapping of positive number rotation when out of range [...Array(5000).keys()].forEach((x,i,a) => { console.log(rotate(a,i*2)[0]) })объяснение:
карта каждого индекса стоимости на индекс смещения. В данном случае
offset = numесли
offset < 0затемoffset + index + positive length of Aбудет указывать на обратное смещение.если
offset > 0 and offset < length of Aзатем просто сопоставьте текущий индекс с индексом смещения А.в противном случае, по модулю смещения и длины для отображения смещения в границах массива.
Возьмем, к примеру
offset = 4иoffset = -4., когда
offset = -4иA = [1,2,3,4,5], для каждого индекса,offset + indexсделает величину (илиMath.abs(offset)) меньше.давайте сначала объясним расчет для индекса отрицательного n.
A[(((n % A.length) + A.length) % A.length)+0]и запугивали. не будет. мне потребовалось 3 минуты в Repl, чтобы решить это.
- мы знаем!--7--> отрицательно, потому что случай
n < 0. Если число больше диапазона массива,n % A.lengthбудет отображать его в диапазоне.n + A.lengthдобавьте это число вA.lengthдля смещения N, необходимая сумма.- мы знаем!--7--> отрицательно, потому что случай
n < 0.n + A.lengthдобавьте это число вA.lengthчтобы компенсировать N правильную сумму.далее сопоставьте его с диапазоном длины A используя модуль. Второй modulous необходим для отображения результата расчета в индексируемый диапазон
первый индекс: -4 + 0 = -4. A. Длина = 5. A. Длина - 4 = 1. А2 is 2. Индекс карты от 0 до 2.
[2,... ]- следующий индекс, -4 + 1 = -3. 5 + -3 = 2. А2 is 3. Индекс карты от 1 до 3.
[2,3... ]- Etc.
то же процесс применяется к
offset = 4. Когдаoffset = -4иA = [1,2,3,4,5], для каждого индекса,offset + indexсделает величину больше.
использование спреда ES6 для неизменного примера ...
[...array.slice(1, array.length), array[0]]и
[array[array.items.length -1], ...array.slice(0, array.length -1)]это, вероятно, не самый эффективный, но он лаконичен.

Comments