Finding out NSArray/NSMutableArray changes' indices - ios

I have a NSMutableArray oldArray. Now, at one point, this NSMutableArray object gets updated with another NSMutableArray, which may have more, less or same number of elements as the previous NSMutableArray.
I want to compare the old and the new array for changes. What I want are two NSArrays addedArray and removedArray which will contain the indices of the elements which have been added and/or removed from the old array.
This whole problem will be more clear with an example :
oldArray = {#"a",#"b",#"d",#"e",#"g"};
newArray = {#"a",#"c",#"d",#"e",#"f",#"h"};
So, here the removed objects are #"b" and #"g" at indices 1 and 4 respectively. And the objects added are #"c", #"f" and #"h" at indices 1, 4 and 5 (first objects are removed, then added).
Therefore,
removedArray = {1,4}; and addedArray = {1,4,5};
I want an efficient way to get these two arrays - removedArray and addedArray from the old and new NSMutableArray. Thanks! If the problem is not very understandable, I'm willing to provide more information.
Edit 1
Perhaps it will be more clear if I explain what I want to use this for.
Actually what I am using this for is updating a UITableView with methods insertRowsAtIndexPaths and removeRowsAtIndexPaths with animation after the tableview gets loaded, so that the user can see the removed rows go out and the new rows come in. The tableview stores the Favourites elements which the user can add or remove. So after adding some favorites and removing some; when the user comes back to the favourites tableview, the animations will be shown.
Edit 2
Should have mentioned this earlier, but the elements in both the old and the new array will be in an ascending order. Only the indices of the removal or addition matters. The order cannot be changed. ex. {#"b",#"a",#"c",#"d"} cannot be an array.

I have tried iterating through the old and the new arrays using loops and if conditions, but is it getting really messy and buggy.
This is not a simple problem. First, note that it may have multiple solutions:
a b c d
b c d e
both (a={0, 1, 2, 3}, r={0, 1, 2, 3}) and (a={3}, r={0}) are valid solutions. What you are probably looking for is a minimal solution.
One way to get a minimal solution is by finding the Longest Common Subsequence (LCS) of the two sequences. The algorithm for finding LCS will tell you which elements of the two sequences belong to the LCS, and which do not. Indexes of each element of the original array that is not in LCS go into the removed array; indexes of elements of the new array that are not in LCS go into the added array.
Here are a few examples (I parenthesized the elements of LCS):
0 1 2 3 4 5
(a) b (d) (e) g
(a) c (d) (e) f h
The items of old not in LCS are 1 and 4; the items of new not in LCS are 1, 4, and 5.
Here is another example:
0 1 2 3
a (b) (c) (d)
(b) (c) (d) e
Now added is 3 and removed is 0.

addedArray = newArray ∖ (newArray ∩ oldArray)
= newArray ∖ ({#"a",#"c",#"d",#"e",#"f",#"h"} ∩ {#"a",#"b",#"d",#"e",#"g"})
= newArray ∖ {#"a",#"d",#"e"}
= {#"a",#"c",#"d",#"e",#"f",#"h"} ∖ {#"a",#"d",#"e"}
= {#"c",#"f",#"h"}
removedArray = oldArray ∖ (oldArray ∩ newArray)
= oldArray ∖ ({#"a",#"b",#"d",#"e",#"g"} ∩ {#"a",#"c",#"d",#"e",#"f",#"h"})
= oldArray ∖ {#"a",#"d",#"e"}
= {#"a",#"b",#"d",#"e",#"g"} ∖ {#"a",#"d",#"e"}
= {#"b",#"g"}
To find intersections of array, you can view the following SO post:
Finding Intersection of NSMutableArrays

If both arrays are already sorted in ascending order, you can find the added and removed
elements with a single loop over both arrays (using two independent pointers into the array):
NSArray *oldArray = #[#"a",#"b",#"d",#"e",#"g"];
NSArray *newArray = #[#"a",#"c",#"d",#"e",#"f",#"h"];
NSMutableArray *removedArray = [NSMutableArray array];
NSMutableArray *addedArray = [NSMutableArray array];
NSUInteger iold = 0; // index into oldArray
NSUInteger inew = 0; // index into newArray
while (iold < [oldArray count] && inew < [newArray count]) {
// Compare "current" element of old and new array:
NSComparisonResult c = [oldArray[iold] compare:newArray[inew]];
if (c == NSOrderedAscending) {
// oldArray[iold] has been removed
[removedArray addObject:#(iold)];
iold++;
} else if (c == NSOrderedDescending) {
// newArray[inew] has been added
[addedArray addObject:#(inew)];
inew++;
} else {
// oldArray[iold] == newArray[inew]
iold++, inew++;
}
}
// Process remaining elements of old array:
while (iold < [oldArray count]) {
[removedArray addObject:#(iold)];
iold++;
}
// Process remaining elements of new array:
while (inew < [newArray count]) {
[addedArray addObject:#(inew)];
inew++;
}
NSLog(#"removed: %#", removedArray);
NSLog(#"added: %#", addedArray);
Output:
removed: (
1,
4
)
added: (
1,
4,
5
)

Related

iOS: Remove NSArray in NSMutableArray in For Loop

This is a pretty simple concept, but I'm not getting the results I'm wanting. I have an NSMutableArray that is populated with NSArrays, I want to loop through that NSMutableArray and remove certain NSArrays based on a key-value pair. My results have many of the NSArrays that I should be removing and I think it has something to do with the count of the NSMutableArray and the int I declare in the For Loop.
Here is my code: (restArray is the NSMutableArray)
for (int i=0; i<restArray.count; i++) {
NSArray *array = restArray[i];
if ([[array valueForKey:#"restaurant_status"] isEqualToString:#"0"]) {
[restArray removeObjectAtIndex:i];
}
}
Does someone know what I am doing wrong here?
It is not recommended to modify an array on what are you currently iterating.
Lets create a tmp array, and reverse your logic.
NSMutableArray * tmpArray = [NSMutableArray array];
for (int i=0; i<restArray.count; i++) {
NSArray *array = restArray[i];
if (![[array valueForKey:#"restaurant_status"] isEqualToString:#"0"] {
[tmpArray addObject:array];
}
}
So at the end of the iteration, you should end up with tmpArray having the arrays you needed.
Use NSPredicate:
NSArray *testArray = #[#{#"restaurant_status" : #"1"}, #{#"restaurant_status" : #"0"}];
NSArray *result = [testArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(restaurant_status == %#)", #"1"]];
When you remove an element all the elements past it shift down by one, e.g. If you remove the element at index 3 then the element previously at index 4 moves to index 3.
Every iteration you increase the index by one.
Combine the above two and you see that when you remove an element your code skips examining the following element.
The simple solution is to reverse the order of the iteration:
for (int i = restArray.count - 1; i >= 0; i--)
and then your algorithm will work.
Addendum
You can safely ignore this addendum if your arrays contain < 2^32 elements and you use Clang or GCC (and most other C compilers).
It has been raised in the comments that this answer has a problem if the array has 0 elements in it. Well yes & no...
First note that the code in the question is technically incorrect: count returns an NSUInteger which on a 64-bit machine is a 64-bit unsigned integer, the loop variable i is declared as an int which is 32-bit signed. If the array has more than 2^31-1 elements in it then the loop is incorrect.
Most people don't worry too much about this for some reason ;-) But let's fix it:
for (NSInteger i = restArray.count - 1; i >= 0; i--)
Back to the problem with an empty array: in this case count returns unsigned 0, C standard arithmetic conversions convert the literal 1 to unsigned, the subtraction is done using modular arithmetic, and the result is unsigned 2^64-1.
Now that unsigned value is stored into the signed i. In C converting from signed to unsigned of the same type is defined to be a simple bit-copy. However converting from unsigned to signed is only defined if the value is within range, and implementation defined otherwise.
Now 2^64-1 is greater than the maximum signed integer, 2^32-1, so the result is implementation defined. In practice most compilers, including Clang and GCC, choose to use bit-copy, and the result is signed -1. With this the above code works fine, both the NSInteger and the int (if you've less than 2^32-1 elements in your array) versions.
What the comments raise is how to avoid this implementation-defined behaviour. If this concerns you the following will handle the empty array case correctly with ease:
for (NSUInteger i = restArray.count; i > 0; )
{
i--; // decrement the index
// loop body as before
}
If the array is empty the loop test, i > 0, will fail immediately. If the array is non-empty i, being initialised to the count, will start as one greater than the maximum index and the decrement in the loop will adjust it - effectively in the loop test i contains the number of elements left to process and in the loop body after the decrement contains the index of the next element to process.
Isn't C fun (and mathematically incorrect by definition)!

how to get distinct objects from an array containing many instances of the same object [duplicate]

This question already has answers here:
NSArray: Remove objects with duplicate properties
(5 answers)
Closed 8 years ago.
I have an array (itemArray) that contains a number of objects. Each of these objects, which we'll refer to as item objects, has a property containing an item identifier that tells what type of item it is. We'll call that one IID.
Now, the user is going to be using the application to add instances of item to itemArray, and the user may add several identical instances (for example, 4 items, each with an IID of 3). Eventually, itemArray is going to contain arguably hundreds of instances of item, and those instances will be added in no particular order, and there could be several instances that are identical to other instances in the array (4 items with an IID of 3, 2 items with an IID of 6, etc etc).
I need to create an array (let's call it tempArray) that will be able to give a summary of the objects in the array based on the IID. I don't need to count the objects of each type in itemArray, I just need to add 1 instance of item to tempArray for each type of item in itemArray.
So, for example:
If my itemArray looks like this:
item.IID = 4
item.IID = 3
item.IID = 4
item.IID = 6
item.IID = 4
item.IID = 5
item.IID = 6
item.IID = 3`
Then I need tempArray too look like this:
item.IID = 4
item.IID = 3
item.IID = 6
item.IID = 5
Where tempArray just shows the variety of objects in itemArray based on the IID.
Thanks in advance!
If you can assume that an item is equal to another item based on their IIDs, I would implement the isEqual method checking the IIDs (returning YES if they're the same) and then use a NSSet to get the "filtered" list (once you have mentioned in your comment that the order isn't important to you). Like this:
#implementation YourItem
- (BOOL)isEqual:(id)object
{
if (self != object)
return NO;
if (![self.class isKindOfClass:[object class]])
return NO;
return self.IID == object.IID;
}
#end
As remembered by #jlehr, you need to override - (NSUInteger)hash too. You can do this by implemeting something like this:
- (NSUInteger)hash
{
/* every property that will make it equals to another object */
return [self.IID hash] ^ [self.name hash] /* ... ^ */;
}
(There is a great post written by Mike Ash explaining in full details Equality and Hashing)
and then...
NSSet *tempSet = [NSSet setWithArray:itemArray];
and if you want to convert it to a NSArray again:
NSArray *tempArray = [tempSet allObjects];
If I understood you correctly, I would solve this using NSDictionary and would insert an item only if the key does not exist (item.iid is the key of the dictionary).
you can ask the dictionary if it has the object or it has the key, both works fine.
May be you can create NSSet from your array and then create NSArray from NSSet.
NSSet *tempSet = [NSSet setWithArray:yourArray];
NSArray *tempArray = [tempSet allObjects];

How to compare every pair of elements inside a NSArray?

I have an NSArray filled with only NSStrings
I understand that to iterate over a NSArray of n elements, all I have to do is use for (NSString *element in arrayOfElements). However, I was wondering if there is specific function that will perform a comparison between every string element in the array with each other. For example, if I have the array:
[#"apple", #"banana", #"peach", #"kiwi"],
how would I do the comparison so apple is compared to banana, peach and then kiwi; and then banana is against peach and wiki, and finally peach is against kiwi?
Try using nested for loops, ex:
for (int i = 0 ; i < array.count ; i ++) {
for (int j = i + 1 ; j < array.count ; j ++) {
// compare array[i] to array [j]
}
}
Edit: And although wottle's suggestion would work, I'd recommend mine in this case, since it won't waste iterations going over the same comparisons multiple times. What I've done in this algorithm by setting j = i + 1 is compare each element in the array only to the ones after it.
Given "the array will not have any duplicates, and every NSString will be unique" this sounds like a great case for using NSSet classes instead of NSArray. NSMutableSet provides:
minusSet:
Removes each object in another given set from the receiving
set, if present.
and
intersectSet:
Removes from the receiving set each object that isn’t a
member of another given set.
I'm not sure which operation you're looking for but it sounds like one of those should cover your exact use case.
What you're trying to do is a bit beyond what custom comparators were meant to do. Typically when you have a list and you want to run a custom comparator, you're doing it to sort the list. You seem to want to do some specific action when certain items in the list compare to others, and for that, I think a loop within a loop is your best bet. It won't be very good performance, so hopefully you are not expecting a large array:
-(void) compareArrayToSelf
{
NSArray *array=#[#"apple", #"bananna", #"peach", #"kiwi"];
for( NSString *value1 in array)
{
for( NSString *value2 in array)
{
if( ![value1 isEqualToString:value2] && [self compareArrayValue:value1 toOtherValue:value2])
{
//Do something with either value1 or value2
}
}
}
}

I want to populate an array2 with values from 1 to 100, but leaving values which are present in array1

I want to populate an array2 with values from 1 to 100, but leaving values which are contained in array1. Array1 may not be sorted.
For Example:If array1[2,8,3], I want array2 to be [1,4,5,6,7,9,10,11......98,99,100]
If I understand correctly what you're trying to achieve, this code snippet will solve the problem:
for (NSInteger i = 1; i < 101; i++) {
if (![array1 containsObject:#(i)]) {
[array2 addObject:#(i)];
}
}
It'll basically iterate from 1 to 100 and see if current number is contained in array1 — if not, it'll add this number to array2 as a NSNumber instance (if you need strings as in your original code, use [NSString stringWithFormat:#"%d", i]).

Comparing an array of arrays inside an array to another array of arrays of strings

I have an array (bigArray from the example) that contains multiply arrays containing strings. I need to add to bigArray another array (such as arrayA) but I want to check if an array like that already exists. If it exists I don't want to add it. The order of the small arrays (such as arrayX from the example) doesn't differentiate them from one another so if I already have an array such as arrayA containing the same arrays but in different order,(arrayZ,arrayY, arrayX instead of arrayX,arrayY,arrayZ but with the same string content) that array won't be added to the big array.
How can I accomplish this?
Examples:
Arrays example:
-bigArray
--arrayA
----arrayX -> 16,4,5,6,64
----arrayY -> 1,3,6,72,14
----arrayZ -> 13,73,50,34
--arrayB
----arrayX -> 1,4,5,6,4,2
----arrayY -> 1,4,6,12,14
----arrayZ -> 13,33,50,34
The order of the small arrays doesn't differentiate them:
--arrayc
----array -> 16,4,5,6,64
----array -> 1,3,6,72,14
is the same as:
--arrayd
----array -> 1,3,6,72,14
----array -> 16,4,5,6,64
Therefore arrayD won't be added to the big array.
If you use NSSet instead of NSArray you get the uniquing for free. The result is a (mutable) set of (immutable) sets of arrays.
NSSet *setA = [NSSet setWithArray:#[ // arrayA
#[#16,#4,#5,#6,#64], // arrayX
#[#1,#3,#6,#72,#14], // arrayY
#[#13,#73,#50,#34], // arrayZ
]];
NSSet *setB = [NSSet setWithArray:#[ // arrayB
#[#1,#4,#5,#6,#4,#2], // arrayX
#[#1,#4,#6,#12,#14], // arrayY
#[#13,#33,#50,#34], // arrayZ
]];
NSMutableArray *bigSet = [NSMutableSet setWithArray:#[setA, setB]];
NSLog(#"%lu", [bigSet count]);
This prints "2", as expected.
NSSet *setC = [NSSet setWithArray:#[ // arrayC
#[#1,#4,#6,#12,#14], // arrayY
#[#1,#4,#5,#6,#4,#2], // arrayX
#[#13,#33,#50,#34], // arrayZ
]];
[bigSet addObject:setC];
NSLog(#"%lu", [bigSet count]);
Still prints "2", because setC and setB are equal.
You could try iterating through the objects of the array you want to add (ArrayB) in your example, and try - (BOOL)containsObject:(id)anObject for the checking whether the smaller arrays (x, y, and ,z) are present in the Array A.
if(ArrayA.count!=ArrayB.count){
//Don't check because the arrays will not be same, so add ArrayB
}else{
int i=0,counter=0;
for(i=0;i<ArrayB.count;i++){
if(![ArrayA contains [ArrayB objectAtIndex:i]]){
counter = 1;
}
}
if(counter==1){
//Add ArrayB because elements are not same.
}else{
//Don't add ArrayB because elements are same.
}
}
Try something like this code.
I'm not sure why this is being done and I'm not sure I completely follow, but you can sort each of the minor arrays before doing any entry by entry comparison. Then the order won't matter.
Maybe you could concat key and value as a new string and compare them together.
Best regards.
/Daniel Carlsson

Resources