канонический способ рандомизировать NSArray в Objective-C

Есть ли канонический способ рандомизировать массив в Objective-C?


person George Armhold    schedule 26.04.2009    source источник
comment
возможный дубликат Как лучше всего перемешать NSMutableArray?   -  person Senseful    schedule 29.01.2015
comment
Рекомендуется Fisher-Yates для NSMutableArray: for (NSUInteger i = self.count; i > 1; i--) [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];   -  person Cœur    schedule 21.11.2015


Ответы (7)


Моя служебная библиотека определяет эту категорию в NSMutableArray для этого:

@interface NSMutableArray (ArchUtils_Shuffle)
- (void)shuffle;
@end

// Chooses a random integer below n without bias.
// Computes m, a power of two slightly above n, and takes random() modulo m,
// then throws away the random number if it's between n and m.
// (More naive techniques, like taking random() modulo n, introduce a bias 
// towards smaller numbers in the range.)
static NSUInteger random_below(NSUInteger n) {
    NSUInteger m = 1;

    // Compute smallest power of two greater than n.
    // There's probably a faster solution than this loop, but bit-twiddling
    // isn't my specialty.
    do {
        m <<= 1;
    } while(m < n);

    NSUInteger ret;

    do {
        ret = random() % m;
    } while(ret >= n);

    return ret;
}

@implementation NSMutableArray (ArchUtils_Shuffle)

- (void)shuffle {
    // http://en.wikipedia.org/wiki/Knuth_shuffle

    for(NSUInteger i = [self count]; i > 1; i--) {
        NSUInteger j = random_below(i);
        [self exchangeObjectAtIndex:i-1 withObjectAtIndex:j];
    }
}

@end

Убедитесь, что вы заполнили генератор случайных чисел (например, srandom(time(NULL))) перед его вызовом; иначе результат не будет очень случайным.

person Becca Royal-Gordon    schedule 26.04.2009
comment
Любить это! Спасибо, что поделился. Мне сейчас очень любопытна остальная часть вашей служебной библиотеки. - person PEZ; 11.01.2011
comment
По большей части это не алгоритмически - это все, от + [NSArray arrayWithCount: numbers:] (где числа - двойные) до - [UIView moveToSuperview: withFrame: animated:], плюс однообъектный стек Core Data для удовольствия. - person Becca Royal-Gordon; 11.01.2011
comment
У меня это не работает. Я имею в виду, что он немного перетасовывается, но некоторые части все еще в порядке. Также, учитывая тот же самый массив, первая перетасовка всегда имеет один и тот же результат. Следовательно, это не совсем случайно. - person ; 09.04.2011
comment
@bresc либо семя случайным образом с srandom(time(NULL)), либо вместо arc4random() - person Bartosz Ciechanowski; 27.04.2011
comment
Также можно использовать srandomdev () - person Catfish_Man; 30.05.2011
comment
Я добавил небольшую вещь: if (n == 0) { return 0; }. Вы можете просто не передать 0, но если вы это сделаете, вы попадете в ловушку этого второго цикла. - person jasongregori; 20.09.2011
comment
Если вы используете OSX 10.7 или iOS 4.3 или новее, вы можете использовать arc4random_uniform(upper_bound) и не должны писать или использовать функцию random_below(i). - person LavaSlider; 16.01.2012
comment
@LavaSlider Хороший момент! Вот реализация stackoverflow.com/a/10258341/396133 - person Abramodj; 21.04.2012

Вот!

- (NSArray*)shuffleArray:(NSArray*)array {

    NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:array];

    for(NSUInteger i = [array count]; i > 1; i--) {
        NSUInteger j = arc4random_uniform(i);
        [temp exchangeObjectAtIndex:i-1 withObjectAtIndex:j];
    }

    return [NSArray arrayWithArray:temp];
}
person Abramodj    schedule 21.04.2012

if ([array count] > 1) {
    for (NSUInteger shuffleIndex = [array count] - 1; shuffleIndex > 0; shuffleIndex--)
        [array exchangeObjectAtIndex:shuffleIndex withObjectAtIndex:random() % (shuffleIndex + 1)];
}

Обязательно заполните функцию random () либо srandomdev (), либо srandom ().

person Nathan Kinsinger    schedule 26.04.2009
comment
То, что вы написали, не имеет смысла как метод экземпляра. Поскольку код существует, это должна быть просто функция или метод класса. Идиоматически это должен быть метод экземпляра NSArray, который возвращает новый перетасованный массив, или метод экземпляра NSMutableArray, который перетасовывает сам себя. - person Chuck; 26.04.2009
comment
Правда, я просто демонстрировал здесь код, особо не беспокоился о том, чтобы поместить его в категорию. Я также забыл упомянуть, что для функции random () необходимо использовать srandomdev () или srandom (). - person Nathan Kinsinger; 26.04.2009
comment
Этот код слегка смещен из-за модуля; см. en.wikipedia.org/wiki/Fisher-Yates_shuffle#Modulo_bias для получения дополнительной информации. об этом. - person Becca Royal-Gordon; 27.04.2009

В SDK ничего не встроено, если вы об этом спрашиваете.

Однако вы можете использовать практически любой алгоритм рандомизации или перемешивания. Различные алгоритмы имеют разные компромиссы с точки зрения случайности, эффективности и т. Д.

http://en.wikipedia.org/wiki/Shuffling#Shuffling_algorithms

Для алгоритмов, которые перемешивают "на месте", начинаются с изменяемого массива, используйте

insertObject:atIndex:
removeObjectAtIndex:

Для алгоритмов, восстанавливающих массив, загрузите его исходным и создайте новый массив.

person amattn    schedule 26.04.2009

Мое решение - это метод категории, который возвращает копию массива (автоматически выпущенную) с элементами, рандомизированными (с использованием arc4random).

@interface NSArray (CMRandomised)

/* Returns a copy of the array with elements re-ordered randomly */
- (NSArray *)randomised;

@end

/* Returns a random integer number between low and high inclusive */
static inline int randomInt(int low, int high)
{
    return (arc4random() % (high-low+1)) + low;
}

@implementation NSArray (CMRandomised)

- (NSArray *)randomised
{
    NSMutableArray *randomised = [NSMutableArray arrayWithCapacity:[self count]];

    for (id object in self) {
        NSUInteger index = randomInt(0, [randomised count]);
        [randomised insertObject:object atIndex:index];
    }
    return randomised;
}

@end
person Chris Miles    schedule 15.10.2011

Нет канонического способа без создания категории на NSArray (т.е. иметь метод экземпляра, такой как arrayWithRandomizedIndices) или NSMutableArray (т.е. иметь метод, подобный randomizeIndices).

Вот пример из моей библиотеки, входящей в категорию на NSMutableArray. Он будет случайным образом переупорядочивать массив, а не перемешивать несколько записей.

- (void) randomizeIndices
{
  if (self == nil || [self count] <= 1)
  {
    return;
  }

  int count = [self count];

  NSMutableArray* copySelf = [NSMutableArray arrayWithArray:self];
  NSMutableArray* mutableResultArray = [NSMutableArray alloc];
  mutableResultArray = [mutableResultArray initWithCapacity:count];
  [mutableResultArray autorelease];

  int objectsMovedCount = 0;

  for (int i = 0; i < count; i++)
  {
    int index = rand() % (count - objectsMovedCount);
    id anObject = [copySelf objectAtIndex:index];
    [mutableResultArray addObject:anObject];
    [copySelf removeObjectAtIndex:index];
    objectsMovedCount++;
  }
  [self setArray:mutableResultArray];
}

Вызовите srand(time(0)); или что-то подобное перед вызовом этого метода или в начале метода.

person SK9    schedule 29.05.2011

Рандомизация NSArray как метод категории Objective-C:

@implementation NSArray (NGDataDynamics)

- (NSArray *)jumbled
{
  NSMutableArray *jumbled = self.mutableCopy;

  NSUInteger idx = self.count-1;
  while(idx)
  {
    [jumbled exchangeObjectAtIndex:idx
                 withObjectAtIndex:arc4random_uniform(idx)];
    idx--;
  }

  return jumbled;
}

@end

Как видно: NSArray Randomization & Psychedelia < / а>

person james_womack    schedule 01.07.2013