หมุนองค์ประกอบในอาร์เรย์ใน JavaScript


86

ฉันสงสัยว่าวิธีใดเป็นวิธีที่มีประสิทธิภาพที่สุดในการหมุนอาร์เรย์ 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 ใด ๆ ที่มีอาร์เรย์หมุนในตัวหรือไม่? (ยังไม่ตอบจากใคร)


1
ฉันไม่เข้าใจว่าตัวอย่างของคุณควรทำอย่างไร ทำไมคุณไม่ใช้months[new Date().getMonth()]เพื่อรับชื่อของเดือนปัจจุบัน?
Gumbo

1
@Jean: รหัสเสีย: วิธีที่คุณทำมันจะคลายองค์ประกอบที่ต่อกันเป็นอาร์เรย์และไม่แยกทีละรายการ คุณจะต้องใช้apply()เพื่อทำให้การนำไปใช้งาน
คริสตอฟ

วันนี้การหมุนเวียนจะปรับเปลี่ยนเดือนเพื่อแสดงรายการนี้ (โดยDecอยู่ในตำแหน่งแรก):["Dec", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"]
Jean Vincent

@ คริสตอฟคุณพูดถูกสิ่งนี้จะไม่ทำงานเป็นฟังก์ชันหมุนทั่วไป ใช้งานได้เฉพาะเมื่อใช้เพื่อแปลงเป็นสตริงทันที
Jean Vincent

@Jean: คุณสามารถแก้ไขเวอร์ชันของคุณผ่านArray.prototype.unshift.apply(this, this.splice(...))- เวอร์ชันของฉันทำสิ่งเดียวกัน แต่ใช้push()แทนunshift()
คริสตอฟ

คำตอบ:


62

Type-safe เวอร์ชันทั่วไปที่กลายพันธุ์อาร์เรย์:

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;
    };
})();

2
Array.prototypeวิธีการแคชที่ดี! +1
James

การใช้สัญลักษณ์แสดงหัวข้อย่อยที่ดีมาก แต่โดยทั่วไปฉันต้องการใช้ข้อยกเว้นหรือเพียงแค่การตอบสนองที่ไม่ดีสำหรับการใช้งานที่ไม่ดี เพื่อให้โค้ดสะอาดและรวดเร็ว ผู้ใช้มีหน้าที่ส่งผ่านพารามิเตอร์ที่ถูกต้องหรือจ่ายผลที่ตามมา ฉันไม่ชอบการลงโทษสำหรับผู้ใช้ที่ดี นอกเหนือจากนั้นสมบูรณ์แบบเพราะมันปรับเปลี่ยน Array ตามที่ร้องขอและไม่ได้รับผลกระทบจากข้อบกพร่องในการใช้งานของฉันซึ่งไม่ได้เปลี่ยนอาร์เรย์แทนที่จะเป็นแต่ละองค์ประกอบตามที่คุณระบุไว้ การส่งคืนนี้ยังดีกว่าที่จะอนุญาตให้มีการผูกมัด ขอบคุณมาก
Jean Vincent

ค่าใช้จ่ายในการปิดที่นี่คืออะไรเพียงเพื่อแคช push และ splice?
Jean Vincent

@Jean: การปิดจับห่วงโซ่ขอบเขตทั้งหมด ตราบใดที่ฟังก์ชันภายนอกอยู่ในระดับบนสุดผลกระทบก็ควรจะเล็กน้อยและเป็น O (1) อย่างไรก็ตามในขณะที่การค้นหาวิธีการ Array คือ O (n) ในจำนวนการเรียกใช้ การเพิ่มประสิทธิภาพการใช้งานอาจทำให้การค้นหาแบบอินไลน์ดังนั้นจึงไม่มีผลประโยชน์ใด ๆ แต่ด้วยล่ามที่ค่อนข้างโง่ที่เราต้องจัดการเป็นเวลานานตัวแปรการแคชในขอบเขตที่ต่ำกว่าอาจส่งผลกระทบอย่างมีนัยสำคัญ
คริสตอฟ

1
สิ่งนี้จะไม่หมุนอาร์เรย์ขนาดใหญ่ ฉันตรวจสอบวันนี้และสามารถรองรับอาร์เรย์ที่มีความยาว 250891 รายการเท่านั้น เห็นได้ชัดว่าจำนวนอาร์กิวเมนต์ที่คุณสามารถส่งผ่านapplyเมธอดนั้นถูก จำกัด ด้วยขนาดโทรสแต็กที่แน่นอน ในเงื่อนไข ES6 ตัวดำเนินการสเปรดจะประสบปัญหาขนาดสแต็กเดียวกัน ด้านล่างฉันให้วิธีแผนที่ในการทำสิ่งเดียวกัน ช้ากว่า แต่ใช้ได้กับหลายล้านรายการ นอกจากนี้คุณยังสามารถใช้แผนที่ด้วยการวนซ้ำแบบง่ายๆสำหรับหรือในขณะและจะเร็วขึ้นมาก
Redu

147

คุณสามารถใช้push(), pop(), shift()และunshift()วิธีการ:

function arrayRotate(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'];

หากคุณต้องการcountโต้แย้งดูคำตอบอื่นของฉัน: https://stackoverflow.com/a/33451102 🖤🧡💚💙💜


ไม่มีการนับแม้ว่าจะไม่ใช่เรื่องใหญ่ขึ้นอยู่กับการใช้งาน
JustGage

@JustGage ฉันเผยแพร่คำตอบอื่นพร้อมการสนับสนุนการนับstackoverflow.com/questions/1985260/…
Yukulélé

2
หมายเหตุ: ระวังว่าวิธีนี้ล่วงล้ำและจะจัดการอาร์เรย์ที่ส่งเป็นพารามิเตอร์ ไม่ยากที่จะแก้ปัญหาการทำซ้ำอาร์เรย์ก่อนส่ง
fguillen

นี่คือเวอร์ชันใน typescript ที่มีตัวแปรซ้ายขวาstackblitz.com/edit/typescript-vtcmhp
DživoJelić

38

ฉันอาจจะทำสิ่งนี้:

Array.prototype.rotate = function(n) {
    return this.slice(n, this.length).concat(this.slice(0, n));
}

แก้ไข     นี่คือเวอร์ชัน mutator:

Array.prototype.rotate = function(n) {
    while (this.length && n < 0) n += this.length;
    this.push.apply(this, this.splice(0, n));
    return this;
}

โปรดทราบว่าฟังก์ชันนี้จะทำให้อาร์เรย์เดิมไม่เปลี่ยนแปลง
Christoph

จำเป็นต้องแก้ไข Array ดั้งเดิม (สิ่งนี้) เช่นเดียวกับ push, pop, shift, unshift, concat และ splice do นอกเหนือจากนั้นก็ใช้ได้
Jean Vincent

@ กัมโบ้ทำไมต้องวนซ้ำ? เราไม่จำเป็นต้องทำให้ n เป็นบวกการประกบก็ใช้ได้กับค่าลบเช่นกัน ในตอนท้ายนี่เป็นเวอร์ชันของคริสตอฟที่ถูกต้อง แต่ค่อนข้างมากซึ่งทำให้ถูกต้องก่อนโดยไม่มีข้อแม้ที่มากเกินไป
Jean Vincent

@Gumbo การแก้ไขความคิดเห็นก่อนหน้าของฉันตัวเลขเชิงลบใช้งานได้เฉพาะเวอร์ชัน splice (n. this.length) การใช้ splice (0, n) ในเวอร์ชันของคุณต้องใช้ int เชิงบวก
Jean Vincent

1
ฉันได้เพิ่มn = n % this.lengthก่อนคำสั่ง return เพื่อจัดการกับจำนวนลบและ / หรือนอกขอบเขต
iedmrc

25

ฟังก์ชั่นนี้ทำงานได้ทั้งสองทางและใช้ได้กับตัวเลขใด ๆ (แม้จะมีจำนวนมากกว่าความยาวอาร์เรย์ก็ตาม):

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(["🧡","💚","💙","💜","🖤"], i), i);
}

ผลลัพธ์:

[ "🖤", "🧡", "💚", "💙", "💜" ]    -6
[ "🧡", "💚", "💙", "💜", "🖤" ]    -5
[ "💚", "💙", "💜", "🖤", "🧡" ]    -4
[ "💙", "💜", "🖤", "🧡", "💚" ]    -3
[ "💜", "🖤", "🧡", "💚", "💙" ]    -2
[ "🖤", "🧡", "💚", "💙", "💜" ]    -1
[ "🧡", "💚", "💙", "💜", "🖤" ]    0
[ "💚", "💙", "💜", "🖤", "🧡" ]    1
[ "💙", "💜", "🖤", "🧡", "💚" ]    2
[ "💜", "🖤", "🧡", "💚", "💙" ]    3
[ "🖤", "🧡", "💚", "💙", "💜" ]    4
[ "🧡", "💚", "💙", "💜", "🖤" ]    5
[ "💚", "💙", "💜", "🖤", "🧡" ]    6

การใช้ฟังก์ชันนี้ฉันพบปัญหาเพราะมันเปลี่ยนแปลงอาร์เรย์เดิม ฉันพบวิธีแก้ปัญหาบางอย่างที่นี่: stackoverflow.com/questions/14491405
ognockocaten

1
@ognockocaten ไม่ใช่ปัญหา แต่เป็นวิธีการทำงานของฟังก์ชันนี้ หากคุณต้องการให้อาร์เรย์ดั้งเดิมไม่เปลี่ยนแปลงให้โคลนไว้ก่อน:var arr2 = arrayRotate(arr.slice(0), 5)
Yukulélé

นี่เป็นคำตอบที่ยอดเยี่ยมและช่วยฉันได้ อย่างไรก็ตามการให้ทางเลือกที่ไม่กลายพันธุ์อาร์เรย์เดิมจะดีที่สุด แน่นอนว่ามันเป็นเรื่องง่ายที่จะสร้างสำเนาต้นฉบับนี่ไม่ใช่แนวทางปฏิบัติที่ดีที่สุดเนื่องจากใช้หน่วยความจำโดยไม่จำเป็น
เว็บไฮบริด

@Hybridwebdev useconst immutatableArrayRotate = (arr, count) => ArrayRotate(arr.clone(), count)
Yukulélé

9

คำตอบจำนวนมากเหล่านี้ดูซับซ้อนเกินไปและอ่านยาก ไม่คิดว่าจะเห็นใครใช้ splice with 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;
}

console.log เอาต์พุต (* สร้างในเดือนพฤษภาคม):

["May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr"]

สำหรับความกะทัดรัดฉันสามารถเสนอฟังก์ชั่นซับเดียวทั่วไปได้สองสามอย่าง (ไม่นับส่วน console.log | return) เพียงแค่ป้อนอาร์เรย์และค่าเป้าหมายในอาร์กิวเมนต์

ฉันรวมฟังก์ชันเหล่านี้เป็นหนึ่งเดียวสำหรับโปรแกรมเกมไพ่สำหรับผู้เล่นสี่คนโดยที่อาร์เรย์คือ ['N', 'E', 'S', 'W'] ฉันแยกพวกเขาออกจากกันในกรณีที่ใครก็ตามต้องการคัดลอก / วางสำหรับความต้องการของพวกเขา เพื่อจุดประสงค์ของฉันฉันใช้ฟังก์ชั่นนี้เมื่อค้นหาว่าเทิร์นของใครอยู่ถัดจากเล่น / แสดงในช่วงต่างๆของเกม (Pinochle) ฉันไม่ได้ใส่ใจในการทดสอบความเร็วดังนั้นหากมีคนอื่นต้องการอย่าลังเลที่จะแจ้งให้เราทราบผล

* ข้อสังเกตข้อแตกต่างระหว่างฟังก์ชันคือ "+1"

function rotateToFirst(arr,val){  // val is Trump Declarer's seat, first to play
    arr=arr.concat(arr.splice(0,arr.indexOf(val)));
    console.log(arr); // return arr;
}
function rotateToLast(arr,val){  // val is Dealer's seat, last to bid
    arr=arr.concat(arr.splice(0,arr.indexOf(val)+1));
    console.log(arr); // return arr;
}

ฟังก์ชันการรวมกัน ...

function rotateArray(arr,val,pos){
    // set pos to 0 if moving val to first position, or 1 for last position
    arr=arr.concat(arr.splice(0,arr.indexOf(val)+pos));
    return arr;
}
var adjustedArray=rotateArray(['N','E','S','W'],'S',1);

modifiedArray =

W,N,E,S

1
คำตอบที่ดี ก่อนที่คุณจะก้าวไปสู่ด้านมืด (PHP)?
นิค

... ฉันเกิดมาในด้านมืด
mickmackusa

:) btw ฉันใช้ที่นี่
นิค

6

การใช้สเปรดของ ES6 สำหรับตัวอย่างที่ไม่เปลี่ยนรูป ...

[...array.slice(1, array.length), array[0]]

และ

[array[array.items.length -1], ...array.slice(0, array.length -1)]

อาจไม่ได้มีประสิทธิภาพมากที่สุด แต่ก็รัดกุม


5

วิธีแก้ปัญหาง่ายๆด้วยชิ้นส่วนและการทำลาย:

const rotate = (arr, count = 1) => {
  return [...arr.slice(count, arr.length), ...arr.slice(0, count)];
};

const arr = [1,2,3,4,5];

console.log(rotate(arr, 1));  // [2, 3, 4, 5, 1]
console.log(rotate(arr, 2));  // [3, 4, 5, 1, 2]
console.log(rotate(arr, -2)); // [4, 5, 1, 2, 3]
console.log(rotate(arr, -1)); // [5, 1, 2, 3, 4]


2
นี่มันดีจริงๆ! คุณสามารถหลีกเลี่ยงความจำเป็นในการตรวจสอบได้count === 0โดยการตั้งค่าเริ่มต้น ที่จะช่วยให้คุณสามารถทำซับได้:const rotate = (arr, n = 1) => [...arr.slice(n, arr.length), ...arr.slice(0, n)];
Nick F

4

นี่เป็นวิธีง่ายๆในการเปลี่ยนรายการในอาร์เรย์:

function rotate(array, stepsToShift) {

    for (var i = 0; i < stepsToShift; i++) {
        array.unshift(array.pop());
    }

    return array;
}

3

@ คริสตอฟคุณทำรหัสสะอาดแล้ว แต่ช้าที่สุด 60% ที่ฉันพบ ดูผลลัพธ์ใน jsPerf: http://jsperf.com/js-rotate-array/2 [แก้ไข] ตกลงตอนนี้มีเบราว์เซอร์มากขึ้นและวิธีการแม่มดที่ไม่ชัดเจนที่สุด

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

@molokocolo โดยสัญชาตญาณการแก้ปัญหาของคุณจะทำงานช้าลงเนื่องจากการเพิ่มจะสูงขึ้นเนื่องจากคุณใช้การวนซ้ำ ฉันได้อัปเดตกรณีทดสอบของคุณโดยเพิ่มขึ้น 5 และดูเหมือนว่าจะทำงานช้าลง: jsperf.com/js-rotate-array/3
Jean Vincent

ฉันคิดไม่ออกว่ากำลังทำฟังก์ชัน rotArray () อยู่ ^^ เพียง แต่มันใช้งานได้ :) (มันเป็นรหัสแปลก ๆ !) Chrome ทำงานเกือบตรงข้ามกับ Firefox ...
molokoloco

วิธีนี้น่าสนใจมากทำงานโดยการแลกเปลี่ยนการอ้างอิงองค์ประกอบในอาร์เรย์ ขนาดอาร์เรย์ไม่เคยเปลี่ยนแปลงซึ่งเป็นเหตุผลว่าทำไมจึงเร็วมากด้วยข้อเสียของการใช้ลูป สิ่งที่น่าสนใจที่แสดงให้เห็นก็คือ Firefox นั้นเร็วที่สุดในการวนซ้ำในขณะที่โครเมี่ยมนั้นเร็วที่สุดในการปรับแต่งอาร์เรย์
Jean Vincent

1
หลังจากวิเคราะห์โค้ดแล้วฉันพบจุดอ่อนที่แสดงให้เห็นว่าโซลูชันนี้เร็วขึ้นสำหรับอาร์เรย์ขนาดเล็กและจำนวนการหมุนที่แน่นอนเท่านั้น: jsperf.com/js-rotate-array/5 ในบรรดาเบราว์เซอร์ทั้งหมดที่เร็วที่สุดยังคงเป็น Google Chrome ที่มีการต่อ / unshift วิธีการแก้. ซึ่งหมายความว่านี่เป็นเรื่องของการเพิ่มประสิทธิภาพเมธอด Array ที่ Chrome ทำได้ดีกว่าเบราว์เซอร์อื่น ๆ
Jean Vincent


3

เมื่อฉันไม่พบตัวอย่างข้อมูลสำเร็จรูปที่จะเริ่มรายการวันด้วย "วันนี้" ฉันก็ทำเช่นนี้ (ไม่ใช่แบบทั่วไปอาจจะละเอียดอ่อนกว่าตัวอย่างข้างต้นมาก แต่ก็ได้ผล):

//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))
    }
}

ขอบคุณ refactor เล็กน้อยโดยโปรแกรมเมอร์ที่ดีกว่าฉันมันสั้นกว่าความพยายามครั้งแรกของฉันหรือสองบรรทัด แต่ยินดีต้อนรับความคิดเห็นเพิ่มเติมเกี่ยวกับประสิทธิภาพ


3
function rotate(arr, k) {
for (var i = 0; i < k+1; i++) {
    arr.push(arr.shift());
}
return arr;
}
//k work as an index array
console.log(rotate([1, 2, 7, 4, 5, 6, 7], 3)); //[5,6,7,1,2,7,4]
console.log(rotate([-1, -100, 3, 99], 2));     //[99,-1,-100,3]

3
// Example of array to rotate
let arr = ['E', 'l', 'e', 'p', 'h', 'a', 'n', 't'];

// Getting array length
let length = arr.length;

// rotation < 0 (move left), rotation > 0 (move right)
let rotation = 5;

// Slicing array in two parts
let first  = arr.slice(   (length - rotation) % length, length); //['p', 'h', 'a' ,'n', 't']
let second = arr.slice(0, (length - rotation) % length); //['E', 'l', 'e']

// Rotated element
let rotated = [...first, ...second]; // ['p', 'h', 'a' ,'n', 't', 'E', 'l', 'e']

ในโค้ดหนึ่งบรรทัด:

let rotated = [...arr.slice((length - rotation) % length, length), ...arr.slice(0, (length - rotation) % length)];

2

คำตอบที่ยอมรับมีข้อบกพร่องที่ไม่สามารถจัดการอาร์เรย์ที่มีขนาดใหญ่กว่าขนาด call stack ซึ่งขึ้นอยู่กับเซสชัน แต่ควรมีค่าประมาณ 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));

หลังจากที่ฉันถูกวิจารณ์ในสองเรื่องดังต่อไปนี้

  1. ไม่จำเป็นต้องมีเงื่อนไขสำหรับการป้อนข้อมูลเชิงบวกหรือเชิงลบเนื่องจากแสดงการละเมิด DRY คุณสามารถทำได้ด้วยแผนที่เดียวเพราะทุกค่าลบ n มีค่าเท่ากับบวก (ถูกต้องทั้งหมด .. )
  2. ฟังก์ชัน Array ควรเปลี่ยนอาร์เรย์ปัจจุบันหรือสร้างอาร์เรย์ใหม่ฟังก์ชันของคุณสามารถทำได้ขึ้นอยู่กับว่าจำเป็นต้องใช้ shift หรือไม่ (ถูกต้องทั้งหมด .. )

ฉันได้ตัดสินใจที่จะแก้ไขโค้ดดังนี้

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 functors 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;
};

shift () และ splice () ช้าเท่าที่ควรจะเร็วกว่าการใช้ map () หรือวิธีการใด ๆ โดยใช้พารามิเตอร์ฟังก์ชัน shift () และ splice () ที่มีการเปิดเผยมากขึ้นก็ง่ายต่อการปรับให้เหมาะสมในระยะยาว
Jean Vincent

@Jean Vincent ใช่คุณพูดถูก .. แผนที่ในความเป็นจริงอาจจะช้าลง แต่ฉันเชื่อว่าตรรกะการหมุนในอุดมคติคือสิ่งนี้ ฉันแค่พยายามแสดงวิธีการเชิงตรรกะ แผนที่สามารถทำให้ง่ายขึ้นได้อย่างง่ายดายด้วย for loop และจะส่งผลให้ความเร็วดีขึ้นอย่างมีนัยสำคัญ ... อย่างไรก็ตามคำตอบที่ได้รับการยอมรับนั้นมีแนวทางที่พัฒนาขึ้นโดยคำนึงถึงภาษาอื่น ๆ ไม่เหมาะสำหรับ JS .... จะไม่ทำงานเมื่อขนาดอาร์เรย์ใหญ่กว่าขนาด call stack (ในกรณีของฉันและในเซสชั่นนี้มีจำนวน จำกัด เพียง 250891 รายการ) ดังนั้นก็คือ .. !
Redu

คุณพูดถูกเกี่ยวกับขนาด call stack ที่ใช้ can เกินนี่เป็นอาร์กิวเมนต์ที่ถูกต้องมากกว่าความเร็วให้พิจารณาแก้ไขคำตอบของคุณเพื่อเน้นจุดนี้ด้วยการเยื้องที่ดีขึ้นด้วย
Jean Vincent

2

ฟังก์ชันนี้เร็วกว่าคำตอบที่ยอมรับสำหรับอาร์เรย์ขนาดเล็กเล็กน้อย แต่เร็วกว่ามากสำหรับอาร์เรย์ขนาดใหญ่ ฟังก์ชั่นนี้ยังอนุญาตให้มีจำนวนการหมุนที่มากกว่าความยาวของอาร์เรย์ซึ่งเป็นข้อ จำกัด ของฟังก์ชันดั้งเดิม

สุดท้ายคำตอบที่ยอมรับจะหมุนไปในทิศทางตรงกันข้ามตามที่อธิบายไว้

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);
};

คุณสามารถตรวจสอบรายละเอียดประสิทธิภาพได้ที่นี่


น่าเสียดายที่มันใช้งานไม่ได้มันจะย่อขนาดอาร์เรย์ลงเป็นศูนย์ซึ่งจะอธิบายได้ว่าทำไมมันจึงเร็วกว่ามากหลังจากการรันครั้งแรกกว่าการใช้งานที่ถูกต้องอื่น ๆ โดยเฉพาะอย่างยิ่งในอาร์เรย์ขนาดใหญ่ ในการทดสอบเกณฑ์มาตรฐานของคุณหากคุณสลับลำดับของการทดสอบและแสดง a.length ในตอนท้ายคุณจะพบปัญหา
Jean Vincent

2

แก้ไข :: เฮ้ปรากฎว่ามีการทำซ้ำมากเกินไป ไม่มีลูปไม่แตกแขนง

ยังคงใช้งานได้กับ 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))

แก้ไข 1 :: * การใช้งานแบบไม่มีสาขาและไม่มีการกลายพันธุ์

เดี๋ยวก่อนปรากฎว่าฉันมีสาขาที่ฉันไม่ต้องการมัน นี่คือวิธีแก้ปัญหาที่ใช้งานได้ จำนวนลบ = หมุนขวาโดย | num | จำนวนบวก = หมุนซ้ายตามจำนวน

function r(A,n,l=A.length) {
  return A.map((x,i,a) => A[(((n+i)%l) + l) % l])
}

สมการจะ((n%l) + l) % lจับคู่จำนวนบวกและลบของค่า n ขนาดใหญ่ตามอำเภอใจ

เดิม

หมุนไปทางซ้ายและขวา หมุนซ้ายกับบวกขวาหมุนกับเชิงลบnn

ใช้งานได้กับปัจจัยการผลิตขนาดใหญ่ที่หยาบคายของ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])
})

คำอธิบาย:

จับคู่ดัชนีของ A กับค่าที่ออฟเซ็ตดัชนี ในกรณีนี้

offset = num

ถ้าoffset < 0จากนั้นoffset + index + positive length of Aจะชี้ไปที่ออฟเซ็ตผกผัน

ถ้าoffset > 0 and offset < length of Aเพียงแค่แมปดัชนีปัจจุบันกับดัชนีออฟเซ็ตของ 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 นาทีในการเล่นซ้ำเพื่อให้ได้ผล

  1. เรารู้ว่าเป็นลบเนื่องจากกรณีที่เป็นn n < 0หากตัวเลขมีขนาดใหญ่กว่าช่วงของ Array n % A.lengthจะแมปลงในช่วง
  2. n + A.lengthเพิ่มตัวเลขนั้นA.lengthเพื่อหักล้าง n จำนวนเงินที่ถูกต้อง
  3. เรารู้ว่าเป็นลบเนื่องจากกรณีที่เป็นn เพิ่มตัวเลขนั้นเพื่อหักล้าง n จำนวนเงินที่ถูกต้องn < 0n + A.lengthA.length
  4. ถัดไปแมปกับช่วงของความยาวของ A โดยใช้โมดูโล การปรับเปลี่ยนที่สองเป็นสิ่งจำเป็นในการแมปผลลัพธ์ของการคำนวณเป็นช่วงที่สามารถจัดทำดัชนีได้

    ป้อนคำอธิบายภาพที่นี่

  5. ดัชนีแรก: -4 + 0 = -4 A.length = 5. A.length - 4 = 1. A 2คือ 2. ดัชนีแผนที่ 0 ถึง 2[2,... ]

  6. ดัชนีถัดไป -4 + 1 = -3 5 + -3 = 2. A 2คือ 3. ดัชนีแผนที่ 1 ถึง 3[2,3... ]
  7. ฯลฯ

offset = 4กระบวนการเดียวกันกับ เมื่อoffset = -4ใดและA = [1,2,3,4,5]สำหรับแต่ละดัชนีoffset + indexจะทำให้ขนาดใหญ่ขึ้น

  1. 4 + 0 = 0. แมป A [0] กับค่าที่ A [4][5...]
  2. 4 + 1 = 5, 5 อยู่นอกขอบเขตเมื่อสร้างดัชนีดังนั้นแมป A 2กับค่าที่ส่วนที่เหลือ5 / 5ซึ่งคือ 0 A 2 = ค่าที่ A [0][5,1...]
  3. ทำซ้ำ

1
Follow a simpler approach of running a loop to n numbers and shifting places upto that element.

function arrayRotateOne(arr, n) {
  for (let i = 0; i < n; i++) {
    arr.unshift(arr.pop());
  }
  return arr;
}
console.log( arrayRotateOne([1,2,3,4,5,6],2));



function arrayRotateOne(arr,n) {
  for(let i=0; i<n;i++){
      arr.push(arr.shift());
      console.log('execute',arr)
    }
     return arr;
 }

console.log (arrayRotateOne ([1,2,3,4,5,6], 2));


1

โซลูชันที่ไม่กลายพันธุ์

var arr = ['a','b','c','d']
arr.slice(1,arr.length).concat(arr.slice(0,1)

ด้วยการกลายพันธุ์

var arr = ['a','b','c','d']
arr = arr.concat(arr.splice(0,1))

1

ฉันกำลังแบ่งปันวิธีการแก้ปัญหาของฉันที่ฉันใช้ในการหมุนบนม้าหมุน อาจแตกได้เมื่อขนาดอาร์เรย์เล็กกว่าdisplayCountแต่คุณสามารถเพิ่มเงื่อนไขพิเศษเพื่อหยุดการหมุนเมื่อมีขนาดเล็กหรือเชื่อมต่ออาร์เรย์หลัก * displayCount ครั้งด้วย

function rotate(arr, moveCount, displayCount) {
  const size = arr.length;

  // making sure startIndex is between `-size` and `size`
  let startIndex = moveCount % size;
  if (startIndex < 0) startIndex += size; 

  return [...arr, ...arr].slice(startIndex, startIndex + displayCount);
}

// move 3 to the right and display 4 items
// rotate([1,2,3,4,5], 3, 4) -> [4,5,1,2]

// move 3 to the left and display 4 items
// rotate([1,2,3,4,5], -3, 4) -> [3,4,5,1]

// move 11 to the right and display 4
// rotate([1,2,3,4,5], 3, 4) -> [2,3,4,5]

0

วิธีการเพิ่มตัวนับจากนั้นหาส่วนที่เหลือของการหารด้วยความยาวอาร์เรย์เพื่อให้ได้ตำแหน่งที่คุณควรจะเป็น

var i = 0;
while (true);
{
    var position = i % months.length;
    alert(months[position]);
    ++i;
}

ไวยากรณ์ของภาษานอกเหนือจากนี้ควรใช้งานได้ดี


2
สิ่งนี้ไม่ได้หมุน Array แต่อย่างใด
Jean Vincent

1
จริง (ตามที่ระบุไว้ในคำตอบ) แต่จะช่วยประหยัดได้โดยต้องหมุนอาร์เรย์
tgandrews

1
แต่จุดรวมของ IS นี้ในการหมุน Array ต่อหัวเรื่องไม่ใช่เพื่อแสดงอาร์เรย์จากออฟเซ็ตที่แน่นอน หากคุณใช้ลูปเดียวกันเพื่อสร้างอาร์เรย์ใหม่มันจะช้ากว่าเวอร์ชันอื่น ๆ อย่างมากโดยใช้เมธอด Array แบบเนทีฟ นอกจากนี้คุณลืมที่จะแยกออกจากลูปที่ไม่มีที่สิ้นสุด
Jean Vincent

0

หากอาร์เรย์ของคุณมีขนาดใหญ่และ / หรือคุณกำลังจะหมุนเวียนมากคุณอาจต้องการพิจารณาใช้รายการที่เชื่อมโยงแทนอาร์เรย์


เห็นด้วย แต่คำถามเกี่ยวข้องกับ Arrays และโดยเฉพาะอย่างยิ่งในการขยายคำกริยา Array
Jean Vincent

0

@molokoloco ฉันต้องการฟังก์ชั่นที่ฉันสามารถกำหนดค่าให้หมุนไปในทิศทาง - จริงสำหรับไปข้างหน้าและเท็จสำหรับย้อนกลับ ฉันสร้างตัวอย่างข้อมูลที่ใช้ทิศทางตัวนับและอาร์เรย์และส่งออกอ็อบเจ็กต์โดยตัวนับจะเพิ่มขึ้นในทิศทางที่เหมาะสมรวมทั้งค่าก่อนหน้าปัจจุบันและค่าถัดไป ไม่ได้แก้ไขอาร์เรย์เดิม

ฉันยังโอเวอร์คล็อกมันกับตัวอย่างของคุณและแม้ว่ามันจะไม่เร็วขึ้นก็จะเร็วกว่าคนที่คุณเปรียบเทียบกับคุณ - 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 ในการตอบคำถามนี้คุณต้องส่งคืน Array โดยที่รายการแรกอยู่ที่เคาน์เตอร์
Jean Vincent

0

ฉันมาช้า แต่ฉันมีอิฐที่จะเพิ่มคำตอบที่ดีเหล่านี้ ฉันถูกขอให้เขียนโค้ดฟังก์ชันดังกล่าวและฉันทำครั้งแรก:

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]
    });
}

0

ฉันไม่แน่ใจว่านี่เป็นวิธีที่มีประสิทธิภาพที่สุดหรือไม่ แต่ฉันชอบวิธีอ่านมันเร็วพอสำหรับงานขนาดใหญ่ส่วนใหญ่เพราะฉันได้ทดสอบในการผลิต ...

function shiftRight(array) {
  return array.map((_element, index) => {
    if (index === 0) {
      return array[array.length - 1]
    } else return array[index - 1]
  })
}

function test() {
  var input = [{
    name: ''
  }, 10, 'left-side'];
  var expected = ['left-side', {
    name: ''
  }, 10]
  var actual = shiftRight(input)

  console.log(expected)
  console.log(actual)

}

test()


0

เนทีฟเร็วเล็กความหมายใช้ได้กับเครื่องยนต์เก่าและ "แกงได้"

function rotateArray(offset, array) {
    offset = -(offset % array.length) | 0 // ensure int
    return array.slice(offset).concat(
        array.slice(0, offset)
    )
}

0

** การใช้ JS เวอร์ชันล่าสุดเราสามารถสร้างได้อย่างง่ายดาย **

 Array.prototype.rotateLeft = function (n) {
   this.unshift(...this.splice(-(n), n));
    return this
  }

ที่นี่ย้าย: จำนวนการหมุนอาร์เรย์ที่คุณสามารถส่งผ่านหมายเลขสุ่ม

let a = [1, 2, 3, 4, 5, 6, 7];
let moves = 4;
let output = a.rotateLeft(moves);
console.log("Result:", output)

0

Arrayใน JS ได้สร้างด้านล่างในวิธีการที่สามารถใช้ในการหมุนอาร์เรย์ค่อนข้างง่ายและเห็นได้ชัดว่าวิธีการเหล่านี้ไม่เปลี่ยนรูปในธรรมชาติ

  • push: แทรกรายการที่ส่วนท้ายของอาร์เรย์
  • pop: ลบรายการออกจากส่วนท้ายของอาร์เรย์
  • unshift: แทรกรายการที่จุดเริ่มต้นของอาร์เรย์
  • shift: ลบรายการออกจากจุดเริ่มต้นของอาร์เรย์

โซลูชันด้านล่าง ( ES6) ใช้อาร์กิวเมนต์สองตัวอาร์เรย์จะต้องหมุนและ n จำนวนครั้งที่ควรหมุนอาร์เรย์

const rotateArray = (arr, n) => {
  while(arr.length && n--) {
    arr.unshift(arr.pop());
  }
  return arr;
}

rotateArray(['stack', 'overflow', 'is', 'Awesome'], 2) 
// ["is", "Awesome", "stack", "overflow"]

สามารถเพิ่มลงใน Array.prototype และสามารถใช้ได้กับแอปพลิเคชันของคุณ

Array.prototype.rotate = function(n) {
 while(this.length && n--) {
   this.unshift(this.pop());
 }
 return this;
}
[1,2,3,4].rotate(3); //[2, 3, 4, 1]

0

ใช้สำหรับลูป นี่คือขั้นตอน

  1. จัดเก็บองค์ประกอบแรกของอาร์เรย์เป็นตัวแปรชั่วคราว
  2. จากนั้นสลับจากซ้ายไปขวา
  3. จากนั้นกำหนดตัวแปร temp ให้กับองค์ประกอบสุดท้ายของอาร์เรย์
  4. ทำซ้ำขั้นตอนเหล่านี้สำหรับจำนวนการหมุน

function rotateLeft(arr, rotations) {
    let len = arr.length;
    for(let i=0; i<rotations; i++){ 
        let temp = arr[0];
        for(let i=0; i< len; i++){
            arr[i]=arr[i+1];
        }
        arr[len-1]=temp;
    }
    return arr;
}

let arr = [1,2,3,4,5];

let rotations = 3;
let output = rotateLeft(arr, rotations);
console.log("Result Array => ", output);



0

ใช้สิ่งต่อไปนี้ -

arr=[1,2,3,4,5]  
let arrs=[]
arrs=arr.slice(d%arr.length).concat(arr.slice(0,d%arr.length))

ที่ไหน ไม่มีของการdหมุน
ทางออกที่ดีที่สุดไม่จำเป็นต้องใช้เทคนิค Brute force ของ Popping Pushing ด้วย ความซับซ้อนของเวลาO (1)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.