วิธีที่ดีที่สุดในการสลับ NSMutableArray คืออะไร


187

หากคุณมีคุณNSMutableArrayจะสุ่มสับเปลี่ยนองค์ประกอบได้อย่างไร

(ฉันมีคำตอบของฉันเองสำหรับเรื่องนี้ซึ่งโพสต์ด้านล่าง แต่ฉันใหม่กับ Cocoa และฉันสนใจที่จะรู้ว่ามีวิธีที่ดีกว่านี้หรือไม่)


อัปเดต: ตามที่ระบุไว้โดย @Mukesh ตั้งแต่ iOS 10+ และ macOS 10.12+ มี-[NSMutableArray shuffledArray]วิธีการที่สามารถใช้เพื่อสลับ ดูhttps://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objcสำหรับรายละเอียด (แต่โปรดทราบว่าสิ่งนี้จะสร้างอาร์เรย์ใหม่แทนที่การสับเปลี่ยนองค์ประกอบในที่)


ดูคำถามนี้: ปัญหาจริงของโลกที่มีการสับแบบไร้เดียงสาที่เกี่ยวข้องกับอัลกอริทึมการสับของคุณ
craigb

นี่คือการดำเนินการใน Swift: iosdevelopertips.com/swift-code/swift-shuffle-array-type.html
Kristopher Johnson

5
ปัจจุบันที่ดีที่สุดคือฟิชเชอร์เยตส์ :for (NSUInteger i = self.count; i > 1; i--) [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
Cœur


ปัญหาที่มีอยู่APIคือมันจะส่งคืนArrayที่อยู่ไปยังตำแหน่งใหม่ในหน่วยความจำ
TheTiger

คำตอบ:



349

ฉันแก้ไขสิ่งนี้โดยเพิ่มหมวดหมู่ไปยัง NSMutableArray

แก้ไข:ลบวิธีที่ไม่จำเป็นออกโดยขอบคุณคำตอบของ Ladd

แก้ไข:เปลี่ยน(arc4random() % nElements)เป็นarc4random_uniform(nElements)ขอบคุณที่จะตอบโดย Gregory Goltsov และความคิดเห็นโดย miho และ blahdiblah

แก้ไข:การปรับปรุงลูปขอบคุณที่คอมเม้นท์โดยรอน

แก้ไข:เพิ่มตรวจสอบว่าอาร์เรย์ไม่ว่างขอบคุณที่แสดงความคิดเห็นโดย Mahesh Agrawal

//  NSMutableArray_Shuffling.h

#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#include <Cocoa/Cocoa.h>
#endif

// This category enhances NSMutableArray by providing
// methods to randomly shuffle the elements.
@interface NSMutableArray (Shuffling)
- (void)shuffle;
@end


//  NSMutableArray_Shuffling.m

#import "NSMutableArray_Shuffling.h"

@implementation NSMutableArray (Shuffling)

- (void)shuffle
{
    NSUInteger count = [self count];
    if (count <= 1) return;
    for (NSUInteger i = 0; i < count - 1; ++i) {
        NSInteger remainingCount = count - i;
        NSInteger exchangeIndex = i + arc4random_uniform((u_int32_t )remainingCount);
        [self exchangeObjectAtIndex:i withObjectAtIndex:exchangeIndex];
    }
}

@end

10
ทางออกที่ดี และใช่ตามที่ willc2 กล่าวถึงการแทนที่ random () ด้วย arc4random () เป็นการปรับปรุงที่ดีเพราะไม่ต้องมีการเพาะ
Jason Moore

4
@ Jason: บางครั้ง (เช่นเมื่อทดสอบ) ความสามารถในการจัดหาเมล็ดพันธุ์เป็นสิ่งที่ดี Kristopher: อัลกอริทึมที่ดี มันเป็นการนำอัลกอริทึมของ Fisher-Yates มาใช้: en.wikipedia.org/wiki/Fisher-Yates_shuffle
JeremyP

4
การปรับปรุงเล็กน้อยพิเศษ : ในการวนซ้ำครั้งล่าสุดของลูป i == นับ - 1 นั่นไม่ได้หมายความว่าเรากำลังแลกเปลี่ยนวัตถุที่ดัชนีฉันด้วยตัวเองหรือไม่? เราสามารถปรับแต่งรหัสเพื่อข้ามการทำซ้ำครั้งล่าสุดได้หรือไม่?
Ron

10
คุณคิดว่าจะโยนเหรียญได้หรือไม่ถ้าผลลัพธ์ตรงข้ามกับด้านที่เคยเป็นมา
Kristopher Johnson

4
การสุ่มแบบนี้มีอคติอย่างละเอียด ใช้แทนarc4random_uniform(nElements) arc4random()%nElementsดูหน้า man arc4randomและคำอธิบายของ modulo biasสำหรับข้อมูลเพิ่มเติม
blahdiblah

38

เนื่องจากฉันยังไม่สามารถแสดงความคิดเห็นได้ฉันคิดว่าฉันตอบรับอย่างเต็มที่ ผมปรับเปลี่ยนการดำเนินงาน Kristopher จอห์นสันสำหรับโครงการของฉันในหลายวิธี (จริงๆพยายามจะทำให้มันเป็นรัดกุมเป็นไปได้) ซึ่งเป็นหนึ่งในนั้นเป็นarc4random_uniform()เพราะมันหลีกเลี่ยงอคติโมดูโล

// NSMutableArray+Shuffling.h
#import <Foundation/Foundation.h>

/** This category enhances NSMutableArray by providing methods to randomly
 * shuffle the elements using the Fisher-Yates algorithm.
 */
@interface NSMutableArray (Shuffling)
- (void)shuffle;
@end

// NSMutableArray+Shuffling.m
#import "NSMutableArray+Shuffling.h"

@implementation NSMutableArray (Shuffling)

- (void)shuffle
{
    NSUInteger count = [self count];
    for (uint i = 0; i < count - 1; ++i)
    {
        // Select a random element between i and end of array to swap with.
        int nElements = count - i;
        int n = arc4random_uniform(nElements) + i;
        [self exchangeObjectAtIndex:i withObjectAtIndex:n];
    }
}

@end

2
โปรดทราบว่าคุณกำลังโทร[self count](ผู้ให้บริการทรัพย์สิน) สองครั้งในแต่ละรอบซ้ำผ่านวง ฉันคิดว่าการย้ายออกจากลูปนั้นคุ้มค่ากับการสูญเสียความกระชับ
Kristopher Johnson

1
และนั่นเป็นเหตุผลที่ฉันยังชอบ[object method]มากกว่าobject.method: ผู้คนมักจะลืมว่าภายหลังไม่ถูกเท่าการเข้าถึงสมาชิก struct มันมาพร้อมกับค่าใช้จ่ายของวิธีการโทร ... แย่มากในวง
DarkDust

ขอบคุณสำหรับการแก้ไข - ฉันคิดว่าการนับที่ผิดนั้นถูกแคชไว้ด้วยเหตุผลบางอย่าง อัปเดตคำตอบ
gregoltsov

10

หากคุณนำเข้าGameplayKitมีshuffledAPI:

https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled

let shuffledArray = array.shuffled()

ฉันมี myArray และต้องการสร้าง shuffleArray ใหม่ของมัน ฉันจะทำอย่างไรกับ Objective - C
Omkar Jadhav

shuffledArray = [array shuffledArray];
andreacipriani

โปรดทราบว่าวิธีนี้เป็นส่วนหนึ่งของGameplayKitดังนั้นคุณต้องนำเข้า
AnthoPak

9

โซลูชันที่ได้รับการปรับปรุงและรัดกุมเล็กน้อย (เปรียบเทียบกับคำตอบยอดนิยม)

อัลกอริทึมเหมือนกันและมีการอธิบายไว้ในวรรณกรรมว่า " สับเปลี่ยน Fisher-Yates "

ในวัตถุประสงค์ -C:

@implementation NSMutableArray (Shuffle)
// Fisher-Yates shuffle
- (void)shuffle
{
    for (NSUInteger i = self.count; i > 1; i--)
        [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
}
@end

ใน Swift 3.2 และ 4.x:

extension Array {
    /// Fisher-Yates shuffle
    mutating func shuffle() {
        for i in stride(from: count - 1, to: 0, by: -1) {
            swapAt(i, Int(arc4random_uniform(UInt32(i + 1))))
        }
    }
}

ใน Swift 3.0 และ 3.1:

extension Array {
    /// Fisher-Yates shuffle
    mutating func shuffle() {
        for i in stride(from: count - 1, to: 0, by: -1) {
            let j = Int(arc4random_uniform(UInt32(i + 1)))
            (self[i], self[j]) = (self[j], self[i])
        }
    }
}

หมายเหตุ: วิธีการแก้ปัญหาที่รัดกุมมากขึ้นในสวิฟท์เป็นไปได้จาก iOS10 GameplayKitใช้

หมายเหตุ: อัลกอริทึมสำหรับการสับแบบไม่เสถียร (พร้อมกับทุกตำแหน่งที่บังคับให้เปลี่ยนหากนับ> 1) ก็มีให้เช่นกัน


อะไรคือความแตกต่างระหว่างอัลกอริธึมของคริสโตเฟอร์จอห์นสันนี้
Iulian Onofrei

@IulianOnofrei เดิมรหัสของ Kristopher Johnson นั้นไม่เหมาะสมและฉันได้ปรับปรุงคำตอบของเขาจากนั้นก็แก้ไขอีกครั้งด้วยการเพิ่มการตรวจสอบครั้งแรกที่ไร้ประโยชน์ ฉันชอบวิธีรัดกุมในการเขียน อัลกอริทึมเหมือนกันและมีการอธิบายไว้ในวรรณกรรมว่า " สับเปลี่ยน Fisher-Yates "
Cœur

6

นี่เป็นวิธีที่ง่ายและรวดเร็วที่สุดในการสลับ NSArrays หรือ NSMutableArrays (ปริศนาวัตถุเป็น NSMutableArray มันมีวัตถุปริศนาฉันได้เพิ่มดัชนีตัวแปรวัตถุปริศนาซึ่งระบุตำแหน่งเริ่มต้นในอาร์เรย์)

int randomSort(id obj1, id obj2, void *context ) {
        // returns random number -1 0 1
    return (random()%3 - 1);    
}

- (void)shuffle {
        // call custom sort function
    [puzzles sortUsingFunction:randomSort context:nil];

    // show in log how is our array sorted
        int i = 0;
    for (Puzzle * puzzle in puzzles) {
        NSLog(@" #%d has index %d", i, puzzle.index);
        i++;
    }
}

บันทึกเอาท์พุท:

 #0 has index #6
 #1 has index #3
 #2 has index #9
 #3 has index #15
 #4 has index #8
 #5 has index #0
 #6 has index #1
 #7 has index #4
 #8 has index #7
 #9 has index #12
 #10 has index #14
 #11 has index #16
 #12 has index #17
 #13 has index #10
 #14 has index #11
 #15 has index #13
 #16 has index #5
 #17 has index #2

คุณอาจเปรียบเทียบ obj1 กับ obj2 และตัดสินใจว่าคุณต้องการส่งคืนค่าที่เป็นไปได้คือ:

  • NSOrderedAscending = -1
  • NSOrderedSame = 0
  • NSOrderedDescending = 1

1
สำหรับโซลูชันนี้ให้ใช้ arc4random () หรือ seed
Johan Kool

17
สับเปลี่ยนนี้เป็นข้อบกพร่อง - ไมโครซอฟท์เพิ่งได้รับการเตือนจาก: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Raphael Schweikert

เห็นด้วยข้อบกพร่องเพราะ "การเรียงลำดับจำเป็นต้องมีคำสั่งที่สอดคล้องกันของการสั่งซื้อ" ตามที่ระบุไว้ในบทความเกี่ยวกับ MS ดูหรูหรา แต่ไม่ใช่
Jeff

2

มีห้องสมุดที่เป็นที่นิยมที่ดีที่มีวิธีการนี้มันเป็นส่วนหนึ่งที่เรียกว่าเป็นSSToolKit ใน GitHub ไฟล์ NSMutableArray + SSToolkitAdditions.h มีวิธีการสลับ คุณสามารถใช้มันได้ ในจำนวนนี้ดูเหมือนจะมีสิ่งที่มีประโยชน์มากมาย

หน้าหลักของห้องสมุดนี้เป็นที่นี่

หากคุณใช้สิ่งนี้รหัสของคุณจะเป็นดังนี้:

#import <SSCategories.h>
NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];

ห้องสมุดนี้มี Pod ด้วย (ดู CocoaPods)


2

จาก iOS ของคุณ 10 คุณสามารถใช้NSArray shuffled()จาก GameplayKit นี่คือผู้ช่วยสำหรับ Array in Swift 3:

import GameplayKit

extension Array {
    @available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
    func shuffled() -> [Element] {
        return (self as NSArray).shuffled() as! [Element]
    }
    @available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
    mutating func shuffle() {
        replaceSubrange(0..<count, with: shuffled())
    }
}

1

หากองค์ประกอบมีการทำซ้ำ

เช่นอาร์เรย์: AAABB หรือ BBAAA

ทางออกเดียวคือ: ABABA

sequenceSelected เป็น NSMutableArray ซึ่งเก็บองค์ประกอบของคลาส obj ซึ่งเป็นตัวชี้ไปยังลำดับบางส่วน

- (void)shuffleSequenceSelected {
    [sequenceSelected shuffle];
    [self shuffleSequenceSelectedLoop];
}

- (void)shuffleSequenceSelectedLoop {
    NSUInteger count = sequenceSelected.count;
    for (NSUInteger i = 1; i < count-1; i++) {
        // Select a random element between i and end of array to swap with.
        NSInteger nElements = count - i;
        NSInteger n;
        if (i < count-2) { // i is between second  and second last element
            obj *A = [sequenceSelected objectAtIndex:i-1];
            obj *B = [sequenceSelected objectAtIndex:i];
            if (A == B) { // shuffle if current & previous same
                do {
                    n = arc4random_uniform(nElements) + i;
                    B = [sequenceSelected objectAtIndex:n];
                } while (A == B);
                [sequenceSelected exchangeObjectAtIndex:i withObjectAtIndex:n];
            }
        } else if (i == count-2) { // second last value to be shuffled with last value
            obj *A = [sequenceSelected objectAtIndex:i-1];// previous value
            obj *B = [sequenceSelected objectAtIndex:i]; // second last value
            obj *C = [sequenceSelected lastObject]; // last value
            if (A == B && B == C) {
                //reshufle
                sequenceSelected = [[[sequenceSelected reverseObjectEnumerator] allObjects] mutableCopy];
                [self shuffleSequenceSelectedLoop];
                return;
            }
            if (A == B) {
                if (B != C) {
                    [sequenceSelected exchangeObjectAtIndex:i withObjectAtIndex:count-1];
                } else {
                    // reshuffle
                    sequenceSelected = [[[sequenceSelected reverseObjectEnumerator] allObjects] mutableCopy];
                    [self shuffleSequenceSelectedLoop];
                    return;
                }
            }
        }
    }
}

การใช้การstaticป้องกันการทำงานกับหลาย ๆ อินสแตนซ์: มันจะปลอดภัยกว่าและอ่านได้ง่ายกว่าการใช้สองวิธีซึ่งเป็นวิธีหลักที่สามารถสับเปลี่ยนและเรียกเมธอดรองได้ในขณะที่วิธีรองเรียกเฉพาะตัวเองเท่านั้น นอกจากนี้ยังมีการสะกดผิด
Cœur

-1
NSUInteger randomIndex = arc4random() % [theArray count];

2
หรือarc4random_uniform([theArray count])จะดียิ่งขึ้นถ้ามีในเวอร์ชันของ Mac OS X หรือ iOS ที่คุณรองรับ
Kristopher Johnson

1
ฉันเราได้รับเช่นนี้จำนวนจะทำซ้ำ
Vineesh TP

-1

คำตอบของ Kristopher Johnsonนั้นค่อนข้างดี แต่ก็ไม่ได้สุ่มทั้งหมด

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

- (void)shuffle
{
   NSUInteger count = [self count];
   for (NSUInteger i = 0; i < count; ++i) {
       NSInteger exchangeIndex = arc4random_uniform(count);
       if (i != exchangeIndex) {
            [self exchangeObjectAtIndex:i withObjectAtIndex:exchangeIndex];
       }
   }
}

ฉันคิดว่าอัลกอริทึมที่คุณแนะนำคือ "ไร้เดียงสา" ดูblog.codinghorror.com/the-danger-of-naivete ฉันคิดว่าคำตอบของฉันมีโอกาส 50% ในการสลับองค์ประกอบถ้ามีเพียงสอง: เมื่อฉันเป็นศูนย์ arc4random_uniform (2) จะกลับ 0 หรือ 1 ดังนั้นองค์ประกอบ zeroth จะแลกเปลี่ยนกับตัวเองหรือแลกเปลี่ยนกับ oneth ธาตุ. ในการทำซ้ำครั้งถัดไปเมื่อฉันเป็น 1, arc4random (1) จะส่งกลับค่า 0 เสมอและองค์ประกอบ ith จะแลกเปลี่ยนกับตัวเองเสมอซึ่งไม่มีประสิทธิภาพ แต่ไม่ถูกต้อง (อาจเป็นเงื่อนไขของลูปi < (count-1))
Kristopher Johnson

-2

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

รหัสง่าย ๆ ที่นี่:

- (NSArray *)shuffledArray:(NSArray *)array
{
    return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        if (arc4random() % 2) {
            return NSOrderedAscending;
        } else {
            return NSOrderedDescending;
        }
    }];
}

สับเปลี่ยนนี้เป็นข้อบกพร่อง - robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Cœur
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.