2008-09-11 15 views
181

यदि आपके पास NSMutableArray है, तो आप तत्वों को यादृच्छिक रूप से कैसे घुमाते हैं?एनएसएमयूटेबलएरे को शफल करने का सबसे अच्छा तरीका क्या है?

(मैं इस बात के लिए अपने ही जवाब है, जो नीचे पोस्ट किया जाता है है, लेकिन मैं कोको के लिए नया हूँ और मैं अगर वहाँ एक बेहतर तरीका है पता करने के लिए इच्छुक हूँ।)


अद्यतन: के रूप में @Mukesh द्वारा नोट किया गया, आईओएस 10+ और मैकोज़ 10.12+ के रूप में, -[NSMutableArray shuffledArray] विधि है जिसका उपयोग शफल करने के लिए किया जा सकता है। विवरण के लिए https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc देखें। (लेकिन ध्यान दें कि यह तत्वों को स्थानांतरित करने के बजाए एक नई सरणी बनाता है।)

+0

स्विफ्ट में एक कार्यान्वयन है: http://iosdevelopertips.com/swift-code/swift-shuffle-array-type में ऐरे के लिए एक सहायक है।एचटीएमएल –

+0

इस सवाल पर एक नज़र डालें: [बेवकूफ शफलिंग के साथ वास्तविक दुनिया की समस्याएं] (http://stackoverflow.com/questions/96840/real-world-problems-with-naive-shuffling) आपके शफल एल्गोरिदम के संबंध में। – craigb

+4

वर्तमान सर्वोत्तम [फिशर-येट्स] है (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle): 'के लिए (NSUInteger i = self.count; i> 1; i--) [self exchangeObjectAtIndex: i - 1 withObjectAtIndex: arc4random_uniform ((u_int32_t) i)]; ' –

उत्तर

341

मैंने एनएसएमयूटेबलएरे में एक श्रेणी जोड़कर इसे हल किया।

संपादित करें: अनावश्यक विधि हटा दी गई है जो लड द्वारा उत्तर देने के लिए धन्यवाद।

संपादित करें: बदल दिया (arc4random() % nElements)arc4random_uniform(nElements) को मिहो और blahdiblah द्वारा ग्रेगरी Goltsov से जवाब देने के लिए धन्यवाद और टिप्पणियों

संपादित करें: जोड़ा जांच: लूप सुधार, धन्यवाद रॉन

संपादित करें द्वारा टिप्पणी करने के लिए वह सरणी खाली नहीं है, महेश अग्रवाल द्वारा टिप्पणी करने के लिए धन्यवाद

// 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 उल्लेख करता है, arc4random() के साथ यादृच्छिक() को प्रतिस्थापित करना एक अच्छा सुधार है क्योंकि कोई बीजिंग आवश्यक नहीं है। –

+4

@ जेसन: कभी-कभी (उदाहरण के लिए जब परीक्षण), बीज की आपूर्ति करने में सक्षम होना एक अच्छी बात है। क्रिस्टोफर: अच्छा एल्गोरिदम। यह फिशर-येट्स एल्गोरिदम का कार्यान्वयन है: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle – JeremyP

+0

'यादृच्छिक() 'के बजाय' arc4random() 'का उपयोग करना आम तौर पर बेहतर होता है। संख्याओं की गुणवत्ता _much_ बेहतर है और बीजिंग की आवश्यकता नहीं है। – zaph

5

यह 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 

आप के रूप में अच्छी obj2 साथ obj1 तुलना और तय है कि तुम क्या संभावित मान वापस जाने के लिए चाहते हो सकता है कर रहे हैं:

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

इसके अलावा, इस समाधान के लिए, arc4random() या बीज का उपयोग करें। –

+17

यह शफल त्रुटिपूर्ण है - क्योंकि माइक्रोसॉफ्ट को हाल ही में याद दिलाया गया है: http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html। –

+0

सहमत, दोषपूर्ण क्योंकि एमएस के बारे में उस आलेख में बताया गया है कि "सॉर्टिंग को ऑर्डर करने की एक स्व-निरंतर परिभाषा की आवश्यकता होती है"। सुरुचिपूर्ण लग रहा है, लेकिन नहीं है। – Jeff

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

या 'arc4random_uniform ([theArray count])' मैक ओएस एक्स या आईओएस के संस्करण पर उपलब्ध होने पर भी बेहतर होगा, जो आप समर्थन कर रहे हैं। –

+1

मैंने इसे दिया है यह संख्या दोहराएगी। –

35

जब से मैं अभी तक टिप्पणी नहीं कर सकता, मैंने सोचा कि मैं एक पूर्ण प्रतिक्रिया योगदान चाहते हैं। मैंने अपनी परियोजना के लिए कई तरीकों से क्रिस्टोफर जॉनसन के कार्यान्वयन को संशोधित किया (वास्तव में इसे यथासंभव संक्षेप में बनाने की कोशिश कर रहा है), उनमें से एक arc4random_uniform() है क्योंकि यह modulo bias से बचाता है।

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

ध्यान दें कि आप लूप के माध्यम से प्रत्येक पुनरावृत्ति पर दो बार '[स्वयं गिनती]' (एक संपत्ति गेटटर) को बुला रहे हैं। मुझे लगता है कि इसे लूप से बाहर ले जाना एक समानता के नुकसान के लायक है। –

+1

और यही कारण है कि मैं अभी भी 'object.method' के बजाय' [ऑब्जेक्ट विधि] 'पसंद करता हूं: लोग भूल जाते हैं कि बाद में संरचना सदस्य तक पहुंचने के रूप में सस्ता नहीं है, यह एक विधि कॉल की लागत के साथ आता है ... एक पाश में बहुत बुरा है। – DarkDust

+0

सुधारों के लिए धन्यवाद - मैंने गलत तरीके से माना कि गिनती कैश की गई थी, किसी कारण से। उत्तर अपडेट किया गया। – gregoltsov

1

वहाँ एक अच्छा लोकप्रिय पुस्तकालय, इस पद्धति है कि के रूप में यह हिस्सा है, SSToolKit in GitHub कहा जाता है। फ़ाइल NSMutableArray + SSToolkitAdditions.h में शफल विधि शामिल है। आप इसका भी उपयोग कर सकते हैं। इनमें से, उपयोगी चीजें हैं।

इस लाइब्रेरी का मुख्य पृष्ठ here है।

आप इस का उपयोग करते हैं, अपने कोड इस तरह होगा:

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

यह पुस्तकालय भी एक Pod है (CocoaPods देखें)

0

हैं तत्वों को दोहराता है।

उदा। सरणी: एक एक एक बी बी या बी बी ए ए ए

एकमात्र समाधान है: एक बी ए बी ए

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; 
       } 
      } 
     } 
    } 
} 
+0

'स्थिर' का उपयोग करके कई उदाहरणों पर काम करना रोकता है: यह दो तरीकों का उपयोग करने के लिए अधिक सुरक्षित और पठनीय होगा, एक मुख्य जो शफल करता है और द्वितीयक विधि को कॉल करता है, जबकि द्वितीयक विधि केवल स्वयं को कॉल करती है और कभी भी पुनः बदलती नहीं है। एक वर्तनी गलती भी है। –

-1

Kristopher Johnson's answer बहुत अच्छा है, लेकिन यह पूरी तरह से यादृच्छिक नहीं है।

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]; 
     } 
    } 
} 
+0

मुझे लगता है कि आपके द्वारा सुझाए गए एल्गोरिदम एक "बेवकूफ शफल" है। Http://blog.codinghorror.com/the-danger-of-naivete/ देखें। मुझे लगता है कि मेरे जवाब में तत्वों को स्वैप करने का 50% मौका है यदि केवल दो हैं: जब मैं शून्य हूं, arc4random_uniform (2) या तो 0 या 1 लौटाएगा, इसलिए शून्य तत्व या तो स्वयं के साथ आदान-प्रदान किया जाएगा या ऑनथ के साथ आदान-प्रदान किया जाएगा तत्व। अगले पुनरावृत्ति पर, जब मैं 1 होता हूं, arc4random (1) हमेशा 0 लौटाएगा, और ith तत्व हमेशा स्वयं के साथ आदान-प्रदान किया जाएगा, जो अक्षम है लेकिन गलत नहीं है। (हो सकता है कि लूप स्थिति 'i <(count-1)'।) –

-2

संपादित होगा: यह सही नहीं है। संदर्भ उद्देश्यों के लिए, मैंने इस पोस्ट को नहीं हटाया। इस दृष्टिकोण को सही क्यों नहीं है इस कारण टिप्पणियां देखें।

सरल यहाँ कोड:

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

यह शफल त्रुटिपूर्ण है - http://robweir.com/blog/2010/02/microsoft-random-browser-ballot.html –

4

शीर्ष संपादित करने के बाद, मैं एक से थोड़ा सुधार हुआ और संक्षिप्त समाधान साझा करने के बारे में सोचा।

एल्गोरिदम समान है और साहित्य में "Fisher-Yates shuffle" के रूप में वर्णित है।

ObjectiveC में:

@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 

स्विफ्ट 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)))) 
     } 
    } 
} 

स्विफ्ट 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]) 
     } 
    } 
} 

नोट: A more concise solution in Swift is possible from iOS10 using GameplayKit.

नोट : An algorithm for unstable shuffling (with all positions forced to change if count > 1) is also available

+0

क्या होगा इस और क्रिस्टोफर जॉनसन के एल्गोरिदम के बीच अंतर हो? –

+0

@IulianOnofrei, मूल रूप से, क्रिस्टोफर जॉनसन का कोड इष्टतम नहीं था और मैंने उसका जवाब सुधार लिया, फिर इसे फिर से बेकार प्रारंभिक चेक के साथ संपादित किया गया। मैं इसे लिखने का अपना संक्षिप्त तरीका पसंद करता हूं। एल्गोरिदम समान है और साहित्य में वर्णित है "[फिशर-येट्स शफल] (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)"। –

5

आईओएस 10 से आप नई shuffled एपीआई का उपयोग कर सकते हैं:

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

let shuffledArray = array.shuffled() 
+0

मेरे पास myArray है और इसके बारे में एक नया शफल बनाना चाहते हैं। मैं उद्देश्य - सी के साथ कैसे करूँ? –

+0

'shuffledArray = [सरणी shuffledArray]; ' – andreacipriani

1

आईओएस 10 से, आप NSArray shuffled() from GameplayKit उपयोग कर सकते हैं। यहां स्विफ्ट 3:

import GameplayKit 

extension Array { 
    func shuffled() -> [Element] { 
     return (self as NSArray).shuffled() as! [Element] 
    } 
    mutating func shuffle() { 
     replaceSubrange(0..<count, with: shuffled()) 
    } 
}