Sort ignoring punctuation (Objective-C) - objective-c

I am trying to sort an iOS UITableView object. I am currently using the following code:
// Sort terms alphabetically, ignoring case
[self.termsList sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
This sorts my list, whist ignoring case. However, it would be nice to ignore punctuation as well. For example:
c.a.t.
car
cat
should be sorted as follows:
car
c.a.t.
cat
(It doesn't actually matter which of the two cats (cat or c.a.t.) comes first, so long as they're sorted next to one another).
Is there a simple method to get around this? I presume the solution would involve extracting JUST the alphanumeric characters from the strings, then comparing those, then returning them back to their former states with the non-alphanumeric characters included again.
In point of fact, the only characters I truly care about are periods (.) but if there is a solution that covers all punctuation easily then it'd be useful to know.
Note: I asked this exact same question of Java a month ago. Now, I am creating the same solution in Objective-C. I wonder if there are any tricks available for the iOS API that make this easy...
Edit: I have tried using the following code to strip punctuation and populate another array which I sort (suggested by #tiguero). However, I don't know how to do the last step: to actually sort the first array according to the order of the second. Here is my code:
NSMutableArray *arrayWithoutPunctuation = [[NSMutableArray alloc] init];
for (NSString *item in arrayWithPunctuation)
{
// Replace hyphens/periods with spaces
item = [item stringByReplacingOccurrencesOfString:#"-" withString:#" "]; // ...hyphens
item = [item stringByReplacingOccurrencesOfString:#"." withString:#" "]; // ...periods
[arrayWithoutPunctuation addObject:item];
}
[arrayWithoutPunctuation sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
This provides 'arrayWithoutPunctuation' which is sorted, but of course doesn't contain the punctuation. This is no good, since, although it is now sorted nicely, it no longer contains punctuation which is crucial to the array in the first place. What I need to do is sort 'arrayWithPunctuation' according to the order of 'arrayWithoutPunctuation'... Any help appreciated.

You can use a comparison block on an NSArray and your code will look like the following:
NSArray* yourStringList = [NSArray arrayWithObjects:#"c.a.t.", #"car", #"cat", nil];
NSArray* yourStringSorted = [yourStringList sortedArrayUsingComparator:^(id a, id b){
NSString* as = (NSString*)a;
NSString* bs = (NSString*)b;
NSCharacterSet *unwantedChars = [NSCharacterSet characterSetWithCharactersInString:#"\\.:',"];
//Remove unwanted chars
as = [[as componentsSeparatedByCharactersInSet: unwantedChars] componentsJoinedByString: #""];
bs = [[as componentsSeparatedByCharactersInSet: unwantedChars] componentsJoinedByString: #""];
// make the case insensitive comparison btw your two strings
return [as caseInsensitiveCompare: bs];
}];
This might not be the most efficient code actually one other option would be to iterate on your array first and remove all unwanted chars and use a selector with the caseInsensitiveCompare method:
NSString* yourStringSorted = [yourStringList sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];

This is a bit cleaner, and a bit more efficient:
NSArray* strings = #[#".....c",#"a.",#"a",#"b",#"b...",#"a..,"];
NSArray* sorted_strings = [strings sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString* a = [obj1 stringByTrimmingCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
NSString* b = [obj2 stringByTrimmingCharactersInSet:[NSCharacterSet punctuationCharacterSet]];
return [a caseInsensitiveCompare:b];
}];
For real efficiency, I'd write a compare method that ignores punctuation, so that no memory allocations would be needed just to compare.

My solution would be to group each string into a custom object with two properties
the original string
the string without punctuation
...and then sort the objects based on the string without punctuation.
Objective C has some handy ways to do that.
So let's say we have two strings in this object:
NSString *myString;
NSString *modified;
First, add your custom objects to an array
NSMutableArray *myStrings = [[NSMutableArray alloc] init];
[myStrings addObject: ...];
Then, sort the array by the modified variable using the handy NSSortDescriptor.
//You can specify the variable name to sort by
//Sorting is done according to the locale using localizedStandardCompare
NSSortDescriptor *mySortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"modified" ascending:YES selector:#selector(localizedStandardCompare:)];
[myStrings sortedArrayUsingDescriptors:#[ mySortDescriptor ]];
Voila! Your objects (and strings) are sorted. For more info on NSSortDescriptor...

Related

Find string in array

I have a fun challenging problem. So I have a mutable array that contains all of my items. I have a textfield that **might have one or two of these items if the person types them in. **
items= [[NSArray alloc]initWithObjects:#"apple", #"orange", #"pear", nil];
items2= [[NSArray alloc]initWithObjects:#"cheese", #"milk", #"eggs", nil];
Allitems= [NSMutableArray array];
[Allitems addObjectsFromArray:items];
[Allitems addObjectsFromArray:items2];
NSArray*WORDS =[Textfield componentsSeparatedByString:#" "];
I am trying to detect what specific words from **Allitems are in the textfield. (If the textfield contains any string from ALLitems, how can I find what specific string?**
for (int i = 0; i < [Allitems count]; i++)
{
NSString *grabstring;
grabstring=[Allitems objectAtIndex:i];
if (textfield isEqualto:grabstring){
?????
pull that specific string from allitems.
}
}
You want the intersection of two sets:
NSMutableSet* intersectionSet = [NSMutableSet setWithArray:Allitems];
[intersectionSet intersectSet:[NSSet setWithArray:WORDS]];
NSArray* intersectionArray = [intersectionSet allObjects];
After this intersectionArray contains the items that are present in both Allitems and WORDS.
BTW, why do you capitalise variable names in a non-standard and inconsistent manner? Why not just allItems and words?
As #Arkku suggests: It's better to switch the arrays. In your example it does not matter much, but in case Allitems were (very) big, you can save (a lot of) memory and CPU usage:
NSMutableSet* intersectionSet = [NSMutableSet setWithArray:WORDS];
[intersectionSet intersectSet:[NSSet setWithArray:Allitems]];
NSArray* intersectionArray = [intersectionSet allObjects];
There are a various ways of doing it, each with different pros and cons. Let's have the following (consistently capitalized) variables in common for each case:
NSArray *allItems = #[ #"apple", #"orange", #"pear", #"cheese", #"milk", #"egg" ];
NSString *textFieldText = #"CHEESE ham pear";
NSArray *words = [textFieldText.lowercaseString componentsSeparatedByString:#" "];
NSPredicate
NSArray *matchingItems = [allItems filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"SELF IN %#", words]];
This is perhaps the shortest (in lines of code) way, but not the most performant if allItems can be very long as it requires traversing all of it.
Iteration
Of course you could also simply iterate over the collection and do the matching manually:
NSMutableArray *matchingItems = [NSMutableArray array];
for (NSString *item in allItems) {
if ([words containsObject:item]) {
[matchingItems addObject:item];
}
}
Again requires traversing all of allItems (although you could break the iteration if all words are matched).
In addition to the for loop there are of course many other ways for iteration, e.g., enumerateObjectsUsingBlock:, but they are unlikely to have any advantage here.
NSSet
NSSet is often a good option for this kind of matching since testing set membership is faster than with NSArray. However, if using the most straightforward method intersetSet: (in NSMutableSet) care must be taken to not inadvertently create a large mutable set only to discard most of its items.
If the order of allItems does not matter, the best way would be to change it from an array into a set and always keep that set around, i.e., instead of creating the array allItems, you would create an NSSet:
NSSet *setOfAllItems = [NSSet setWithArray:allItems];
Or if it needs to be mutable:
NSMutableSet *setOfAllItems = [NSMutableSet set];
[setOfAllItems addObjectsFromArray:items1];
[setOfAllItems addObjectsFromArray:items2];
Then, when you have that set, you create a temporary mutable set out of words (which is presumably always the smaller set):
NSMutableSet *setOfMatches = [NSMutableSet setWithArray:words];
[setOfMatches intersectSet:setOfAllItems];
NSArray *matchingItems = setOfMatches.allObjects;
This would be likely be the most performant solution if setOfAllItems is large, but note that the matches will then need to be exact. The other methods are more easily adapted to things like matching the strings in words against fields of objects or keys in a dictionary (and returning the matched objects rather than the strings). In such a case one possibility to consider would be an NSDictionary mapping the words to match to the objects to return (also fast to then iterate over words and test for membership in the dictionary).
Conversion to string
And, since the question included conversion of matches to a string:
[matchingItems componentsJoinedByString:#", "]
In the example case this would result in the string "pear, cheese" (or possibly "cheese, pear" if using sets).

Get list of Values for an NSArray of NSDictionary

I've got the following NSArray :
NSArray myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
I want the list of value of my dictionaries :
#[#"5 min",#"15 min",#"30 min",#"1 hour"]
And the list of key of my dictionaries :
#[#300, #900, #1800, #3600]
What is the best way to do that ? I was thinking about predicate, but I don't know how to use it ?
Without some code to show how you'd want to go about this it is difficult to be sure exactly what you are after, and there is a bit of confusion in the question.
First, a predicate is exactly that - a statement that can be proven true or false. Predicates are hence used in logic expressions, including those employed implicitly in database queries - such as Core Data.
That is not what you want, if I read your question correctly. What you want is to reduce the complexity of your data model, removing some excess (one would hope) information in the process. A sort of flattening of an array of dictionaries.
Fair enough.
I can also see how the confusion with predicates came about - they are most often constructed using Key-Value Coding. KVC, as it is also known, is a very powerful technique that can accomplish what you are after. It just does not have much to do with a logic statement.
Having cleared that up, with KVC you can do what you want, and with minimal fuss. It goes like this:
NSArray *values = [myArray valueForKeyPath: #"#unionOfArrays.#allValues"];
NSArray *keys = [myArray valueForKeyPath: #"#unionOfArrays.#allKeys"];
A brief explanation might be in order:
The results that we want are
All the values (or keys) of each dictionary, obtaining an array of arrays of values (or keys)
Then we want to flatten these arrays into a single array.
To obtain all values (or keys) from a dictionary using KVC, the special key is #allValues or #allKeys, respectively.
The #unionOfArrays operator makes a union of the arrays obtained from the expression that follows it, i.e., flattens it into the array you wanted.
The price you pay for this coding simplicity is that you have to use KVC key paths with collection operators, which are just strings in your code. You therefore lose any help from the compiler with syntax and it doesn't check that the keys you enter exist in the objects. Similarly, the debugger and error messages are unhelpful if you mistype or use the wrong operator, for instance.
You can use dictionary property allValues to get all values of dictionary.
Try this code in your case
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *arr = [NSMutableArray array];
for (NSDictionary *dict in myArray) {
[arr addObject:[[dict allValues] objectAtIndex:0]];
}
NSLog(#"%#",arr);
Note : Make sure you have only one value in each dictionary.
it will return
[
5 min,
15 min,
30 min,
1 hour
]
#johnyu's answers is technically correct, but I don't see any reason to include the secondary loop, especially if the data structure will remain the same.
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *arrayOfValues = [NSMutableArray new];
NSMutableArray *arrayOfKeys = [NSMutableArray new];
for (NSDictionary *dictionary in myArray) {
[arrayOfValues addObject:dictionary.allValues[0]];
[arrayOfKeys addObject:dictionary.allKeys[0]];
}
NSLog(#"%#",arrayOfKeys);
NSLog(#"%#",arrayOfValues);
Try this:
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
NSMutableArray *valueArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in myArray) {
for (NSString *key in dictionary) {
[keyArray addObject:key];
[valueArray addObject:[dictionary objectForKey:key]];
}
}

Parsing text from one array into another array in Objective C

I created an array called NSArray citiesList from a text file separating each object by the "," at the end of the line. Here is what the raw data looks like from the text file.
City:San Jose|County:Santa Clara|Pop:945942,
City:San Francisco|County:San Francisco|Pop:805235,
City:Oakland|County:Alameda|Pop:390724,
City:Fremont|County:Alameda|Pop:214089,
City:Santa Rosa|County:Sonoma|Pop:167815,
The citiesList array is fine (I can see count the objects, see the data, etc.) Now I want to parse out the city and Pop: in each of the array objects. I assume that you create a for loop to run through the objects, so if I wanted to create a mutable array called cityNames to populate just the city names into this array I would use this kind of for loop:
SMutableArray *cityNames = [NSMutableArray array];
for (NSString *i in citiesList) {
[cityNames addObject:[???]];
}
My question is what is what query should I use to find just the City: San Francisco from the objects in my array?
You can continue to use componentsSeparatedByString to divide up the sections and key/value pairs. Or you can use an NSScanner to read through the string parsing out the key/value pairs. You could use rangeOfString to find the "|" and then extract a range. So many options.
Many good suggestions in the answers here in case you really want to construct an algorithm to parse the string.
As an alternative to that, you can also look at it as a problem of declaring the structure of the data and then just have the system do the parsing. For a case like yours, regular expressions will do that nicely. Whether you prefer to do it one way or the other is largely a question of taste and coding standards.
In your specific case (if the city name is all you need to extract from the string), then also notice that there is a bit of a shortcut available that will turn it into a one-line solution: Match the whole string, define a single capture group and substitute that one to make a new string:
NSString *city = [i stringByReplacingOccurrencesOfString: #".*City:(.*?)\\|.*"
withString: #"$1"
options: NSRegularExpressionSearch
range: NSMakeRange(0, row.length)];
The variable i is the same that you have defined in your for-loop, i.e. a string containing a string representing a line in your input file:
City:San Jose|County:Santa Clara|Pop:945942,
I have added the initial .* to make the pattern robust to future new fields added to the rows. You can remove it if you don't like it.
The $1 in the substitution string represents the first capture group, i.e. the parenthesis in the regex pattern. In this specific case, the substring containing the city name. Had there been more capture groups, they would have been named $2-$9. You can check the documentation on NSRegularExpression and NSString if you want to know more.
Regular expressions are a topic all of their own, not confined to the Cocoa, although all platforms use regex implementations with their own idiosyncrasies.
You want to use componentsSeparatedByString: as below. (These lines do no error checking)
NSArray *fields = [i componentsSeparatedByString:#"|"];
NSString *city = [[[fields objectAtIndex:0] componentsSeparatedByString:#":"] objectAtIndex:1];
NSString *county = [[[fields objectAtIndex:1] componentsSeparatedByString:#":"] objectAtIndex:1];
If you can drop the keys, and a couple delimiters like this:
San Jose|Santa Clara|945942
San Francisco|San Francisco|805235
Oakland|Alameda|390724
Fremont|Alameda|214089
Santa Rosa|Sonoma|167815
Then you can simplify the code (still no error checking):
NSArray *fields = [i componentsSeparatedByString:#"|"];
NSString *city = [fields objectAtIndex:0];
NSString *county = [fields objectAtIndex:1];
for (NSString *i in citiesList) {
// Divide each city into an array, where object 0 is the name, 1 is county, 2 is pop
NSArray *stringComponents = [i componentsSeparatedByString:#"|"];
// Remove "City:" from string and add the city name to the array
NSString *cityName = [[stringComponents objectAtIndex:0] stringByReplacingCharactersInRange:NSMakeRange(0, 5) withString:#""];
[cityNames addObject:cityName];
}

Having trouble taking an index of an array and making it an NSString

I get an array from a JSON and I parse it into an NSMutableArray (this part is correct and working). I now want to take that array and print the first object to a Label. Here is my code:
NSDictionary *title = [[dictionary objectForKey:#"title"] objectAtIndex:2];
arrayLabel = [title objectForKey:#"label"];
NSLog(#"arrayLabel = %#", arrayLabel); // Returns correct
//Here is where I need help
string = [arrayLabel objectAtIndex:1]; //I do not get the first label (App crashes)
NSLog(#"string = %#", string);
other things that I have already tried are as follows:
string = [NSString stringWithFormat:#"%#", [arrayImage objectAtIndex:1]];
and
string = [[NSString alloc] initWithFormat:#"%#", [arrayImage objectAtIndex:1]];
Any help is greatly appriciated!
EDIT: The app does not return a single value and crashes.
Your code doesn't match the structure of your JSON. In your comment on the deleted answer, you said you got an exception when sending objectAtIndex: to an NSString. In your case, arrayLabel isn't an array when you think it is.
If your JSON has an object, your code needs to treat it as an NSDictionary. Likewise for arrays and NSArray and strings and NSString.
In addition to whatever else was going on, you repeatedly refer to "first" but use the index 1. In most C-based programming languages (and others, as well) the convention is that indexes into arrays are 0-based. So, use index 0 to get the first element.

optimising a nested for loop

So, I have the following loop, and it's a bit of a bottle neck - is there any way I can speed this up?
NSArray *array = [an array of NSDictionaries];
NSArray *otherArray = [an array of NSStrings];
NSMutableArray *newArray = [NSMutableArray new] autorelease];
for (NSDictionary *dict in array)
{
NSString *name = [[NSString alloc] initWithString:[dict objectForKey#"name"]];
for (NSString *n in otherArray)
{
if ([name hasPrefix:n])
[newArray addObject:dict];
}
[name release];
}
You can define a NSPredicate and use
- (NSArray *)filteredArrayUsingPredicate:(NSPredicate *)predicate
on your array and don't loop on your own. You need to profile if it is actually faster.
About all I can see for substantial gains is that you could make up a boolean C-style array indexed by the first letter of your prefix and pre-load it with YES/NO based on whether that character is a "hit". (Probably you'd want a 256-element array indexed by the low byte of the 2-byte character.) Inside the outer loop take the first character of name, index this array, and if it's NO then skip the rest of the outer loop body. Only works if the prefix array is fairly small, though (so less than about half of the boolean array elements are YES).
You can probably make a small improvement by using a C-style array rather than the prefix NSArray, but at the expense of creating that C-style array up front.
There are other techniques that would involve hashing, but the setup expense and complexity is probably not worth it.
You can use a predicate to filter the array & remove the loop entirely.
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(%#, $str, name BEGINSWITH[cd] $str).#count != 0", otherArray];
NSArray * newArray = [array filteredArrayUsingPredicate:predicate];
Assuming name is the key.