I want to sort an NSArray of NSStrings by similarity to a single given string, as opposed to relative sort order for items within the array.
Or to put it a different way, I want to sort the array by comparing all the items contained in the array by similarity to a single item, not sort them by comparing each other.
I have the logic for similarity figured out, it's just how I use the NSArray and/or NSSortDescriptor APIs to achieve this.
sortedArrayUsingComparator should allow you to achieve this ...
NSArray* sourceArray = //... your data
NSString* itemToCompareTo = sourceArray[5]; // locate the item you want to compare to
NSArray* sortedArray = [listItem sortedArrayUsingComparator: ^(id a, id b) {
// compare a, b, and itemToCompareTo here, using your sort logic
}
Related
This question already has answers here:
Sort NSArray of custom objects based on sorting of another NSArray of strings
(5 answers)
Closed 5 years ago.
How to sort an NSArray based on a list of values ? For example:
An object of the NSArray has a property :
NSString* language; //- may contain value #"FR", #"NL", #"SP", #"EN", and others languages
How can we sort based on the list of values like:
NSArray* sortingByList = #[#"FR", #"NL", #"SP", #"EN"];
The expected results should look like this:
- FR
- FR
- NL
- NL
- NL
- SP
- SP
- EN
- EN
- EN
- other languages
...
- (NSArray*) sortArray:(NSArray *)language withOrder:(NSArray *)sortingByList
{
return [language sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSUInteger pos1 = [sortingByList indexOfObject:obj1];
NSUInteger pos2 = [sortingByList indexOfObject:obj2];
if (pos1 < pos2)
return NSOrderedAscending;
else if (pos1 == pos2)
return NSOrderedSame;
else
return NSOrderedDescending;
}];
}
The best answer to the question "how do I sort based on some particular interesting order" is invariably to use one of the methods which takes a block. Some sorts can be achieved using NSPredicate-based sorts, but block-based are more flexible and often easier.
The block these methods take will be passed two elements and must return a value based on the ordering of those two elements. For example using NSArray's sortedArrayUsingComparator:
NSArray *sorted
= [someArray
sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull x, id _Nonnull y)
{
// return NSOrderedAscending,
// NSOrderedSame or
// NSOrderedDescending based
// on the order of x and y
}
];
In your case you have an array, sortingByList, which gives the desired ordering of the elements. In outline an algorithm is:
Get languageX and languageY from x and y respectively
Get the indexes (using a standard NSArray method), indexX and indexY, of languageX and languageY in sortingByList. If either don't occur in the array set the index to one greater than the maximum index.
Compare indexX and indexY and return their ordering as the result.
If step (3) ranks x and y as equal you might want to do a secondary sort based on some other property of the objects. A benefit of using block-based sorting is you can easily sort based on any number of keys.
If you get stuck implementing a block that orders your objects correctly ask a new question, showing your code, and explaining where you got stuck. Someone will undoubtedly help you again at the point.
HTH
Use below code to sort array which contains string elements.
unsortedStrings is NSArray which contains unsorted string elements.
NSArray *sortedStrings =
[unsortedStrings sortedArrayUsingSelector:#selector(compare:)];
NSArray *array = #[#"FR", #"NL", #"SP",#"NL", #"EN",#"FR"];
NSArray *sortedArray = [array sortedArrayUsingSelector:
#selector(localizedCaseInsensitiveCompare:)];
NSLog(#"sortedArray is --%#",sortedArray);
Out put is
SortedArray is --(
EN,
FR,
FR,
NL,
NL,
SP
)
As part of an autocomplete box, I am searching names within an array of contacts. However, after the user picks a name from the suggested List, I need to grab the id of the contact which is in the array of contact objects but not the array of names that appear in the suggest box. I've been working with just the names as that's what I want to display in the suggestion box but have an array of contacts as well.
How would I convert code below (probably using key values) to search the name dimensions of an array of objects instead of an array of names so as to keep track of the ids of the objects. I am kind of fuzzy on arrays and key values.
//the array being searched looks something like #[#"John", #"Dave", #"Sam", #"Xian", #"Ahmed", #"Johann"];
//I want to search the names in an array that looks something like:
(
{
first = "John";cid = 2;},
{
first = "Dave";cid = 44;},
{
first = "Xian";cid=99})
//this code works great to search names but I lose track ids. Thank you for any suggestions.
-(void)searchArray: (NSMutableArray*) array forString: (NSString *) term {
[_contactsSuggested removeAllObjects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#",term];
NSArray *tempArray = [array filteredArrayUsingPredicate:predicate];
_contactsSuggested = [NSMutableArray arrayWithArray:tempArray];
[_autocompleteTableView reloadData];
}
Create a Contact object. Give it a name property, an id property, and any other properties you need. Then write code that searches an array of Contact objects rather than just an array of names. You could then create a predicate using predicateWithBlock to filter the items that match your name property.
Sorry guys, this problem I am running into is pretty trivial. I just can't wrap my head around it so hope someone can help me. Your help is really appreciated. I am getting JSON data through NSURLConnectDelegate with a web API. I get something like this back:
(
{
id = 340
name = Vicent },
{
id = 339
name = Johny },
{
id = 338
name = Eric }
)
and I save it in a NSMutableArray as a global variable. Now, I have a NSSet of "ids". For example:
{
340, 339
}
In the numberOfRowsInSection, I return the set's count. I am trying to load only the ids in the NSSet from the array with the data saved from the webAPI, so I do something like this in cellForRowIndexPath:
for (NSNumber *num in [set allObjects]) {
NSString *newString = [[savedArray objectAtIndex:indexPath.row]
NSString *new = [num stringValue];
if ([new isEqual:newString]) {
}}
How can I just populate the ids I want?
The JSON makes it look like you have an array of dictionaries, which is a reasonable data structure to use as the data source for a table view.
It sounds like you're trying to filter your array to only include the items that are in your set. Is that right?
If so, you could write code that would create a new array containing the subset of your array elements who's ID is also in your set. There are at least a half-dozen ways to do that. One fairly simple approach would be to use the NSArray method indexesOfObjectsPassingTest. You'd pass that method a block of code that would check each array element to see if it's id object was in your set.
That would give you an NSIndexSet with the indexes of the items in your array who's ID are in your set. Then you could use the NSArray method objectsAtIndexes to get an array of only the objects that are also in the set. Something like this (Assuming that your array of dictionaries is called savedArray and your set is called allObjects:
//get the indexes of items in the array savedArray who's id appears in the set allObjects
NSIndexSet *indexes = [savedArray indexesOfObjectsPassingTest:
^(NSDictionary *obj,
NSUInteger idx,
BOOL *stop)
{
return [allObjects member: obj[#"id"]] != nil;
}
];
//Now build an (immutable) array of just the objects who's ID are in the set
NSArray *subArray = [savedArray objectsAtIndexes: indexes];
The array subArray created above is immutable. If you need a mutable array you would need to make a mutable copy, which is a one-line change.
Disclaimer: I still struggle a little with block syntax, so the above might not be exactly correct, but it gives you the general idea.
I have an array and it has lots of dictionary's keys it comes from API. My array as follows
Dictionary keys array :
NSArray *arr = #[#"01", #"02", #"03"];
Dictionary with key-value pairs
NSDictionary *dic = #{#"01": #"Hero", #"02" : #"Enemy", #"03" : #"Boss"};
Basically i want to match array values corresponding to dictonary keys without using array. I found a solition about that but I don't want to use for-loop for every cell(I have a lots of cell). My solution is like that
for(NSString *item in arr) {
[convertedArr addObject:[dic valueForKey:item]];
}
NSLog(#"%#", [convertedArr componentsJoinedByString:#","]);
Asumme have an array like this (1,2,3) and dictionary looks like {1 = "a", 2 = "b", 3 = "c"} I just want to give an array and it should return dictionary values like this ("a","b","c")
Anybody should give me better approach without using array? Thanks.
You can replace your for-loop by
NSArray *convertedArr = [dic objectsForKeys:arr notFoundMarker:#""];
which is at least less code. (The notFoundMarker: is added for all keys
in the array which are not present in the dictionary. Your code would crash
in that situation.)
It might perform slightly better because it is a library
function. But I doubt that the difference is big, because in any case a dictionary
loopup is required for all keys in arr.
I need to scroll through several thousands of words to categorize them... to determine which words have the same pattern. (this part works)
For example, a four letter word that has two m's in 2nd & 4th position represent a pattern ("-m-m"). Once I have gone through all the words, I will know how many words there are for any given pattern. I am scrolling through now, but the problem I have is 'remembering' how many words I have in any given pattern.
I was thinking of using NSMutableDictionary and have the key be the pattern ('-m-m-') and the object represent the count of that pattern. This means every time I come across a pattern, I look up that pattern in the dictionary, get the key, increment the key, and put it back in the dictionary.
I need help with both the decision and syntax for performing this task.
Thank You
The answer to your question was this part of your (given) question "I will know how many words there are for any given pattern.". I would use an array of dictionary. You use the dictionary to store key value pair: a known pattern and the count. And you use the array to store those KVP records. So the next time you detect a pattern, search for the array for that record (dictionary), if found, increment the count. If not, create new record and set the count to 1.
Added sample code:
#define kPattern #"Pattern"
#define kPatternCount #"PatternCount"
-(NSMutableDictionary *)createANewDictionaryRecord:(NSString *) newPattern
{
int count = 1;
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
newPattern, kPattern,
[NSString stringWithFormat:#"%i",count], kPatternCount,
nil];
return myDictionary;
}
-(void)addANewPatternToArray:(NSMutableDictionary *)newDictionary
{
// NSMutableArray *myArrayOfDictionary = [[NSMutableArray alloc]init]; // you need to define it somewhere else and use property etc.
[self.myArrayOfDictionary addObject:newDictionary]; //or [self.myArrayOfDictionary addObject:newDictionary]; if you follow the recommendation above.
}
-(BOOL)existingPatternLookup:(NSString *)pattern
{
for (NSMutableDictionary *obj in self.myArrayOfDictionary)
{
if ([[obj objectForKey:kPattern] isEqual:pattern])
{
int count = [[obj objectForKey:kPatternCount] intValue] + 1;
[obj setValue:[NSString stringWithFormat:#"%i",count] forKey:kPatternCount];
return YES;
}
}
[self.myArrayOfDictionary addObject:[self createANewDictionaryRecord:pattern]];
return NO;
}
-(void)testData
{
NSMutableDictionary *newDict = [self createANewDictionaryRecord:#"mmm"];
[self addANewPatternToArray:newDict];
}
-(void) printArray
{
for (NSMutableDictionary * obj in self.myArrayOfDictionary)
{
NSLog(#"mydictionary: %#", obj);
}
}
- (IBAction)buttonPressed:(id)sender
{
if ([self existingPatternLookup:#"abc"])
{
[self printArray];
} else
{
[self printArray];
}
}
Not being an objective C expert but solving this problem in java before, I would say a dictionary(I used a map when doing it in java) is the best way. Check if the key(pattern) already exist if so increment that count else put a new one in the dictionary.
EDIT
If you want to not just get the count of a pattern, but in fact tell which words fall under that pattern, I would use a dictionary of strings to mutable arrays. In the arrays you store the words and the key to the array is the pattern(as a string), similar code as above but instead of just incrementing the count, you have to add the new word to the array.
The only difference in NSDictionary and NSMutableDictionary is that one can have objects added to it. I think your implementation is good, but English is a complex language. It would be more efficient to parse out the string with regex than to set a key for it.
Why don't you use NSCountedSet instead:
NSCountedSet Class Reference
..which is available in iOS 2.0 and later?
Each distinct object inserted into an NSCountedSet object has a counter associated with it. NSCountedSetkeeps track of the number of times objects are inserted [...] Thus, there is only one instance of an object in an NSSet object even if the object has been added to the set multiple times. The count method defined by the superclass NSSet has special significance; it returns the number of distinct objects, not the total number of times objects are represented in the set.
Then use:
- (NSUInteger)countForObject:(id)anObject
Use a dictionary of NSMutableArrays, and check for the existence of each search key as you recommended. If the key doesn't exist, add an NSMutableSet or NSMutableArray (depending on your needs) for the searched key type "-m-m" for example, and then add to the set or array for that key.