How to randomize letters correctly from an NSString - ios

I am creating a word scrambler and I am having issues randomizing the letters. When the letters get randomized, it doesn't make sense.
For example, the word PARK shows as AAPA. So, as you can tell it won't make sense for the user when it is time to unscramble.
Just so you know, I am using a .plist file to hold the words.
This is the code I am using to randomize the letters:
_words = [NSMutableArray arrayWithCapacity:scramblelength];
for (int i=0;i<scramblelength;i++) {
NSString *letter = [scramble substringWithRange:[scramble rangeOfComposedCharacterSequenceAtIndex:arc4random()%[scramble length]]];
Then, I am creating UIImageViews to display the scrambled words:
if (![letter isEqualToString:#""]) {
GameView *boxes = [[GameView alloc] initWithLetter:letter andSideLength:boxSide];
boxes.center = CGPointMake(xOffset + i*(boxSide + kTileMargin), kScreenHeight/4*3);
[self.scrambleView addSubview:boxes];
[_words addObject:boxes];
What am I doing wrong here? I would like for the letters in the scrambled words to make sense.
Please help, I am stuck on this one!
Thanks!

As long as your string length will fit in 32 bits, this should be fine. If not, I would replace arc4random_uniform with a uniform random number generator in C++ and compile this as an Objective-C++ module.
The code simply iterates through the string, and swaps each composed character sequence with some random composed character sequence from the same string.
Sorry, that's what happens when you are arrogant and just type out code. Let me know if you have trouble with this one...
For much larger strings, there is a more efficient way, but this seems to do the trick.
NSMutableString category...
#interface NSMutableString (Scramble)
- (void)scramble;
#end
#implementation NSMutableString (Scramble)
static void
swapRanges(NSMutableString *string, NSRange iRange, NSRange jRange)
{
// Need to replace the "trailing" component first
if (NSEqualRanges(iRange, jRange)) return;
if (iRange.location > jRange.location) {
NSRange tmpRange = iRange;
iRange = jRange;
jRange = tmpRange;
}
NSString *iString = [self substringWithRange:iRange];
NSString *jString = [self substringWithRange:jRange];
[string replaceCharactersInRange:jRange withString:iString];
[string replaceCharactersInRange:iRange withString:jString];
}
- (void)scramble
{
for (NSUInteger i = 0; i < self.length; ++i) {
NSRange iRange = [self rangeOfComposedCharacterSequenceAtIndex:i];
NSUInteger j = arc4random_uniform(self.length);
NSRange jRange = [self rangeOfComposedCharacterSequenceAtIndex:j];
swapRanges(self, iRange, jRange);
}
}
#end
NSString category...
#interface NSString (Scramble)
- (NSString*)scrambledString;
#end
#implementation NSString (Scramble)
- (NSString *)scrambledString
{
NSMutableString *result = [self mutableCopy];
[result scramble];
return [result copy];
}
#end
Sample use...
[someMutableString scramble];
NSString *mixedUp = [someString scrambledString];
Or, if you are comfortable with C++, convert to a std::wstring, call std::random_shuffle, then convert that to a NSString. Lots less bugs when using proven, well tested code.

When you are getting a random letter, you need to do something to remove that letter from your NSMutableArray (ie the word's letters when in order). So as you iterate through the word, each time there are fewer characters remaining. Right now, from your limited code block (the first one), it appears you might not be doing that. You want something like "[_words removeObjectAtIndex:letterIndex]" and you would also want to iterate from number of letters down to zero as you remove items from the array also: for (int i=[_words count]; i > [_words count]; i--) because you need to go from 4 letters down to 0 letters left.

So, I'm sure there are more efficient ways to do this, but I go by the rule of not optimizing until you need to. With that in mind, this code appears to work correctly:
- (NSString *)scrambleWord:(NSString *)word {
NSMutableArray *letterArray = [self letterArrayFromWord:word];
NSMutableString *returnValue = [[NSMutableString alloc] init];
do {
int randomIndex = arc4random() % letterArray.count;
[returnValue appendString:letterArray[randomIndex]];
[letterArray removeObjectAtIndex:randomIndex];
if (letterArray.count == 1) {
[returnValue appendString:letterArray[0]];
break;
}
} while (YES);
if ([[returnValue copy] isEqualToString:word]) {
return [self scrambleWord:word];
} else {
return [returnValue copy];
}
}
- (NSMutableArray *)letterArrayFromWord:(NSString *)word {
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < word.length; i = i + 1) {
[array addObject:[NSString stringWithFormat:#"%C", [word characterAtIndex:i]]];
}
return array;
}

Related

What would be a way to get all combinations of a NSString in Objective-C in a specific pattern?

I have a NSString *strName = #"JonnySmith";
What I want to do is get an NSArray of NSStrings with all possible combinations of a name, omitting certain characters. For example:
#"J";
#"Jo";
#"Jon";
but also combinations like:
#"JSmith";
#"JonSmith"
#"JonnSm";
#"JonSmt";
#"Smith";
#"th";
But they need to be in the order of the original name (the characters can't be out of order, just omitted). Basically traversing left to right in a loop, over and over again, until all possible combos are made.
What is the most efficient way to do this in Objective-C without make a mess?
Let's see if we can give you some pointers, everything here is abstract/pseudocode.
There are 2^n paths to follow, where n is the number of characters, as at each character you either add it or do not.
Taking your example after the first character you might produce #"" and #"J", then to each of these you either add the second character or not, giving: #"", #"J" (add nothing), #"o", "#Jo". Observe that if you have repeated characters anywhere in your input, in your sample you have two n's, this process may produce duplicates. You can deal with duplicates by using a set to collect your results.
How long is a character? Characters may consist of sequences of unicode code points (e.g. 🇧🇪 - Belgium flag, if it prints in SO! Letters can be similarly composed), and you must not split these composed sequences while producing your strings. NSString helps you here as you can enumerate the composed sequences invoking a block for each one in order.
The above give you the pseudocode:
results <- empty set
for each composed character in input do block:
add to results a copy of each of its members with the composed character appended
You cannot modify a collection at the same time you enumerate it. So "add to results" can be done by enumerating the set creating a new collection of strings to add, then adding them all at once after the enumeration:
new items <- empty collection
for every item in results
add to new items (item appending composed character)
results union new items
Optimising it slightly maybe: in (2) we had the empty string and in (4) we append to the empty string. Maybe you could not add the empty string to start and initialise new items to the composed character?
Hint: why did I write the non-specific collection in (4)?
Have fun. If you code something up and get stuck ask a new question, describe your algorithm, show what you've written, explain the issue etc. That will (a) avoid down/close votes and (b) help people to help you.
One possibility is to consider every combination to be a mask of bits, where 1 means the character is there and 0 means the character is missing, for example:
100010000 for JonnySmith will mean JS
000000001 for JonnySmith will mean h
It's simple to generate such masks because we can just iterate from 1 (or 000000001) to 111111111.
Then we only have to map that mask into characters.
Of course, some duplicates are generated because 1110... and 1101... will both be mapped to Jon....
Sample implementation:
NSString *string = #"JonnySmith";
// split the string into characters (every character represented by a string)
NSMutableArray<NSString *> *characters = [NSMutableArray array];
[string enumerateSubstringsInRange:NSMakeRange(0, string.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
[characters addObject:substring];
}];
// let's iterate over all masks
// start with zero if you want empty string to be included
NSUInteger min = 1;
NSUInteger max = (1 << characters.count) - 1;
NSMutableString *buffer = [[NSMutableString alloc] initWithCapacity:characters.count];
NSMutableSet *set = [NSMutableSet set];
for (NSUInteger mask = min; mask <= max; mask++) {
[buffer setString:#""];
// iterate over all bits in the generated mask, map it to string
for (NSInteger charIndex = 0; charIndex < characters.count; charIndex++) {
if ((mask & (1 << (characters.count - charIndex - 1))) != 0) {
[buffer appendString:[characters objectAtIndex:charIndex]];
}
}
// add the resulting string to Set, will handle duplicates
[set addObject:[buffer copy]];
}
NSLog(#"Count: %#", #(set.count)); // 767
The size for NSUInteger will give us the maximum number of characters we can use using this method.
Noticed the question is old but no answer is accepted. I think you can generate all permutations and then omit results which don't match your criteria (or tweak this code per your needs)
#interface NSString (Permute)
- (NSSet *)permutations;
#end
#implementation NSString (Permute)
- (NSSet *)permutations {
if ([self length] <= 1) {
return [NSSet setWithObject:self];
}
NSMutableSet *s = [NSMutableSet new];
[s addObject:[self substringToIndex:1]];
for (int i = 1; i < self.length; i++) {
char c = [self characterAtIndex:i];
s = [self words:s insertingLetterAtAllPositions:[NSString stringWithFormat:#"%C",c]];
}
return [s copy];
}
- (NSMutableSet *)words:(NSSet *)words insertingLetterAtAllPositions:(NSString *)letter {
NSMutableSet *collector = [NSMutableSet new];
for (NSString *word in words) {
[collector unionSet:[word allInsertionsOfLetterAtAllPositions:letter]];
}
return collector;
}
- (NSMutableSet *)allInsertionsOfLetterAtAllPositions:(NSString *)letter {
NSMutableSet *collector = [NSMutableSet new];
for (int i = 0; i < [self length] + 1; i++) {
NSMutableString *mut = [self mutableCopy];
[mut insertString:letter atIndex:i];
[collector addObject:[mut copy]];
}
return collector;
}
#end
// usage
[#"abc" permutations];
You can do it quite easily with a little recursion. It works like this:
Check if the length is 1, then return an array of 2 elements, the empty string and the string.
Call recursively with input string minus the first character and assign to sub-result.
Duplicate the sub-result, adding the first character to each string.
Return the result.
Remember to not call for empty string. If you want to omit the empty result string just remove the first element. Also, if you use the same letter several times, you will get some result strings several times. Those can be removed afterwards.
- (void)combinations:(NSString *)string result:(NSMutableArray *)result {
if (string.length == 1) {
[result addObjectsFromArray:#[ #"", string ]];
} else {
[self combinations:[string substringFromIndex:1] result:result];
for (NSInteger i = result.count - 1; i >= 0; --i)
[result addObject:[[string substringToIndex:1] stringByAppendingString:result[i]]];
}
}
// Call like this, for speed only one mutable array is allocated
NSString *test = #"0123456789";
NSMutableArray *result = [NSMutableArray arrayWithCapacity:1 << test.length];
[self combinations:test result:result];

I need to have a mutable Array that has 8 interpolated strings in IOS

I'm new to IOS and I'm not sure if I'm on the right track. What I need to know is if I'm on the right track and if I'm off it a hint on what to fix so I can get back on track. The mutable Array should read an array of speakers and say "Hello, my name is <speakerArray>" it should do that 8 times with a different name each time. This is what I Have:
- (NSArray*)badgesForSpeakers:(NSArray*)speakers {
for(speakers i = 0; i => 7; i++)
{
NSString *greetings =#"Hello, my name is .";
NSMutableArray *badges = [speakers arrayByAddingObjectsFromArray:greetings];
}
return badges;
}
Let's take this one step at a time. First of all, your operator in the loop is wrong; you mean to execute while i is less than or equal to 7. Thus, change => to <=. However, it's more stylish to say i < 8. And finally, it's most stylish of all to use what's called "Fast Enumeration", which allows you to loop without an index at all. In fact, it will work no matter how many items are in your speakers array! That takes us here:
- (NSArray*)badgesForSpeakers:(NSArray*)speakers {
for (NSString* speaker in speakers)
{
NSString *greetings =#"Hello, my name is .";
NSMutableArray *badges = [speakers arrayByAddingObjectsFromArray:greetings];
}
return badges;
}
Next, greetings isn't an array! It's a string. That's why calling -arrayByAddingObjectsFromArray: doesn't make any sense, and why the compiler isn't going to like it. Let's make its name singular, greeting, to reflect this fact. Strategy: Your goal here is to create an empty array, then construct items one by one and add them to that array. That takes us to:
- (NSArray*)badgesForSpeakers:(NSArray*)speakers {
NSMutableArray *badges = [NSMutableArray array]; //Here we make an empty array
for (NSString* speaker in speakers)
{
NSString *greeting =#"Hello, my name is .";
[badges addObject:greeting]; //Here we add one item to it each time 'round the loop
}
return badges;
}
Last, your string has no interpolation right now! It reads literally "Hello, my name is ." We do string interpolation using the -stringWithFormat: method.
Finished Product:
- (NSArray*)badgesForSpeakers:(NSArray*)speakers {
NSMutableArray *badges = [NSMutableArray array];
for (NSString* speaker in speakers)
{
NSString *greeting = [NSString stringWithFormat:#"Hello, my name is %#.",speaker];
[badges addObject:greeting];
}
return badges;
}
That should get you started with fast enumeration and string interpolation. Remember to compile your code often and try to understand the compiler errors--it would have helped you with some of these issues.
Maybe you mean this
- (NSMutableArray *)badgesForSpeakers:(NSArray *)speakers {
NSMutableArray *badges = [[NSMutableArray alloc] init];
for (NSString *speaker in speakers) {
[badges addObject:[NSString stringWithFormat:#"Hello, my name is %#", speaker]];
}
return badges;
}
plz use this code
- (NSArray*)badgesForSpeakers:(NSArray*)speakers {
NSMutableArray *badges = [NSMutableArray alloc];
for(int i = 0; i < speakers.count; i++)
{
NSString *greetings =[NSString stringWithFormat:#"Hello, my name is .%#",[speakers objectAtIndex:i]];
badges = [speakers addObject:greetings];
}
return [badges copy];
}

Create string based on characters expected

I would like to create a string based on the number of characters passed in. Each character passed in will be a "X". So for example, if the length passed in is 5, then the string created should be
NSString *testString=#"XXXXX";
if it is 2 then it would be
NSString *testString=#"XX";
Can anyone tell me what the most efficient way to do this would be?
Thank you!
If you know the maximum length is some reasonable number then you could do something simple like this:
- (NSString *)xString:(NSUInteger)length {
static NSString *xs = #"XXXXXXXXXXXXXXXXXXXXXXXXXXX";
return [xs substringToIndex:length];
}
NSString *str = [self xString:5]; // str will be #"XXXXX";
If you pass in too large of a length, the app will crash - add more Xs to xs.
This approach is more efficient than building up an NSMutableString but it does make an assumption about the maximum length you might need.
- (NSString *)stringOf:(NSString *)str times:(NSInteger)count
{
NSMutableString *targ = [[NSMutableString alloc] initWithCapacity:count];
for (int i=0; i < count; i++)
{
[targ appendString:str];
}
return targ;
}
and
[self stringOf:#"X" times:4];
note that initWithCapacity: (in performance manner) better than init. But I guess that's all for efficiency.
The way I would do it is
NSMutableString *xString = [[NSMutableString alloc] init];
while ( int i = 0; i < testString.length; i++ ) {
[xString appendString:#"X"];
i++;
}
NSUInteger aLength. // assume this is the argument
NSMutableString *xStr = [NSMutableString stringWithCapacity: aLength];
for ( NSUInteger i = 0; i < aLength; i++ ) {
[xStr appendFormat:#"X"];
}
The following will do what you ask in one call:
NSString *result = [#"" stringByPaddingToLength:numberOfCharsWanted
withString:characterToRepeat
startingAtIndex:0];
where numberOfCharsWanted is an NSUInteger and characterToRepeat is an NSString containing the character.

IOS word game. Validate word performance [closed]

Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 10 years ago.
Improve this question
I'm building a scrabble game, and having some problem with the word dictionary. It contains ~700,000 words, and about 18 MB big.
Right now, I'm loading the whole dict into an array, which takes 12 seconds on an iPhone 4.
wordList = [NSMutableArray arrayWithContentsOfFile: [[self applicationDocumentsDirectory] stringByAppendingString:#"/wordlist.plist"]];
I have two questions:
Is there a better way to load the wordlist faster and/or reduce memory?
It takes about 12 seconds to get all possible words from a set of letters. Is it possible to make it quicker? Here's the code:
-(NSMutableArray *)getValidWords:(NSString *)letters{
NSMutableArray *list = [[NSMutableArray alloc] init];
for (int i = 0, c = [wordList count]; i < c; i++){
if ([self isWordValid: [wordList objectAtIndex: i] forLetters:letters]){
[list addObject:[wordList objectAtIndex: i]];
}
}
return list;
}
- (BOOL)isWordValid:(NSString *)word forLetters:(NSString *)ltrs{
int i, z;
NSRange range;
BOOL found;
static NSMutableString *letters = nil;
if ([word length] < 2) return NO;
if(letters == nil) {
letters = [[NSMutableString alloc] initWithString:ltrs];
}
else {
[letters setString: ltrs];
}
found = NO;
range.length = 1;
for(i = 0; i < [word length]; i++){
for(z = 0; z < [letters length]; z++){
if([word characterAtIndex:i] == [letters characterAtIndex:z]){
range.location = z;
[letters deleteCharactersInRange: range];
found = YES;
break;
}
}
if (found == NO){
return NO;
}
found = NO;
}
return YES;
}
You need to change few things to speed up.
Use fast enumeration in place of old C-style loop.
Avoid a lot of method calls.
Use NSPredicate and/or Regex if possible.
As whenever you write [letters length] a method is called, instead of finding it millions of time (this is inside 3rd level of nested loop), store it in a variable and use it.
Fast enumeration : Instead of for(int i=0; i<[someArrays count];i++) use for(id object in someArrays).
Use this
[NSThread detachNewThreadSelector:#selector(fetchWords:) toTarget:self withObject:data];
Do not do it in main thread
use this code modify it if u need to search words
NSMutableArray *subpredicates = [NSMutableArray array];
for(NSString *term in arryOfWordsToBeSearched) {
NSPredicate *p = [NSPredicate predicateWithFormat:#"self contains[cd] %#",term];
[subpredicates addObject:p];
}
NSPredicate *filter = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
result = (NSMutableArray*)[arryOfDummyData filteredArrayUsingPredicate: filter];
//result is a array
Just for starters, create a NSCharacterSet from your letters and call this function before calling the lengthy function. This is a faster check to reduce the possibilities and it should improve your computation time.
NSCharacterSet* lettersSet = [NSCharacterSet characterSetWithCharactersInString:letters];
- (BOOL)isWordValid:(NSString*)word forLettersSet:(NSCharacterSet*)lettersSet {
if ([word length] < 2) return NO;
NSCharacterSet* wordLetters = [NSCharacterSet characterSetWithCharactersInString:word];
return [lettersSet isSupersetOfSet:wordLetters];
}
Ideally, your word database should have precomputed the letter count for each word (e.g. every = {e=2, r=1, v=1, y=1} and your should work only with these structures. Note that the order of letters is not significant - using this fact can greatly improve the performance of your algorithm.
You can also try to create a Core Data database - every word will be one record with a number field for every letter. Then you can create a request which will return you the available words VERY fast.
(Of course, the database will probably take bigger amount of space).
EDIT: Now I have found out NSCountedSet class, so let's try to use it:
-(NSCountedSet*)lettersSetFromString:(NSString*)string {
NSCountedSet* letters = [NSCountedSet set];
[string enumerateSubstringsInRange:NSMakeRange(0, self.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[letters addObject:substring];
}];
}
-(NSMutableArray *)validWordsForLetters:(NSString*)letters {
NSCountedSet* lettersSet = [self lettersSetFromString:letters];
NSMutableArray* results = [NSMutableArray array];
for (NSString* word in wordList) {
if ([word length] < 2) {
continue;
}
NSCountedSet* wordLettersSet = [self lettersSetFromString:word];
if ([wordLettersSet isSubsetOfSet:lettersSet]) {
[results addObject:word];
}
}
return results;
}
Generating the counted sets for every word beforehand will help the perfomance a lot. Using a Core Data database will still be faster and better for the OS memory.

Obj-c Turning long string into multidimensional array

I have a long NSString, something like "t00010000t00020000t00030000" and so on. I need to split that up into each "t0001000".
I'm using...
NSArray *tileData = [[GameP objectForKey:#"map"] componentsSeparatedByString:#"t"];
And it splits it up, but the "t" is missing, which I need (although I think I could append it back on). The other way I guess would be to split it up by counting 8 char's, although not sure how to do that.
But ideally I need it split into a [][] type array so I can get to each bit with something like...
NSString tile = [[tileData objectAtIndex:i] objectAtIndex:j]];
I'm new to obj-c so thanks for any help.
If they're not strictly the t characters that separate the sections, i. e. the parts are always 8 characters long, then it's very easy to do it:
NSString *string = #"t00010000t00020000t00030000";
NSMutableArray *arr = [NSMutableArray array];
int i;
for (i = 0; i < string.length; i += 8) {
[arr addObject:[string substringWithRange:NSMakeRange(i, 8)]];
}
and here arr will contain the 8-character substrings.
Edit: so let me also provide yet another solution for the multidimensional one. Of course #KevinH's solution with the characters is very elegant, but if you need an NSString and you don't mind implementing another method, it's fairly easy to add something like this:
#implementation NSString (EightCarAdditions)
- (NSString *)charAsStringInSection:(NSInteger)section index:(NSInteger)index
{
return [self substringWithRange:NSMakeRange(section * 8 + index, 1)];
}
- (unichar)charInSection:(NSInteger)section index:(NSInteger)index
{
return [self characterAtIndex:section * 8 + index];
}
#end
(Beware that characterAtIndex returns a unichar and not a char - be prepared for more than 1 byte-wide UTF-(8, 16, 32) stuff.) You can then call these methods on an NSString itself, so it's very convenient:
unichar ch = [string charInSection:1 index:3];
H2CO3's answer is spot-on for the first part. For the second (the multi-dimensional array), if I understand what you want, you don't need another array for the individual characters. For each NSString in the array, you can access each character by calling characterAtIndex. So, extending the example above:
for (NSString *item in arr) {
NSLog(#"The fifth character of this string is: %C", [item characterAtIndex:4]);
}
And if you're looking to chain these together, as in your example, you can do that too:
NSLog(#"The fifth character of the fourth string is: %C",
[[arr objectAtIndex:3] characterAtIndex:4]);

Resources