एक NSMutableArray को फेरबदल करने का सबसे अच्छा तरीका क्या है?


187

यदि आपके पास एक है NSMutableArray, तो आप बेतरतीब ढंग से तत्वों को कैसे फेरबदल करते हैं?

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


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


इस प्रश्न पर एक नज़र डालें: अपने शिफ़िंग एल्गोरिथ्म के संबंध में भोले-भाले लोगों के साथ वास्तविक दुनिया की समस्याएं
१०:३० बजे क्रेग

यहाँ स्विफ्ट में एक कार्यान्वयन है: iosdevelopertips.com/swift-code/swift-shuffle-array-type.html
क्रिस्टोफर जॉनसन

5
वर्तमान सर्वश्रेष्ठ फिशर-येट्स हैं :for (NSUInteger i = self.count; i > 1; i--) [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
कॉर

2
दोस्तों, Apple द्वारा दिए गए शफल एरे की iOS 10 ++ नई अवधारणा से , इस उत्तर को देखें
मुकेश

मौजूदा समस्या APIयह है कि यह एक नया रिटर्न देता है Arrayजो मेमोरी में नए स्थान को संबोधित करता है।
द टाइगर

जवाबों:



349

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

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

संपादित करें: बदल दिया (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
अच्छा समाधान है। और हां, विलक 2 के रूप में, रैंडम () को आर्क 4random () के साथ बदलने के लिए एक अच्छा सुधार है क्योंकि कोई बीजारोपण की आवश्यकता नहीं है।
जेसन मूर

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

4
एक सुपर मामूली सुधार: लूप के अंतिम पुनरावृत्ति में, मैं == गिनती - 1. क्या इसका मतलब यह नहीं है कि हम सूचकांक में वस्तु का आदान-प्रदान कर रहे हैं? क्या हम हमेशा पिछले पुनरावृत्ति को छोड़ने के लिए कोड को ट्वीक कर सकते हैं?
रॉन

10
क्या आप किसी सिक्के को फ़्लिप करने पर विचार करते हैं यदि परिणाम उस पक्ष के विपरीत है जो मूल रूप से था?
क्रिस्टोफर जॉनसन

4
यह फेरबदल सूक्ष्म रूप से पक्षपाती है। के arc4random_uniform(nElements)बजाय का उपयोग करें arc4random()%nElements। अधिक जानकारी के लिए arc4random मैन पेज और modulo पूर्वाग्रह की यह व्याख्या देखें ।
1

38

चूंकि मैं अभी तक टिप्पणी नहीं कर सकता, मैंने सोचा कि मैं पूर्ण प्रतिक्रिया में योगदान दूंगा। मैंने अपनी परियोजना के लिए क्रिस्टोफर जॉनसन के कार्यान्वयन को कई तरीकों से संशोधित किया (वास्तव में इसे जितना संभव हो उतना संक्षिप्त करने की कोशिश कर रहा है), उनमें से एक होने के 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]लूप के माध्यम से प्रत्येक पुनरावृत्ति पर दो बार (एक संपत्ति पाने वाला) कॉल कर रहे हैं । मुझे लगता है कि इसे लूप से बाहर ले जाना सुगमता के नुकसान के लायक है।
क्रिस्टोफर जॉनसन

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

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

10

यदि आप आयात करते हैं GameplayKit, तो एक shuffledएपीआई है:

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

let shuffledArray = array.shuffled()

मेरे पास मेरा ऐरे है और मैं एक नया फेरबदल करना चाहता हूं। मैं इसे ऑब्जेक्टिव - सी के साथ कैसे करूं?
ओंकार जाधव

shuffledArray = [array shuffledArray];
andreacipriani 15

ध्यान दें कि यह विधि इसका हिस्सा है GameplayKitइसलिए आपको इसे आयात करना होगा।
एन्थोपैक जूल

9

थोड़ा सुधार हुआ और संक्षिप्त समाधान (शीर्ष उत्तरों की तुलना में)।

एल्गोरिथ्म समान है और साहित्य में " फिशर-येट्स फेरबदल " के रूप में वर्णित है ।

उद्देश्य-सी में:

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

नोट: स्विफ्ट का अधिक संक्षिप्त समाधान iOS10 के उपयोग से संभव है GameplayKit

नोट: अस्थिर फेरबदल के लिए एक एल्गोरिथ्म (सभी स्थितियों को बदलने के लिए मजबूर किया जाता है अगर गिनती> 1) भी उपलब्ध है


इस और क्रिस्टोफर जॉनसन के एल्गोरिथ्म में क्या अंतर होगा?
इयूलियन ओनोफ्रेई

@ इयूलियनऑनफ्रेई, मूल रूप से, क्रिस्टोफर जॉनसन का कोड इष्टतम नहीं था और मैंने उनके जवाब में सुधार किया, फिर इसे कुछ बेकार प्रारंभिक जांच के साथ फिर से संपादित किया गया। मैं इसे लिखने का अपना संक्षिप्त तरीका पसंद करता हूं। एल्गोरिथ्म समान है और साहित्य में " फिशर-येट्स फेरबदल " के रूप में वर्णित है ।
कूर

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

आप obj2 की तुलना obj2 के साथ भी कर सकते हैं और तय कर सकते हैं कि आप क्या करना चाहते हैं।

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

1
इस समाधान के लिए भी, आर्क 4 आयामी () या बीज का उपयोग करें।
जोहान कूल

17
यह फेरबदल त्रुटिपूर्ण है - जैसा कि हाल ही में Microsoft को याद दिलाया गया है: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
राफेल श्विकर्ट

सहमत, त्रुटिपूर्ण क्योंकि "छँटाई के लिए एमएस के बारे में उस लेख में बताया गया है" आदेश की एक आत्म-सुसंगत परिभाषा की आवश्यकता है। सुरुचिपूर्ण दिखता है, लेकिन नहीं है।
जेफ

2

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

इस पुस्तकालय का मुख्य पृष्ठ यहाँ है

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

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

इस लाइब्रेरी में एक पॉड भी है (कोकोपोड्स देखें)


2

IOS 10 से, आप GameplayKit से NSArray काshuffled() उपयोग कर सकते हैं । यहाँ स्विफ्ट 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रोकता है कई उदाहरण पर काम कर रहा है: यह अधिक सुरक्षित और दो तरीकों, एक मुख्य एक है कि फेरबदल करता है और माध्यमिक प्रणाली को बुलाती है उपयोग करने के लिए पढ़े जा सकेंगे, जबकि माध्यमिक विधि केवल खुद कहता है और फेरबदल कभी नहीं। साथ ही स्पेलिंग मिस्टेक भी है।
कूर

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

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

1
मैंने इस तरह दिया कि संख्या दोहराएगी।
विनेश टीपी

-1

क्रिस्टोफर जॉनसन का जवाब बहुत अच्छा है, लेकिन यह पूरी तरह से यादृच्छिक नहीं है।

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

मुझे लगता है कि आपके द्वारा सुझाया गया एल्गोरिदम एक "भोला-भाला व्यक्ति" है। ब्लॉग देखें । मुझे लगता है कि मेरे उत्तर में तत्वों की अदला-बदली का 50% मौका है यदि केवल दो ही हैं: जब मैं शून्य हो, तो आर्क 4 आयामी_ऑनफॉर्म (2) 0 या 1 में वापस आ जाएगा, इसलिए शून्य तत्व या तो स्वयं के साथ आदान-प्रदान किया जाएगा या oneth के साथ बदल दिया जाएगा। तत्व। अगले पुनरावृत्ति पर, जब मैं 1 होता है, तो आर्क 4 आयामी (1) हमेशा 0 वापस आ जाएगा, और आईआईटी तत्व का हमेशा खुद के साथ आदान-प्रदान किया जाएगा, जो अक्षम है लेकिन गलत नहीं है। (शायद लूप की स्थिति होनी चाहिए i < (count-1)।)
क्रिस्टोफर जॉनसन

-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
कोयूर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.