0

I searched all over the internet to find an answer for this. I am looping through two lists (list1 and list2) with nested for loops and removing duplicate records in first list based on three criteria. If all records in these two lists match each other, I get an out of bounds error. I assume it happens when I remove all the items from the first list, and when it finally reduces to 0, and does not have any records to loop through, but putting an if statement to check the count of the first list (if inbox_emails_filtered_contacts.Count > 0) does not help either. Please let me know if any of you can tell me why this errors out.

Outlook Add-in in C#.net

for (int i = 0; i < list1.Count; i++)
{
    for (int j = 0; j < list2.Count; j++)
    {
        if (list1.Count > 0)
        {
            if ((list1[i].username == registered_user)
                && (list1[i].from_email.ToLower() == list2[j].from_email.ToLower())
                && (list1[i].email_subject == list2[j].email_subject)
                && (list1[i].email_timestamp.ToLongDateString() == list2[j].email_timestamp.ToLongDateString()))
            {
                //Remove the duplicate email from inbox_emails_filtered_contacts
                list1.RemoveAt(i);
            }
        }
    }
}
amey1908
  • 157
  • 3
  • 11
  • It looks like you're looping through two arrays, didn't know you could index a list like that? You'll probably just want to decrement the index everytime you remove an item – dtsg Jul 20 '12 at 07:40
  • When you remove item at index i, item at i+1 position takes its place. So in the next loop, you treat the i+2 item, and miss the i+1 item (which is at i position now ...). A reverse for doesn't introduce this problem. – Vivien Ruiz Jul 20 '12 at 07:44
  • What is the type of elements contained in list1 and list2? I suppose these lists are generic lists `List`, what is the name of actual type `T`? – Ivan Golović Jul 20 '12 at 08:03
  • Yes, Ivan, you are right, it is generic list of type 'email' which is a user-defined class. THe email class has the following attributes - public string username; public string from_email; public string from_name; public string email_subject; public string email_body; public DateTime email_timestamp; – amey1908 Jul 20 '12 at 08:51
  • Can anything else modify the list in another thread whilst the code is executing? – Bob Vale Jul 20 '12 at 09:03

4 Answers4

1

I would suggest using while loop here. Also you need to break out of inner loop if a match is found to restart checking from the beginning.

int i = 0;
while (i < list1.Count)
{
    int found = 0;
    for (int j = 0; j < list2.Count; j++)
    {
        if ((list1[i].username == registered_user)
            && (list1[i].from_email.ToLower() == list2[j].from_email.ToLower())
            && (list1[i].email_subject == list2[j].email_subject)
            && (list1[i].email_timestamp.ToLongDateString() == list2[j].email_timestamp.ToLongDateString()))
        {
            //Remove the duplicate email from inbox_emails_filtered_contacts
            list1.RemoveAt(i);
            found = 1;
            break;
        }
   }
   if (!found)
   {
       i++;
   }
}
  • I still get the same error - "Index was out of range. Must be non-negative and less than the size of the collection." Do you think its the problem with the size of the list. I define as follows - List list = new List() where email is a class – amey1908 Jul 20 '12 at 08:08
  • So I checked my code in debugging mode and the code gets to if statement even when the while state is not true. For example, i=13 and list1=13 and the code still bombs at the if statement when it shouldn't be there as the condition in while statement is false. Not sure how it gets through. – amey1908 Jul 20 '12 at 08:27
0

Use a reverse for when you iterate a list/array while removing item out of it (snippet "forr" in Visual) :

for (int i = list1.Count - 1; i >= 0; i--)
Vivien Ruiz
  • 618
  • 3
  • 11
  • OK, there is an other problem too then, but you still should use this pattern, or you'll get in trouble ;) – Vivien Ruiz Jul 20 '12 at 07:54
  • 1
    The other problem is that you end up calling removeAt multiple times if there are multiple matches. Add a break statement straight after the removeAt line to stop the j loop from continuing. – Bob Vale Jul 20 '12 at 08:01
0

The likelyhood is that there are multiple duplicates in application_emails and so removeAt(i) is being called multiple times. Your if will never catch the case where i is the last item in a list longer than 2 and you end up removing multiple times.

Additionally you will also end up skipping emails with the remove statement. Assume i=4, if you remove at 4 index 4 will contain a new item and i will be 5 on the next iteration. You may be better off with a while loop

int i=0;
while (i < inbox_emails.Count) {
   bool foundDuplicate=false;
   for (int j=0;j<for (int j = 0; j < application_emails.Count; j++) {       
     if ((inbox_emails[i].username == registered_user)       
         && (inbox_emails[i].from_email.ToLower() == application_emails[j].from_email.ToLower())       
         && (inbox_emails[i].email_subject == application_emails[j].email_subject)       
         && (inbox_emails[i].email_timestamp.ToLongDateString() == application[j].email_timestamp.ToLongDateString()))       
        {       
           foundDuplicate=true;
           break;   // This is optional but it stops the j loop from continuing as we've found a duplicate
        }       
    }   
    if (foundDuplicate) {
       inbox_emails.RemoveAt(i);
    } else {
       i++;
    }
}

If this is a single thread you could just replace the list using Linq, make sure you have a using System.Linq

inbox_emails = inbox_emails.Where(i=> 
                 i.username != registered_user 
                 || ! application_emails.Any(j=>
                    i.from_email.ToLower() == j.from_email.ToLower()
                    && i.email_subject == j.email_subject
                    && i.email_timestamp.ToLongDateString() == j.email_timestamp.ToLongDateString()
                    )
                 ).ToList();
Bob Vale
  • 18,094
  • 1
  • 42
  • 49
  • Thanks for your help but the code above is still giving me the same error. Not sure what's wrong. Any other ideas. – amey1908 Jul 20 '12 at 08:24
  • So I checked my code in debugging mode and the code gets to if statement even when the while state is not true. For example, i=13 and inbox_emails=13 and the code still bombs at the if statement when it shouldn't be there as the condition in while statement is false. Not sure how it gets through. – amey1908 Jul 20 '12 at 08:26
  • The tags indicate outlook addin. Does this mean that the lists are actualy outlook objects, and if so has outlook modified the collection on you whilst you are processsing the list? – Bob Vale Jul 20 '12 at 09:07
  • Bob, I am reading data from outlook objects but creating my own lists in the code. The list1 and list2 are of user-defined class 'email'. Right now my code runs on the main outlook thread and nothing can change the lists since the outlook gets locked while my code is running. – amey1908 Jul 20 '12 at 09:56
  • @amey1908 as this is a single thread can you replace the list with my edited answer to perform a linq query – Bob Vale Jul 20 '12 at 14:12
0

here is an implementation of an Email class

class Email:IComparable<Email>
{
    private static int _Id;

    public Email()
    {
        Id = _Id++;
    }

    public int Id { get; private set; }
    public string UserName { get; set; }
    public string Body { get; set; }
    public string Subject { get; set; }
    public DateTime TimeStamp { get; set; }

    public override int GetHashCode()
    {
        return UserName.GetHashCode() ^ Body.GetHashCode() ^ Subject.GetHashCode();
    }

    public int CompareTo(Email other)
    {
        return GetHashCode().CompareTo(other.GetHashCode());
    }

    public override string ToString()
    {
        return String.Format("ID:{0} - {1}", Id, Subject);
    }

    public override bool Equals(object obj)
    {
        if (obj is Email)
            return CompareTo(obj as Email) == 0;
        return false;
    }

}

and 2 ways of getting the diff.

        var list1 = new List<Email>();
        var ran = new Random();
        for (int i = 0; i < 50; i++)
        {
            list1.Add(new Email() { Body = "Body " + i, Subject = "Subject " + i, UserName = "username " + i, TimeStamp = DateTime.UtcNow.AddMinutes(ran.Next(-360, 60)) });
        }
        var list2 =  new List<Email>();

        for (int i = 0; i < 50; i++)
        {
            if (i % 2 == 0)
                list2.Add(new Email() { Body = "Body " + i, Subject = "Subject Modifed" + i, UserName = "username " + i, TimeStamp = DateTime.UtcNow.AddMinutes(ran.Next(-360, 60)) });
            else 
                list2.Add(new Email() { Body = "Body " + i, Subject = "Subject " + i, UserName = "username " + i, TimeStamp = DateTime.UtcNow.AddMinutes(ran.Next(-360, 60)) });

        }


        foreach (var item in list2.Intersect<Email>(list1))
        {
            Console.WriteLine(item);
        }

        foreach (var item in list1)
        {
            for (int i = 0; i < list2.Count; i++)
            {

                if (list2[i].Equals(item))
                {
                    Console.WriteLine(item);
                    list2.RemoveAt(i);
                    break;
                }
            }
        }

        Console.WriteLine(list2.Count);
Stig
  • 1,323
  • 16
  • 22