Comparing string arrays - objective-c

Sorry in advance if this is a stupid question. I'm working on a simple program that compares two arrays filled with strings. One is a list of 1309 proper names the other is a list of 235,877 english words. The point of the program is to compare the lists, and have any words that appear on both lists added to a mutable array. Then, the program will enumerate through the mutable array and print out the words that are on both lists. Here is my code:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *nameString = [NSString stringWithContentsOfFile:#"/usr/share/dict/propernames"
encoding:NSUTF8StringEncoding
error:NULL];
NSString *wordString = [NSString stringWithContentsOfFile:#"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
NSArray *names = [nameString componentsSeparatedByString:#"\n"];
NSArray *words = [wordString componentsSeparatedByString:#"\n"];
NSMutableArray *namesAndWords = [[NSMutableArray alloc]init];
for (NSString *w in words){
for (NSString *n in names){
if ([[n lowercaseString] compare:w] == NSEqualToComparison){
[namesAndWords addObject: w];}}}
for (NSString *item in namesAndWords){
NSLog(#"%#", item);}
NSLog(#"There are %lu items in the array",[namesAndWords count]);
NSLog(#"%lu", [names count]);
NSLog(#"%lu", [words count]);
}
return 0;
}
As of right now, I've got this program working exactly as it should (showing 294 matches). My real question is when I first tried comparing the strings I tried it like this:
for (NSString *w in words){
for (NSString *n in names){
if ([n caseInsensitiveCompare:w] == NSEqualToComparison){
[namesAndWords addObject: w];}}}
and like this:
for (NSString *w in words){
for (NSString *n in names){
if ([n compare:w options:NSCaseInsensitiveSearch] == NSOrderedSame){
[namesAndWords addObject: w];}}}
These two ways both gave me 1602 matches and for some reason adds some items from both arrays into the mutable array namesAndWords. So for example in the console I will see Woody and woody printed out.
The other way I tried was this:
for (NSString *w in words){
for (NSString *n in names){
if ([n compare:w] == NSOrderedSame){
[namesAndWords addObject: w];}}}
When doing it this way it added all 1309 strings from the names array. Before running this I actually thought I wouldn't get any matches since I didn't specify it to be case insensitive.
I'm trying to figure out why these methods that seem so similar have the different results that they do. I'm also trying to find out why if ([[n lowerCaseString] compare:w] == NSEqualToComparison) is the right way to go. Any help here is greatly appreciated.

Because the below line checks the word only converts lowercase string of the first array and not the second one. It gets only matching value like m->m including duplicates.
[[n lowercaseString] compare:w] == NSEqualToComparison
Below is my workout for your problem.
NSMutableArray *actualarray1=[[NSMutableArray alloc] init];
NSMutableArray *actualarray2=[[NSMutableArray alloc] init];
actualarray1=[#[#"Apple",#"Litchi",#"Plum",#"Litchi",#"Pineapple",#"mango",#"Apple",#"berry",#"Pineapple",#"berry",#"mango",#"Apple"]mutableCopy];
actualarray2=[#[#"guava",#"Orange",#"Litchi",#"Pineapples",#"mangoes",#"Orange",#"Strawberry",#"Pineapple",#"berry",#"mango",#"Apple"]mutableCopy];
NSMutableArray *namesAndWords = [[NSMutableArray alloc]init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ([[n lowercaseString] compare:w] == NSEqualToComparison){
[namesAndWords addObject: w];}}}
NSLog(#"Array without duplicates %d",(int)[namesAndWords count]);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ([n compare:w options:NSCaseInsensitiveSearch] == NSOrderedSame){
[namesAndWords addObject: w];}}}
NSLog(#"Array with duplicates %d",(int)[namesAndWords count]);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ( [n caseInsensitiveCompare:w] == NSOrderedSame ){
[namesAndWords addObject: w];}}}
NSLog(#"Array with duplicates %d",(int)[namesAndWords count]);
In the above code, array 1 has duplicates on itself and array 2 as well. Please try some manual iterations and it is just because of the last two comparison ends up with one-to-many mapping. Last two methods which produces duplicates on your case is just because, you're using for each loop and checking all the values in the array. What will be the result if you remove the duplicates in the array before comparing? Let's have a look at below code.
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:actualarray1];
NSArray *arrayWithoutDuplicates = [orderedSet array];
actualarray1=[arrayWithoutDuplicates mutableCopy];
orderedSet = [NSOrderedSet orderedSetWithArray:actualarray2];
arrayWithoutDuplicates = [orderedSet array];
actualarray2=[arrayWithoutDuplicates mutableCopy];
NSLog(#"%# %#",actualarray1,actualarray2);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *w in actualarray1){
for (NSString *n in actualarray2){
if ( [n caseInsensitiveCompare:w] == NSOrderedSame ){
[namesAndWords addObject: w];}}}
//Your code works like a charm!
NSLog(#"After removing duplicates %d",(int)[namesAndWords count]);
namesAndWords=[[NSMutableArray alloc] init];
for (NSString *s in actualarray1){
if([actualarray2 containsObject:s]){
[namesAndWords addObject: s];
}
}
//This is my code which eventually reduces time
NSLog(#"Count after unique %d",(int)[namesAndWords count]);
I'd suggest you to not to use comparison like [[n lowercaseString] compare:w] == NSEqualToComparison which has incorrect logic. Because, you're converting only one object from the array to lowercase and the logic is incorrect. Because, it gets only lowercase data which is matching in the above code. Instead, you can use [n caseInsensitiveCompare:w] == NSOrderedSame if you need values with unique or remove duplicates before comparing. Also, it is not advisable to use fast iteration in this scenario since performance may be degraded if the array is too large.
Hope it clears your doubt!

When you use:
[[n lowercaseString] compare:w]
You will only get a match if w is lowercase. In your case insensitive comparisons the case of both n and w are ignored. This will account for the different results. HTH

Related

Why does -[NSString compare:options:] return true when the strings are not equal?

I am checking if a string appears twice in a row within an array. This code doesn't seem to work, as it just prints out the entire array. What am I missing?
NSString *nameString =
[NSString stringWithContentsOfFile:#"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
NSArray *names = [nameString componentsSeparatedByString:#"\n"];
//Save last item
NSMutableString *lastOne = [NSMutableString stringWithCapacity:20];
// Go through the array one string at a time
for (NSString *n in names) {
if ([n compare:lastOne options:NSCaseInsensitiveSearch]) {
NSLog(#"%#", n);
}
[lastOne setString:n];
}
compare: and related functions don't return booleans, they return an NSComparisonResult. If you want to see if a string is equal you should instead use
if ([n compare:lastOne options:NSCaseInsensitiveSearch] == NSOrderedSame)

Browsing an object's fields

In Objective C, I have an object e.g. Person with a lot of fields firstName, lastName, phoneNumber, address, city... and so on. These fields types are NSString and any of these could be nil.
Now I want to concatenate my field values in another NSString :
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
for (NSString *field in #[p.firstName, p.lastName, p.phoneNumber,
p.adress, p.city, ....more fields...]) {
if ([field length] > 0) {
[s appendFormat:#"%#\n", field];
}
}
Issue is that this code crash whenever one of the field is nil. I have the exception :
[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object
from objects[0]'
How could I handle simply the case of nil values within my for loop ?
I agree with #TomPace's post, for this small number I would do a simple if/else.
However, there may be times you do need to loop through a list of fields.
It's a bad idea to blindly pull the values into an array as you could be trying inserting nil values into the array. In this case, it would be better to place the field names into a key array as strings and loop through the list using valueForKey: to access the values. I would possibly store the keys list somewhere else where it can be used again.
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
NSArray *keys = #[#"firstName", #"lastName", #"phoneNumber", #"adress", #"city"];
for (NSString *key in keys)
{
NSString *value = [p valueForKey:key];
if ([value length] > 0) {
[s appendFormat:#"%#\n", value];
}
}
Person *person = [[Person alloc] init];
person.firstName = nil;
person.lastName = #"lastName";
NSMutableString *s = [[NSMutableString alloc] init];
[s appendFormat:#"%#\n", person.firstName == nil?#"":person.firstName];
[s appendFormat:#"%#\n", person.lastName == nil?#"":person.lastName];
For a selection of fields this small, don't use a for loop.
You may be saving a bit of code by attempting the for-loop structure, but it's really not the way to go if you're building the NSArray with only a few fields, and especially because you can't put nil items in it.
A better way to go is:
Person *p = ...
NSMutableString *s = [[NSMutableString alloc] init];
if ([p.firstName length] > 0) [s appendFormat:#"%#\n", p.firstName];
if ([p.lastName length] > 0) [s appendFormat:#"%#\n", p.lastName];
if ([p.phoneNumber length] > 0) [s appendFormat:#"%#\n", p.phoneNumber];
if ([p.adress length] > 0) [s appendFormat:#"%#\n", p.adress];
if ([p.city length] > 0) [s appendFormat:#"%#\n", p.city];
Edit, after original Question was updated with large amount of fields.
Like #BergQuester said, an approach to support a larger, arbitrary set of fields is using KVO-style inspection.
NSArray *fieldNames = #[#"firstName", #"lastName", #"phoneNumber", ....more fields...];
NSString *field;
for (NSString *fieldName in fieldNames) {
field = [p valueForKey:fieldName];
if ([field length] > 0 ) {
[s appendFormat: #"%#\n", field];
}
}
Try to create NSMutableString category
#import "NSMutableString+checkForNilObject.h"
#implementation NSMutableString (checkForNilObject)
-(void) appendNotNillObject:(NSString *) string
{
if(string)
{
[self appendString:string];
}
}
#end
You can override the getters of the class Person.
#implementation Person
- (NSString *)firstName{
if (_firseName == nil)
_firstName = #"";
return _firstName;
}
///....Other setters
#end
Like this you can define all your setters here.

NSTableColumn sorts numbers wrongly

This is a NSTableView with IB bindings to a NSArrayController, it displays all values correctly.
However it sorts the numbers only by their first char value e.g. it will put 115.31 below 2.5, and 23.9 below 4.71, etc.
It takes values from a retained NSMutableArray with Strings in it, I also tried by converting all strings to NSNumber/NSDecimalNumber, still no luck:
NSMutableArray *array1 = [[string1 componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]] mutableCopy];
NSMutableArray *array1alt = [NSMutableArray array];
for(NSString *strNum in array1)
{
NSNumber *number = strNum;
[array1alt addObject:number];
}
Please help, thanks.
EDIT: This is how NSMutableArray(s) of my NSTableColumn(s) get filled:
NSMutableArray *rows = [NSMutableArray array];
for (NSUInteger i = 0; i < array1alt.count && i < array2.count && i < array3.count && i < array4.count; i++)
{
NSMutableDictionary *row = [NSMutableDictionary dictionary];
[row setObject:[array1alt objectAtIndex:i] forKey:#"Apples"];
[row setObject:[array2 objectAtIndex:i] forKey:#"Oranges"];
[row setObject:[array3 objectAtIndex:i] forKey:#"Peaches"];
[row setObject:[array4 objectAtIndex:i] forKey:#"Plums"];
[rows addObject:row];
}
[myArrayController2 setContent:rows2];
[aTableView reloadData];
I'm surprised that you aren't getting a compiler warning at:
NSNumber *number = strNum;
You probably want:
NSNumber *number = [NSNumber numberWithDouble:[strNum doubleValue]];
Or, more simply:
NSNumber *number = #([strNum doubleValue]);
If you don't want to deal with number conversions on the output, you could sort your original array of strings like so:
NSArray *array2 = [array1 sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
double num1 = [obj1 doubleValue];
double num2 = [obj2 doubleValue];
if (num1 < num2)
return NSOrderedAscending;
else if (num1 > num2)
return NSOrderedDescending;
else
return NSOrderedSame;
}];
If you want to use decimal numbers, you could probably do something like:
NSMutableArray *array2 = [NSMutableArray array];
for (NSString *strNum in array1)
{
[array2 addObject:[NSDecimalNumber decimalNumberWithString:strNum]];
}
[array2 sortUsingComparator:^NSComparisonResult(NSDecimalNumber *obj1, NSDecimalNumber *obj2) {
return [obj1 compare:obj2];
}];
Without seeing more code this is what i think:
If i understood correctly, you have your NSArrayController as data source.
You need to sort your data before attaching it to your table.
You have NSArrayController methods:
- (void)setSortDescriptors:(NSArray *)sortDescriptors
and
- (NSArray *)arrangeObjects:(NSArray *)objects
With this you will get sorted array to use for your table.
Maybe you will need to call reloadData of your NSTableView.
I cant test these right now because i'm at my laptop right now which doesn't have MacOS :)

Spliting string to array by constant number

I'v been trying to split string to array of components by number, but have no idea how to do it. I know that each components lenght is 9 except the last one. But there is no separation between them. Maybe anyone would know how could i make this split possible?
string : E44000000R33000444V33441
And i'd like to get array with: E44000000 R33000444 V33441
in past I'v used this method, but i guess there should be a way to separate by constant number. Any ideas
NSArray *myWords = [message componentsSeparatedByString:#";"];
Please try the below code.
NSString *stringTest = #"E44000000R33000444V33441323";
NSMutableArray *arrayTest = [NSMutableArray array];
while([stringTest length] > 8) {
[arrayTest addObject:[NSString stringWithString:[stringTest substringToIndex:9]]];
stringTest = [stringTest substringFromIndex:9];
}
NSLog(#"arrayTest - %#", arrayTest);
Try this one..
NSString *mainString=#"E44000000R33000444V";
NSMutableArray *brokenString=[NSMutableArray new];
int start=0;
for (; start<mainString.length-9; start+=9) {
[brokenString addObject:[mainString substringWithRange:NSMakeRange(start, 9)]];
}
[brokenString addObject:[mainString substringFromIndex:start]];
NSLog(#"->%#",brokenString);
Output is :
->(
E44000000,
R33000444,
V
)
I investigated the NSString, and i didn't found any function like that. But you can create a category of NSString and put this function in that category and you can use as a NSString instance method.
- (NSArray *) componentSaparetedByLength:(NSUInteger) length{
NSMutableArray *array = [NSMutableArray new];
NSRange range = NSMakeRange(0, length);
NSString *subString = nil;
while (range.location + range.length <= self.length) {
subString = [self substringWithRange:range];
[array addObject:subString];
//Edit
range.location = range.length + range.location;
//Edit
range.length = length;
}
if(range.location<self.length){
subString = [self substringFromIndex:range.location];
[array addObject:subString];
}
return array;
}
You can get the substring upto the characters which you want in a loop(string length) & pass the next index for getting the next substring. After getting each substring you can add it to the array.
Used SubstringToIndex & SubstringFromIndex functions to get the substring.
Also not an requirement here, I want to propose a solution that is capable of handling characters from more sophisticated script systems, like surrogate pairs, base characters plus combining marks, Hangul jamo, and Indic consonant clusters.
#interface NSString (Split)
-(NSArray *)arrayBySplittingWithMaximumSize:(NSUInteger)size
options:(NSStringEnumerationOptions) option;
#end
#implementation NSString (Split)
-(NSArray *)arrayBySplittingWithMaximumSize:(NSUInteger)size
options:(NSStringEnumerationOptions) option
{
NSMutableArray *letterArray = [NSMutableArray array];
[self enumerateSubstringsInRange:NSMakeRange(0, [self length])
options:(option)
usingBlock:^(NSString *substring,
NSRange substringRange,
NSRange enclosingRange,
BOOL *stop) {
[letterArray addObject:substring];
}];
NSMutableArray *array = [NSMutableArray array];
[letterArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (idx%size == 0) {
[array addObject: [NSMutableString stringWithCapacity:size]];
}
NSMutableString *string = [array objectAtIndex:[array count]-1];
[string appendString:obj];
}];
return array;
}
#end
usage
NSArray *array = [#"E44000000R33000444V33441" arraysBySplittingWithMaximumSize:9
options:NSStringEnumerationByComposedCharacterSequences];
results in:
(
E44000000,
R33000444,
V33441
)

Searching NSArray using suffixes

I have a word list stored in an NSArray, I want to find all the words in it with the ending 'ing'.
Could someone please provide me with some sample/pseudo code.
Use NSPredicate to filter NSArrays.
NSArray *array = #[#"test", #"testing", #"check", #"checking"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH 'ing'"];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
Let's say you have an array defined:
NSArray *wordList = // you have the contents defined properly
Then you can enumerate the array using a block
// This array will hold the results.
NSMutableArray *resultArray = [NSMutableArray new];
// Enumerate the wordlist with a block
[wordlist enumerateObjectsUsingBlock:(id obj, NSUInteger idx, BOOL *stop) {
if ([obj hasSuffix:#"ing"]) {
// Add the word to the result list
[result addObject:obj];
}
}];
// resultArray now has the words ending in "ing"
(I am using ARC in this code block)
I am giving an example using blocks because its gives you more options should you need them, and it's a more modern approach to enumerating collections. You could also do this with a concurrent enumeration and get some performance benefits as well.
Just loop through it and check the suffixes like that:
for (NSString *myString in myArray) {
if ([myString hasSuffix:#"ing"]){
// do something with myString which ends with "ing"
}
}
NSMutableArray *results = [[NSMutableArray alloc] init];
// assuming your array of words is called array:
for (int i = 0; i < [array count]; i++)
{
NSString *word = [array objectAtIndex: i];
if ([word hasSuffix: #"ing"])
[results addObject: word];
}
// do some processing
[results release]; // if you're not using ARC yet.
Typed from scratch, should work :)