Search code examples
objective-crandomuikituicolorarc4random

random color from an array for a different string


I'd like to pick random number for a different string. The following example is similar to my scenario:

+ (UIColor *)ivl_randomColorWithSeedString:(NSString *)seedString {

    srand48(seedString ? seedString.hash : arc4random());

    float red = 0.0;
    while (red < 0.1 || red > 0.84) {
        red = drand48();
    }

    float green = 0.0;
    while (green < 0.1 || green > 0.84) {
        green = drand48();
    }

    float blue = 0.0;
    while (blue < 0.1 || blue > 0.84) {
        blue = drand48();
    }

    return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
}

what I'd like to achieve is getting a random color for a different string, if I have the same string then return the same index.

- (void)setImageWithImage:(UIImage *)image orString:(NSString *)string colorArray:(NSArray *)colorArray circular:(BOOL)isCircular textAttributes:(NSDictionary *)textAttributes {
  if (!textAttributes) {
    textAttributes = @{
                       NSFontAttributeName: [self fontForFontName:nil],
                       NSForegroundColorAttributeName: [UIColor whiteColor]
                       };
  }

  NSMutableString *displayString = [NSMutableString stringWithString:@""];

  NSMutableArray *words = [[string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];

  //
  // Get first letter of the first and last word
  //
  if ([words count]) {
    NSString *firstWord = [words firstObject];
    if ([firstWord length]) {
      // Get character range to handle emoji (emojis consist of 2 characters in sequence)
      NSRange firstLetterRange = [firstWord rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, 1)];
      [displayString appendString:[firstWord substringWithRange:firstLetterRange]];
    }

    if ([words count] >= 2) {
      NSString *lastWord = [words lastObject];

      while ([lastWord length] == 0 && [words count] >= 2) {
        [words removeLastObject];
        lastWord = [words lastObject];
      }

      if ([words count] > 1) {
        // Get character range to handle emoji (emojis consist of 2 characters in sequence)
        NSRange lastLetterRange = [lastWord rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, 1)];
        [displayString appendString:[lastWord substringWithRange:lastLetterRange]];
      }
    }
  }

  srand48(string ? string.hash : arc4random());

  NSUInteger randomIndex = arc4random() % [colorArray count];
  UIColor *backgroundColor = colorArray[randomIndex];

  if (image != nil) {
    self.image = image;
  } else {
    self.image = [self imageSnapshotFromText:[displayString uppercaseString] backgroundColor:backgroundColor circular:isCircular textAttributes:textAttributes];
  }
}

What's the best approach on this?


Solution

  • You won't be able to achieve that with arc4random. You need to use seeded version of random number generator random and seed it with a value that is coming out of your string, that will be unique for different string values.

    The result of such generation is exactly as you want it - for same seed the generated sequence will be exactly the same each time. For different seed values, random sequence will be different.

    For example you could have length of string as seed, but to make it more mutable you could calculate sum of characters represented as int value or as you are doing right now use a hash function.

    So in your method you should be doing calling srandom(seed) at the beginning and use random instead of arc4random and srand48.

    I also wrote a library for such scenarios, which will be useful here:

    https://github.com/grzegorzkrukowski/RandomUtils

    To get random color, simply use:

    + (void) setSeed:(unsigned)seed;
    + (UIColor*) randomColorUseSeed:(BOOL)useSeed
    

    If you want to have colors in an array and just take one of those indexes, solution is the same:

    + (void) setSeed:(unsigned)seed;
    + (id) randomElementFromArray:(NSArray*) array useSeed:(BOOL) useSeed;
    

    As parameter for setSeed pass a value generated based on your strings described as above.