I am trying to customise the combination method found in the solution given here: Combinations of different NSArray objects so that it utilises recursion, instead of the solution given, which is using iteration. The reason why I am trying to customise this function is to increase the performance of this function as it starts slowing down significantly when the arrays start getting larger and there are many combinations to compute.
Any advice on how this could be done?
I think you'd have to combine iteration and recursion. If you want it to do a varying number of arrays then I came up with something like this...
First pass at an iterative method.
- (NSArray *)combineArrays:(NSArray *)arrays
{
// arrays is an array of arrays of elements that you want to combine.
NSMutableArray *combinations = [NSMutableArray array];
for(NSArray *array in arrays) {
NSMutableArray *temp = [NSMutableArray array];
for (id element in array) {
if (combinations.count == 0) {
// the first level of the array is just all the elements from the first array
[temp addObject:#[element]];
} else {
// copy each array in combinations for each new element and add new element to end
for (NSArray *array in combinations) {
[temp addObject:[array arrayByAddingObject:element];
}
}
}
// save the current combinations
combinations = temp;
}
return combinations;
}
I'm almost certain that the middle part can be recursed into a single method. Just working on that now.
- (NSArray *)combineArray:(NSArray *)combinations withArrays:(NSMutableArray *)arrays
{
// if there are no arrays to combine then you are at the end of recursion so return combinations.
if (!array || array.count == 0) {
return combinations;
}
// to start with you "combine" the first array with an empty array
if (!combinations) {
combinations = #[#[]];
}
NSMutableArray *newCombinations = [NSMutableArray array];
NSArray *array = [arrays firstObject];
for (id element in array) {
for (NSArray combination in combinations) {
[newCombinations addObject:[combination arrayByAddingObject:element];
}
}
[arrays removeObjectAtIndex:0];
return [self combineArray:newCombinations withArrays:arrays];
}
Something like this might work. It's no faster than iteration though.
Take a look at this answer JavaScript - Generating combinations from n arrays with m elements which does what you want but in JavaScript. You still have to touch each element of each array once though.
Related
Without unintentionally killing performance, does this appear at first glance to be acceptable for perhaps 200 guid strings in one list compared for equality with 100 guid strings from another list to find the matching indexes.
I have a method signature defined like so...
-(NSArray*)getItemsWithGuids:(NSArray*)guids
And I wanted to take that passed in array of guids and use it in conjunction with this array...
NSArray *allPossibleItems; // Has objects with a property named guid.
... to obtain the indexes of the items in allPossibleItems which have the matching guids from guids
My first instinct was to try indexesOfObjectsPassingTest but after putting together the block, I wondered whether the iOS framework already offers something for doing this type of compare more efficiently.
-(NSArray*)getItemsWithGuids:(NSArray*)guids
{
NSIndexSet *guidIndexes = [allPossibleItems indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
SomeObjWithGuidProperty *someObject = obj;
for (NSString *guid in guids) {
if ([someObject.guid isEqualToString:guid]) {
return YES;
}
}
return NO;
}];
if (guidIndexes) {
// Have more fun here.
}
}
Since you're working with Objective-C (not Swift) check out YoloKit. In your case, you can do something like:
guids.find(^(NSString *guid){
return [someObject.guid isEqualToString:guid];
});
My thought would be to use a set -
-(NSArray*)getItemsWithGuids:(NSArray*)guids inAllObjects:(NSArray *)allObjects
{
NSSet *matchGuids=[NSSet setWithArray:guids];
NSMutableArray *matchingObjects=[NSMutableArray new];
for (SOmeObjectWithGuidProperty *someObject in allObjects) {
if ([matchGuids contains:someObject.guid]) {
[matchingObjects addObject:someObject];
}
}
return [matchingObjects copy];
}
Your code looks like it would have O(n^2) performance, which is bad. I think the solution of converting guids to an NSSet and then using NSSet's containsObject would likely be much more performant. You could rewrite your indexesOfObjectsPassingTest code to use an NSSet and containsObject pretty easily.
If order doesn't matter much, I would suggest to change data structure here. Instead of using NSArray, consider to use NSDictionary with guid as key and someObject as value. In this case, you should use -[NSDictionary objectsForKeys:notFoundMarker:] method to obtain objects.
It will work much faster, than enumeration trough 2 arrays. If the NSDictionary key have a good hash function, accessing an element, setting an element, and removing an element all take constant time. NSString has good hash.
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSArray *objectsAndNulls = [allPossibleItemsDictionary objectsForKeys:guids notFoundMarker:[NSNull null]];
if (objectsAndNulls) {
// Have more fun here.
// You should check that object in objectsAndNulls is not NSNull before using it
}
return objectsAndNulls;
}
UPD Unfortunately, there is no way to pass nil as notFoundMarker. If you can't provide usable notFoundMarker value and don't want to perform additional checks, you can query objects one by one and fill NSMutableArray. In this case you will avoid pass trough array to remove NSNulls:
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:guids.count];
for (NSString *guid in guids) {
SomeObjWithGuidProperty *object = allPossibleItemsDictionary[guid];
if (nil != object) {
[objects addObject:object];
}
}
if (nil != objects) {
// Have more fun here.
}
return object;
}
I have an object containing an array of NSNumbers (indexes) and an array of NSDictionaries (indexesTitles) corresponding to indexes, containing some info.
I have to call a method for each object.index and associate object.indexTitles to the returning results, saving them into a single array.
At the end of it, I want to remove indexes duplicates, preserving the associated indextTitles in an efficient way, because I'm working with large arrays.
NSMutableArray *resultArray = [NSMutableArray array];
NSMutableArray *titlesArray = [NSMutableArray array];
for(NSNumber *index in object.indexes)
{
NSArray *resultsIndexArray = [self methodThatReturnsAnArray];
NSString *indexTitleDictionary = [object.indexesTitles objectAtIndex:i];
for(NSNumber *resultId in resultsIndexArray)
{
[titlesArray addObject:indexDictionary];
[resultArray addObject:resultId];
}
i++;
}
[fullResultsArray addObject:titlesArray];
[fullResultsArray addObject:resultArray];
I've found that the most efficient way to remove duplicates is using an
NSOrderedSet like this:
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:resultArray];
resultArray = [orderedSet.array mutableCopy];
How can I remove the corresponding entries in titlesArray? how can I preserve the association?
I've also tried to use a NSDictionary like {resultId, titleDictionary} and storing them into an array, but I haven't found a efficient way to remove dictionaries with the same result, they are all too slow.
Any suggestion?
It is not completely clear to me what your problem is, maybe this will help:
A good way to remove duplicates is not to add them in the first place, replace:
for(NSNumber *resultId in resultsIndexArray)
{
[titlesArray addObject:indexDictionary];
[resultArray addObject:resultId];
}
with:
for(NSNumber *resultId in resultsIndexArray)
{
// only add if resultId not already in resultArray
if( ![resultArray containsObject:resultId] )
{
[titlesArray addObject:indexDictionary];
[resultArray addObject:resultId];
}
}
The containsObject: call requires a linear search, if your data set is large you might wish to change resultArray to an NSMutableSet and titlesArray to an NSMutableDictionary mapping from resultId to indexDictionary values.
HTH
This chunk of code is a method that creates an array for use by multiple other classes. Input is an array from a CoreData fetch, of type NSDictionaryResultType.
3 of the fields are strings that I need to break into arrays, thus the componentsSeparatedByString.
The resulting array, _dataProductionArray, works great --- BUT --- this chunk of code takes a FULL 5 SECONDS to process for about 32,000 records.
Any help pointing out glaring mistakes that are causing this slow performance would be greatly appreciated!!
NSMutableArray *dataArray = [NSMutableArray array];
int j = 0;
int maxNumMonths = 0;
for (id obj in _dictionaries) {
if ([_dictionaries[j] [#"month"] length] >0 ) {
// get production values
NSArray *aItems = [_dictionaries[j] [#"prodA"] componentsSeparatedByString:#","];
NSArray *bItems = [_dictionaries[j] [#"prodB"] componentsSeparatedByString:#","];
NSArray *monthItems = [_dictionaries[j] [#"month"] componentsSeparatedByString:#","];
NSMutableArray *productionAArray = [NSMutableArray array];
NSMutableArray *productionBArray = [NSMutableArray array];
int monthLoop = 1;
for (NSNumber *month in monthItems) {
if (monthLoop <= MONTHS_OF_PRODUCTION) {
if ([month intValue] == monthLoop) {
[productionAArray addObject:[aItems objectAtIndex:monthLoop-1]];
[productionBArray addObject:[bItems objectAtIndex:monthLoop-1]];
productionCount ++;
if (monthLoop > maxNumMonths)
maxNumMonths = monthLoop;
}
}
monthLoop++;
}
NSDictionary *arrayItem = #{#"name":_dictionaries[j] [#"name"],
#"type":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"type"]],
#"height":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"height"]],
#"aArray":productionAArray,
#"bArray":productionBArray,
};
[dataArray addObject:arrayItem];
}
j++;
}
_dataProductionArray = [NSArray arrayWithArray:dataArray];
I can see a few optimizations you could do in the loop, but I'm not sure how much these would help (especially if the compiler is doing them anyway). The root problem is that 32k is a lot of iterations.
Do you need all 32k results at once? You could get a dramatic improvement in user experience by doing this work lazily, as the UI demands the transformed record.
This approach would be to make dataProductionArray a mutable dictionary, indexed by an NSNumber index. Then, instead of ...
// replace this
self.dataProductionArray[128];
// with this
[self dataProductionAtIndex:#128];
That new getter method calls the code you wrote lazily, like this ...
- (id)dataProductionAtIndex:(NSNumber *)index {
// replace dataProductionArray with dataProductionDictionary
id result = self.dataProductionDictionary[index];
if (!result) {
result = [self getDataAt:index];
self.dataProductionDictionary[index] = result;
}
return result;
}
Then getDataAt: is a simple refactor of the code you posted, except instead of looping 32k elements, it does the work for just one index that gets passed in....
- (id)getDataAt:(NSNumber *)index {
int j = [index intValue];
// no loop, just skip to iteration j
NSArray *aItems = [_dictionaries[j] [#"prodA"] componentsSeparatedByString:#","];
NSArray *bItems = [_dictionaries[j] [#"prodB"] componentsSeparatedByString:#","];
// and so on, then at the end, don't save arrayItem, just return it
NSDictionary *arrayItem = #{#"name":_dictionaries[j] [#"name"],
#"type":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"type"]],
#"height":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"height"]],
#"aArray":productionAArray,
#"bArray":productionBArray,
};
return arrayItem;
}
PS - A mutable dictionary is a good data structure for lazy evaluation. The next level of sophistication is NSCache, which acts like a mutable dictionary and also manages memory (class ref here).
Your for loop is daft. Just write
for (NSDictionary* dict in _dictionaries)...
and use dict instead of _dictionaries [j]. One method call saved each time.
stringWithFormat: creates a new string each time. Can't you just add the item itself instead of turning it into a string?
Instead of extracting all the items into productionAArray and productionBArray, create an NSIndexSet, fill it in the loop -- or better yet using a block -- and create the arrays in one go.
I need help with the following:
I have an NSArray with NSStrings, I want to loop thru these strings and find a matching string, when match is found the strings after this match will be extracted into an NSDictionary until a certain other match is hit.
Here is an example:
NSArray *array = #[#"Fruit",#"Apple",#"Vegtable",#"Tomato",#"Fruit",#"Banana",#"Vegtable",#"Cucumber"];
So I want to loop thru this array and split it in 2 arrays one for fruit and one for vegetable.
Anyone can help with the logic?
Thanks
This is probably the simplest way to solve the problem:
NSArray *array = #[#"Chair",#"Fruit",#"Apple",#"Orange",#"Vegetable",#"Tomato",#"Fruit",#"Banana",#"Vegetable",#"Cucumber"];
NSMutableArray *fruitArray = [NSMutableArray array];
NSMutableArray *vegetableArray = [NSMutableArray array];
NSMutableArray *currentTarget = nil;
for (NSString *item in array)
{
if ([item isEqualToString: #"Fruit"])
{
currentTarget = fruitArray;
}
else if ([item isEqualToString: #"Vegetable"])
{
currentTarget = vegetableArray;
}
else
{
[currentTarget addObject: item];
}
}
In one iteration over the array, you just keep adding items to a result array using a pointer to one of two result arrays according to the last occurrence of the #"Fruit" or #"Vegetable" string.
This algorithm ignores all items before the first occurrence of the #"Fruit" or #"Vegetable" string, because the currentTarget is initialized to nil, which ignores the addObject: messages. If you want different behaviour, just change the initialization.
You said you wanted the results in a NSDictionary, but didn't specify what should be the key. If you want one NSDictionary with two keys, Fruit and Vegetable, and values NSArrays containing the items, just use the arrays previously created:
NSDictionary *dict = #{ #"Fruit": fruitArray, #"Vegetable": vegetableArray };
PS: You have a typo in your example, Vegtable instead of Vegetable. I corrected it in my code, so keep it in mind.
If I completely understand you:
NSArray *array = #[#"Fruit",#"Apple",#"Vegtable",#"Tomato",#"Fruit",#"Banana",#"Vegtable",#"Cucumber"];
NSMutableArray *fruits = [NSMutableArray array];
NSMutableArray *vegtables = [NSMutableArray array];
for (NSInteger i = 0; i < array.count; ++i){
if ([array[i] isEqualToString:#"Fruit"]){
++i;
[fruits addObject:array[i]];
}
else if ([array[i] isEqualToString:#"Vegtable"]){
++i;
[vegtables addObject:array[i]];
}
}
The goal is to compare two arrays as and check if they contain the same objects (as fast as possible - there are lots of objects in the arrays). The arrays cannot be checked with isEqual: as they are differently sorted.
I already tried the solution posted here (https://stackoverflow.com/a/1138417 - see last code snippet of the post by Peter Hosey). But this doesn't work with differently sorted arrays.
The code I'm using now is the following:
+ (BOOL)arraysContainSameObjects:(NSArray *)array1 andOtherArray:(NSArray *)array2 {
// quit if array count is different
if ([array1 count] != [array2 count]) return NO;
BOOL bothArraysContainTheSameObjects = YES;
for (id objectInArray1 in array1) {
BOOL objectFoundInArray2 = NO;
for (id objectInArray2 in array2) {
if ([objectInArray1 isEqual:objectInArray2]) {
objectFoundInArray2 = YES;
break;
}
}
if (!objectFoundInArray2) {
bothArraysContainTheSameObjects = NO;
break;
}
}
return bothArraysContainTheSameObjects;
}
This works, but those are two nested fast enumerations. Is there a way to do a faster comparison?
As per your code, you are strict to same number of elements and each object of first array should be there in second array and vice versa.
The fastest way would be to sort both the array and compare them.
Ex:
NSArray *array1=#[#"a",#"b",#"c"];
NSArray *array2=#[#"c",#"b",#"a"];
array1=[array1 sortedArrayUsingSelector:#selector(compare:)];
array2=[array2 sortedArrayUsingSelector:#selector(compare:)];
if ([array1 isEqualToArray:array2]) {
NSLog(#"both have same elements");
}
else{
NSLog(#"both having different elements");
}
How about converting both arrays to sets and comparing them.
NSSet *set1 = [NSSet setWithArray:arr1];
NSSet *set2 = [NSSet setWithArray:arr2];
Compare the two using
if([set1 isEqualToSet:set2]) {
}
Use containsObject: method instead of iterating the whole array.
NSArray *array;
array = [NSArray arrayWithObjects: #"Nicola", #"Margherita", #"Luciano", #"Silvia", nil];
if ([array containsObject: #"Nicola"]) // YES
{
// Do something
}
like this
+ (BOOL)arraysContainSameObjects:(NSArray *)array1 andOtherArray:(NSArray *)array2 {
// quit if array count is different
if ([array1 count] != [array2 count]) return NO;
BOOL bothArraysContainTheSameObjects = YES;
for (id objectInArray1 in array1) {
if (![array2 containsObject:objectInArray1])
{
bothArraysContainTheSameObjects = NO;
break;
}
}
return bothArraysContainTheSameObjects;
}
Tried to get the accepted answer working but it wasn't quite the best fit for my situation.
I found this answer and all credit goes to #joel kravets for the method.
Basically sorting using a comparator enables you to sort using objects more easily - hence the problem I was facing when trying to use the above solution.
NSArray * array1 = [NSArray arrayWithArray:users];
NSArray * array2 = [NSArray arrayWithArray:threadUsers];
id mySort = ^(BUser * user1, BUser * user2){
return [user1.name compare:user2.name];
};
array1 = [array1 sortedArrayUsingComparator:mySort];
array2 = [array2 sortedArrayUsingComparator:mySort];
if ([array1 isEqualToArray:array2]) {
NSLog(#"both are same");
}
else{
NSLog(#"both are different");
}
Previously I had tried to use other answers like those above, using break to go through loops but in the end this answer came out easiest probably due to its speed and also that in the end we have the if statement allowing us to put code depending on if they are the same or different.
Thanks to Anoop for getting me on the right track and Joel for helping me to tighten the efficiency of it
If you want to check whether both arrays contain the same duplicates, just use NSCountedSet. It's like an NSSet, but each object in the set also has a count telling you how often it has been added. So
BOOL same = (array1.count == array2.count);
if (same && array.count > 0)
{
NSCountedSet* set1 = [[NSCountedSet alloc] initWithArray:array1];
NSCountedSet* set2 = [[NSCountedSet alloc] initWithArray:array2];
same = ([set1 isEqual: set2]);
}
No matter how you do it, this will be time consuming, so you might consider if there are special cases that can be handled quicker. Are these arrays usually the same, or almost the same, or is it true 99% of the time that they are different and that 99% of the time a random element of array1 is not in array2? Are the arrays often sorted? In that case, you could check whether there are identical objects in identical positions, and then consider only those objects that are not the same. If one array contains objects a, b, c, d, e and the other contains a, b, x, d, y, then you only need to compare the array [c, e] vs. [x, y].
[docTypes containsObject:#"Object"];
It will works for your req. As early as fast it will return boolean value for it.
This way the complexity is O(N^2), if you follow this approach you can't do it with a lower complexity. While instead you can do it with O(N log(N)) if you sort both arrays and then compare them. This way after having them sorted you will do it using isEqualToArray: in other N operations.
NSArray *filtered = [someArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"someParamter == %#", paramValue]]];
if (filtered.count) {
}
the main plus is you can use it for any kind of objects: custom, system, NSDictionary. for example I need to know is my UINavigationController's stack contains MySearchResultsVC and MyTopMenuItemsVC or not:
NSArray *filtered = [self.navigationController.viewControllers filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"class IN %#",
[NSArray arrayWithObjects:
[MySearchResultsVC class],
[MyTopMenuItemsVC class],
nil]]];
if (filtered) {
/* ok, now we can handle it! */
}
I know it's late but i just wanna share what i did..
NSString *stringArr1 = [NSString stringWithFormat:#"%#", array1];
NSString *stringArr2 = [NSString stringWithFormat:#"%#", array2];
if ([stringArr1 isEqual: stringArr2])
NSLog(#"identical");
else
NSLog(#"not");
this is just like comparing "#[#1,#2,#3,#4]" == "[#3,#2,#1,#4]".. which is obviously false..
i guess this will do:
[array1 isEqualToArray:array2];
returns bool;