7

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.

aksh1t
  • 5,410
  • 1
  • 37
  • 55
  • 1
    Well, I have tried iterating through the old and the new arrays using loops and if conditions, but is it getting really messy and buggy. That works for some cases and not for some others. I was wondering whether there is some method with the NSMUtableArray which would ease what I want to do. – aksh1t Jun 26 '13 at 09:38
  • Do you really need the indices of the added and removed objects? To me this sounds more like a job for what NSSet is made for. Or NSMutableSet respectively. You can unify sets, remove one set from the other, identify intersects etc. And you can create them from arrays and vice versa. But you will loose the indices. – Hermann Klecker Jun 26 '13 at 09:43
  • @HermannKlecker - The indices of the removed and added elements are all that I want. Those are for the animation in the tableview using methods `insertRowsAtIndexPaths` and `removeRowsAtIndexPaths`. – aksh1t Jun 26 '13 at 09:59
  • In the example above there are two arrays. Both haven an Elment NSString with the contents @"a". These are two objects of the same type and value. As you wish to apply those on re-aliging a table view, do we have a chance that in your concrete case, both objects represended by @"a" in your example are actually the very same object meaning instance? If they are the same object then you could solve that easily with NSArray built-in methods. – Hermann Klecker Jun 26 '13 at 10:30
  • Probably the simplest is to put them into NSSets, do your "logic" on the sets, then find the original items in your arrays to produce the indexes you need. Probably not the most efficient, though. – Hot Licks Jun 26 '13 at 11:20

3 Answers3

5

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Oh, how foolish of me, but I should have mentioned it in the question, the elements in the old and the new arrays will always be in an ascending sequence. __b c d a__ is not possible. It will only be adding or removing the elements in an ascending order. I'll edit the question. – aksh1t Jun 26 '13 at 10:08
  • @aksh1t That's OK, the algorithm for LCS is going to work on arbitrary sequences. – Sergey Kalinichenko Jun 26 '13 at 10:10
  • @aksh1t I edited to make sure that both sequences are in ascending order. – Sergey Kalinichenko Jun 26 '13 at 10:12
  • wow. ok this is something that I could work my loops and ifs around. Thanks! Accepting in some time. – aksh1t Jun 26 '13 at 10:13
  • @aksh1t The article that I linked has a section on [printing the diff](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Print_the_diff), you can adapt that algorithm to your needs by storing indexes rather than printing elements. – Sergey Kalinichenko Jun 26 '13 at 10:19
  • Found an implementation here, but am yet to try it out - https://github.com/khanlou/NSArray-LongestCommonSubsequence – trss Feb 24 '14 at 14:54
3
  1. 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"}             
    
  2. 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

Community
  • 1
  • 1
Ozair Kafray
  • 13,351
  • 8
  • 59
  • 84
  • 3
    @Filip Every problem has a pretty, elegant, fast, and incorrect solution. This is one of them :) To see that it's plainly incorrect, consider what would this algorithm produce when the new array is the old array that has been rotated by a single element: this algorithm would produce two empty answers. – Sergey Kalinichenko Jun 26 '13 at 09:46
  • This one, like @dasblinkenlight says does not serve my purpose of getting the indices. 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. – aksh1t Jun 26 '13 at 09:52
1

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
)
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • This is exactly what I did (with for loops instead of while though). Thanks for the answer! I accepted the other answer, because it helped me with understanding the solution. Thanks anyways. – aksh1t Jun 27 '13 at 06:12