IOS word game. Validate word performance [closed] - ios

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.

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

How do I count the occurrences of ALL characters of a string?

I am trying to find the number of times each character in a string is used. for example, in the string "wow" I would like to count the number of times the character "w" is used and the number of times the character "o" is used. I would then like to add these characters to an NSMutableArray. Is there a programmatic way to count the number of times all specific characters are used? To get the number of occurrences of ALL characters in an NSString? Or would I have to go through the process of counting the occurrences of each individual character separately?
See iOS - Most efficient way to find word occurrence count in a string
NSString *string = #"wow";
NSCountedSet *countedSet = [NSCountedSet new];
[string enumerateSubstringsInRange:NSMakeRange(0, [string length])
options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationLocalized
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop){
// This block is called once for each word in the string.
[countedSet addObject:substring];
// If you want to ignore case, so that "this" and "This"
// are counted the same, use this line instead to convert
// each word to lowercase first:
// [countedSet addObject:[substring lowercaseString]];
}];
NSLog(#"%#", countedSet);
NSLog(#"%#", [countedSet allObjects]);
NSLog(#"%d", [countedSet countForObject:#"w"]);
The exact answer depends on some questions -
Do you only want to count the characters a-z or do you want punctuation as well?
Do you need to count unicode characters or just 8 bit characters?
Is case important ie. is A different to a?
Assuming you only want to count 8 bit, a-z independent of case, you could use something like -
- (NSArray *)countCharactersInString:(NSString *)inputString
{
NSMutableArray *result=[[NSMutableArray alloc]initWithCapacity:26];
for (int i=0;i<26;i++) {
[result addObject:[NSNumber numberWithInt:0]];
}
for (int i=0;i<[inputString length];i++)
{
unichar c=[inputString characterAtIndex:i];
c=tolower(c);
if (isalpha(c))
{
int index=c-'a';
NSNumber *count=[result objectAtIndex:index];
[result setObject:[NSNumber numberWithInt:[count intValue]+1] atIndexedSubscript:index];
}
}
return (result);
}
An alternative approach is to use an NSCountedSet - it handles all characterspunctuation etc, but will be 'sparse' - there is no entry for a character that is not present in the string. Also, the implementation below is case sensitive - W is different to w.
- (NSCountedSet *)countCharactersInString:(NSString *)inputString
{
NSCountedSet *result=[[NSCountedSet alloc]init];
for (int i=0;i<[inputString length];i++)
{
NSString *c=[inputString substringWithRange:NSMakeRange(i,1)];
[result addObject:c];
}
return result;
}
NSString *str = #"Program to Find the Frequency of Characters in a String";
NSMutableDictionary *frequencies = [[NSMutableDictionary alloc]initWithCapacity:52];
initWithCapacity:52 - capacity can be more depends on character set (for now : a-z, A-Z)
for (short i=0; i< [str length]; i++){
short index = [str characterAtIndex:i];
NSString *key = [NSString stringWithFormat:#"%d",index];
NSNumber *value = #1;
short frequencyCount=0;
if ([frequencies count] > 0 && [frequencies valueForKey:key]){
frequencyCount = [[frequencies valueForKey:key] shortValue];
frequencyCount++;
value = [NSNumber numberWithShort:frequencyCount];
[frequencies setValue:value forKey:key];
}
else{
[frequencies setValue:value forKey:key];
}
}
To display occurrence of each character in string
[frequencies enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSString *ky = (NSString*)key;
NSNumber *value = (NSNumber*)obj;
NSLog(#"%c\t%d", ([ky intValue]), [value shortValue]);
}];

How to randomize letters correctly from an NSString

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

Insert an object into NSMutable array and shift element one position ahead iOS

I want to insert an object (say 1.5) into NSMutableArray (say array with content: 1,2,3,4) between 1 and 2. The resultant array would be one element greater (say 1,1.5,2,3,4).
How can this be acheived in iOS using NSMutableArray?
Assuming you know the index to insert at, just use NSMutableArray's insertObject:atIndex: (reference). In your case, you want:
[yourMutableArray insertObject:#(1.5) atIndex:1];
If you don't know the index, you can do something like this (copy-pasted because nobody likes broken links):
#implementation NSMutableArray (SelfSorting)
- (void)insertNumberAtSortedLocation:(NSNumber *)aNumber
{
NSUInteger count = [self count];
// if there are no contents yet, simply add it
if (!count)
{
[self addObject:aNumber];
return;
}
NSRange searchRange;
searchRange.location = 0;
searchRange.length = count;
// bubble sort finding of insert point
do
{
NSInteger index = searchRange.location + searchRange.length/2;
NSNumber *testNumber = [self objectAtIndex:index];
switch ([aNumber compare:testNumber])
{
case NSOrderedAscending:
{
//searchRange.length = searchRange.length / 2;
searchRange.length = index - searchRange.location;
break;
}
case NSOrderedDescending:
{
int oldLocation = searchRange.location;
searchRange.location = index+1;
searchRange.length = searchRange.length - (searchRange.location - oldLocation);
break;
}
case NSOrderedSame:
{
searchRange.length = 0;
searchRange.location = index;
break;
}
}
} while (searchRange.length>0);
// insert at found point
[self insertObject:aNumber atIndex:searchRange.location];
}
And then, call:
[yourMutableArray insertNumberAtSortedLocation:#(1.5)];
Two lines of code
Just append the item and then sort or sort at usage time, sorting is actually very cheap, almost O(n) for non pathological cases.
NSMutableArray *a = [#[#1, #2 ,#3 ,#4] mutableCopy];
// Two lines of code:
[a addObject:#(1.5)];
[a sortUsingSelector:#selector(compare:)];
NSLog(#"a: %#", a);
NSLog oputput:
a: (
1,
"1.5",
2,
3,
4
)
The above should be O(log n)
Or if you don't want the entire array sorted, just insert after the first entry that is less than it:
NSUInteger count = [a count];
int index = 0;
while (index < count && [a[index] compare:aNumber] == NSOrderedAscending) {
index += 1;
}
[a insertObject:aNumber atIndex:index];
The above is O(n) as opposed to a binary search which is O(log n) but for most arrays there is not a meaningful time difference.
For the example in your question, you would write [array insertObject:#(1.5) atIndex:1]
NSMutableArray * foo = [[NSMutableArray alloc] init];
[foo insertObject:<#(id)#> atIndex:<#(NSUInteger)#>]

Less sensitive string comparison [duplicate]

This question already has answers here:
Comparing strings which are not exactly same
(3 answers)
Closed 9 years ago.
answered thanks to all that helped. I'm new to stack overflow as far as posting so if my edit is messy i apologize. the code below is what i ended up coming up with and think it may help others though i believe the person who answered last may also produce similar results.
I have been searching for a while now and I am sure I could break this down and get it done with a couple of sloppy if statements but I am looking for a common, efficient way.
I have a search with typical string comparison. For each string in an array using NSRange, but the results I get from that can be picky.
If I have a string for example Bat Man I could find it with searching Bat or Man but not once I add an m onto bat for Batm.
I guess what im looking for, is if the characters exist within the string and not exactly that string.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
[roomsList removeAllObjects];
stringScore = 0;
// clear
if([searchText length] == 0){
searchingRooms = NO;
[roomsList addObjectsFromArray:[newDataManager shared].rooms];
[self.gsgSearchBar resignFirstResponder];
// search users
}else{
searchingRooms = YES;
for(QBChatRoom *room in [newDataManager shared].rooms){
stringScore = 0;
NSString* newRoomNameText = [room.name stringByReplacingOccurrencesOfString:#"_" withString:#""];
NSString* newSearchText = [searchText stringByReplacingOccurrencesOfString:#" " withString:#""];
for(int i = 0 ;i<[newSearchText length]; i++) {
char mySearchChar = [newSearchText characterAtIndex:i];
lastSearchString = [NSString stringWithFormat:#"%c",mySearchChar];
NSRange searchRange = [newRoomNameText rangeOfString:lastSearchString options:NSCaseInsensitiveSearch];
if (searchRange.location != NSNotFound){
stringScore = stringScore + 1;
if (stringScore == newSearchText.length) {
[roomsList addObject:room];
}
}}}}
[gsgTableView reloadData];
}
You can use predicates to solve this problem. They come in very handy while filtering arrays.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"Self contains[c] %#",self.searchString.text];
NSArray *filteredArray = [self.superHeroesArray filteredArrayUsingPredicate:predicate];
Adding [c] after contains makes sure that the search is case insensitive.

Resources