NSMutableArray'ı Karıştırmanın En İyi Yolu Nedir?


187

Eğer varsa NSMutableArray, öğeleri rastgele nasıl karıştırırsınız?

(Bunun için kendi cevabım var, aşağıda yayınlanmıştır, ancak Kakao için yeniyim ve daha iyi bir yol olup olmadığını bilmek istiyorum.)


Güncelleme: @Mukesh tarafından belirtildiği gibi, iOS 10+ ve macOS 10.12+'den itibaren, -[NSMutableArray shuffledArray]karıştırmak için kullanılabilecek bir yöntem var. Ayrıntılar için https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc adresine bakın. (Ancak bunun, öğeleri yerinde karıştırmak yerine yeni bir dizi oluşturduğunu unutmayın.)


Şu soruya bir göz atın: Karışık karıştırma algoritmanıza göre saf karıştırma ile gerçek dünya sorunları .
craigb


5
Şu anki en iyisi Fisher-Yates :for (NSUInteger i = self.count; i > 1; i--) [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
Cur

2
Çocuklar, Apple tarafından verilen iOS 10 ++ yeni shuffle dizisi konseptinden , Bu cevaba bakın
Mukesh

Varolan sorun , bellekteki yeni konuma adreslenen APIbir yeni döndürür Array.
TheTiger

Yanıtlar:



349

NSMutableArray'e bir kategori ekleyerek bunu çözdüm.

Edit: Ladd tarafından cevap sayesinde gereksiz yöntem kaldırıldı.

Düzenleme: Değiştirildi (arc4random() % nElements)içinarc4random_uniform(nElements) Gregory Goltsov ve yorumlarla cevap sayesinde Miho ve blahdiblah tarafından

Edit: Döngü iyileştirme, Ron tarafından yapılan yorum sayesinde

Düzenleme: Mahesh Agrawal tarafından yapılan yorum sayesinde dizinin boş olmadığından emin olun.

//  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
Güzel çözüm. Ve evet, willc2'den bahsedildiği gibi, random () yerine arc4random () koymak, tohumlama gerektirmediği için hoş bir gelişmedir.
Jason Moore

4
@Jason: Bazen (örneğin test ederken), bir tohum tedarik edebilmek iyi bir şeydir. Kristopher: güzel algoritma. Fisher-Yates algoritmasının bir uygulaması: en.wikipedia.org/wiki/Fisher-Yates_shuffle
JeremyP

4
Bir süper minör artış: döngünün son tekrarında i saymak == - 1. biz kendisiyle endeksi i de nesneyi alışverişi olduğunu ortalama gelmiyor mu? Her zaman son yinelemeyi atlamak için kodu değiştirebilir miyiz?
Ron

10
Madalyonun sadece sonuçta ortaya çıkan tarafın tersi olması durumunda çevrildiğini düşünüyor musunuz?
Kristopher Johnson

4
Bu karıştırma ince bir şekilde önyargılı. Yerine arc4random_uniform(nElements)kullanın arc4random()%nElements. Daha fazla bilgi için arc4random man sayfasına ve bu modulo önyargı açıklamasına bakın .
blahdiblah

38

Henüz yorum yapamadığım için tam bir yanıt vereceğimi düşündüm. Kristopher Johnson'un projem için uygulamasını birkaç şekilde değiştirdim (gerçekten mümkün olduğunca özlü yapmaya çalışıyorum), bunlardan biri modulo önyargısındanarc4random_uniform() kaçındığı için .

// 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]Döngü üzerinden her yinelemede iki kez (özellik alıcısı) çağırdığınızı unutmayın . Bence döngüden çıkarmak özlülük kaybına değer.
Kristopher Johnson

1
Ve bu yüzden hala [object method]bunun yerine tercih ediyorum object.method: insanlar daha sonra bir yapı üyesine erişmek kadar ucuz olmadığını, bir yöntem çağrısının maliyeti ile geliyor ... bir döngüde çok kötü.
DarkDust

Düzeltmeler için teşekkür ederim - Yanlış sayımın bir nedenden dolayı önbelleğe alındığını varsaydım. Yanıt güncellendi.
gregoltsov


9

Biraz gelişmiş ve özlü bir çözüm (en iyi cevaplara kıyasla).

Algoritma aynıdır ve literatürde " Fisher-Yates shuffle " olarak tanımlanmaktadır.

Hedef-C'de:

@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 ve 4.x sürümlerinde:

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 ve 3.1'de:

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

Not: iOS10 kullanarak Swift'te daha özlü bir çözüm mümkündür GameplayKit.

Not: Kararsız karıştırma için bir algoritma (sayı> 1 ise tüm pozisyonlar değişmeye zorlanır) da mevcuttur


Bu ve Kristopher Johnson algoritması arasındaki fark ne olurdu?
Iulian Onofrei

@IulianOnofrei, başlangıçta Kristopher Johnson'un kodu uygun değildi ve cevabını geliştirdim, sonra işe yaramayan ilk kontrol eklendiğinde tekrar düzenlendi. Kısa yazımı tercih ederim. Algoritma aynıdır ve literatürde " Fisher-Yates shuffle " olarak tanımlanmaktadır.
Cur

6

Bu, NSArrays veya NSMutableArrays'ı karıştırmanın en basit ve en hızlı yoludur (nesne bulmacaları bir NSMutableArray'dir, bulmaca nesneleri içerir. Dizideki başlangıç ​​konumunu gösteren bulmaca nesne değişken dizinine ekledim)

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

günlük çıkışı:

 #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 ile obj2'yi karşılaştırabilir ve olası değerleri ne döndürmek istediğinize karar verebilirsiniz:

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

1
Ayrıca bu çözüm için arc4random () veya tohum kullanın.
Johan Kool

17
Microsoft'un yakın zamanda hatırlattığı gibi bu karışıklık kusurlu: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html .
Raphael Schweikert

Kabul edildi, kusurlu çünkü "sıralama, MS hakkında o makalede belirtildiği gibi, kendi kendini tutarlı bir sıralama tanımı gerektirir". Zarif görünüyor, ama değil.
Jeff

2

GitHub'da SSToolKit adı verilen bu yöntemin parçası olan hoş bir popüler kütüphane var . NSMutableArray + SSToolkitAdditions.h dosyası shuffle yöntemini içerir. Ayrıca kullanabilirsiniz. Bunların arasında tonlarca yararlı şey var gibi görünüyor.

Bu kütüphanenin ana sayfası burada .

Bunu kullanırsanız, kodunuz şöyle olacaktır:

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

Bu kütüphanede ayrıca bir Pod (bkz. CocoaPods)


2

İOS 10'dan itibaren GameplayKit'ten NSArray'ishuffled() kullanabilirsiniz . İşte Swift 3'te Array için bir yardımcı:

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

Elemanların tekrarları varsa.

örneğin dizi: AAABB veya BBAAA

tek çözüm: ABABA

sequenceSelected bir nesneye işaret eden obj sınıfı sınıf öğelerini saklayan bir NSMutableArray.

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

staticbirden çok örnek üzerinde çalışmayı önleyen bir yöntem kullanmak: iki yöntem kullanmak çok daha güvenli ve okunaklı olacaktır, ikincisi karıştırma yapan ve ikincil yöntemi çağıran bir yöntemdir, ikincil yöntem ise yalnızca kendisini çağırır ve hiçbir zaman yeniden karıştırmaz. Ayrıca bir yazım hatası var.
Coeur

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

2
veya arc4random_uniform([theArray count])desteklediğiniz Mac OS X veya iOS sürümünde mevcutsa daha da iyi olabilir.
Kristopher Johnson

1
Bu şekilde verdik, numara tekrarlanacak.
Vineesh TP

-1

Kristopher Johnson'ın cevabı oldukça güzel, ama tamamen rastgele değil.

2 öğeden oluşan bir dizi verildiğinde, bu işlev her zaman tersine çevrilmiş diziyi döndürür, çünkü dizininizin geri kalanında rasgele aralığınızı oluşturursunuz. Daha doğru bir shuffle()fonksiyon,

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

Bence önerdiğiniz algoritma "saf bir karışıklık". Bkz. Blog.codinghorror.com/the-danger-of-naivete . Sanırım cevabımın sadece iki tane varsa elemanları takas etme şansı% 50'dir: i sıfır olduğunda, arc4random_uniform (2) 0 veya 1 döndürür, bu yüzden sıfırıncı eleman kendisiyle değiştirilir veya oneth ile değiştirilir öğesi. Bir sonraki yinelemede, i 1 olduğunda, arc4random (1) her zaman 0 döndürür ve ith öğesi her zaman kendisiyle değiştirilir, bu da verimsizdir ancak yanlış değildir. (Belki döngü koşulu olmalıdır i < (count-1).)
Kristopher Johnson

-2

Düzenleme: Bu doğru değil. Referans amacıyla, bu yazıyı silmedim. Bu yaklaşımın neden doğru olmadığına dair yorumlara bakın.

Basit kod burada:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.