How do you modify an array while iterating through it?

G

Gustaf Liljegren

I ran into this problem today: I got an array with Account objects. I need
to iterate through this array to supplement the accounts in the array with
more data. But the compiler complains when I try to modify the objects in
the array while iterating through it. I marked the bugs in this code:

// Loop through all previously added accounts
foreach(Account a in a1) // a1 is an ArrayList
{
// If name and context is the same
if(a.Name == name && a.Context == context)
{
// Add the amount to the existing account object in the array
a.Amount += amount; // BUG!
break;
}
else
{
// Add a new account object to the array
Account newAccount = new Account(name, ns, context, amount);
a1.Add(newAccount); // BUG!
}
}

There is an outer loop where name, context and amount are found. Hope I
provided enough info. What is the right way of doing this?

Gustaf
 
J

Jon Skeet [C# MVP]

Gustaf Liljegren said:
I ran into this problem today: I got an array with Account objects. I need
to iterate through this array to supplement the accounts in the array with
more data. But the compiler complains when I try to modify the objects in
the array while iterating through it. I marked the bugs in this code:

// Loop through all previously added accounts
foreach(Account a in a1) // a1 is an ArrayList
{
// If name and context is the same
if(a.Name == name && a.Context == context)
{
// Add the amount to the existing account object in the array
a.Amount += amount; // BUG!
break;
}
else
{
// Add a new account object to the array
Account newAccount = new Account(name, ns, context, amount);
a1.Add(newAccount); // BUG!
}
}

There is an outer loop where name, context and amount are found. Hope I
provided enough info. What is the right way of doing this?

You should be able to change the account itself - the first one
shouldn't be a problem, in other words (unless it's a value type?) -
but the second one is changing the list itself, which you can't do
without it throwing an exception. However, I'm surprised if you get a
*compiler* exception rather than a runtime exception.

Could you produce a short but complete example program which
demonstrates the problem?
 
G

Gustaf Liljegren

Jon Skeet said:
You should be able to change the account itself - the first one
shouldn't be a problem, in other words (unless it's a value type?) -

You're right. This is in fact no problem.
but the second one is changing the list itself, which you can't do
without it throwing an exception. However, I'm surprised if you get a
*compiler* exception rather than a runtime exception.

Rigth again. I learned to separate them now.

So, the problem remaining is how to add another Account within that loop.
The logic is:

1. See if the account already exists in the array.
2. If it does, add a new amount to the old one. If not, create a new account
in the array.

I'll see if I can come up with a simple example to work on.

Gustaf
 
J

Jon Skeet [C# MVP]

So, the problem remaining is how to add another Account within that loop.
The logic is:

1. See if the account already exists in the array.
2. If it does, add a new amount to the old one. If not, create a new account
in the array.

I'll see if I can come up with a simple example to work on.

You can't change a list while you're iterating through it. One common
solution is to build up another list containing the items you *want* to
add to the list, and then add all of them after you've finished
iterating. (You could use ArrayList.AddRange for that.)
 
M

Michael S

Gustaf Liljegren said:
So, the problem remaining is how to add another Account within that loop.
The logic is:

1. See if the account already exists in the array.
2. If it does, add a new amount to the old one. If not, create a new account
in the array.

You could change the foreach-statement into a for-loop (going backwards).

Or even better. Not use an ArrayList, but a HashTable for the lookup.

Best Regards

- Michael S
 
G

Gustaf Liljegren

Jon Skeet said:
You can't change a list while you're iterating through it. One common
solution is to build up another list containing the items you *want* to
add to the list, and then add all of them after you've finished
iterating. (You could use ArrayList.AddRange for that.)

Thanks for all your help. I see that I have not given a good enough
description. It's a grouping problem. The input data is a list of accounts
(identified by name) where some accounts occurs more than once. I want the
accounts grouped so that the amounts are summed up. For example, this input
data

Account 1: 2.50
Account 2: 7.00
Account 1: 1.50
Account 3: 9.00
Account 2: 3.50
Account 2: 6.00
Account 3: 3.50
Account 1: 6.50

shall result in these groups (each in an Account object):

Account 1: 10.5
Account 2: 16.5
Account 3: 12.5

My way of doing this is looping through the input data, account by account.
If the account has already been found, I just add the amount from the
current account object. If not, I add the whole object to the array. But it
won't work, since I can't add items to the array while iterating through it.

Maybe I need an Accounts collection class with methods like AccountExists(),
AddAccount() and UpdateAmount().

Gustaf
 
J

Jon Skeet [C# MVP]

Gustaf Liljegren said:
Thanks for all your help. I see that I have not given a good enough
description. It's a grouping problem. The input data is a list of accounts
(identified by name) where some accounts occurs more than once. I want the
accounts grouped so that the amounts are summed up. For example, this input
data

Account 1: 2.50
Account 2: 7.00
Account 1: 1.50
Account 3: 9.00
Account 2: 3.50
Account 2: 6.00
Account 3: 3.50
Account 1: 6.50

shall result in these groups (each in an Account object):

Account 1: 10.5
Account 2: 16.5
Account 3: 12.5

My way of doing this is looping through the input data, account by account.
If the account has already been found, I just add the amount from the
current account object. If not, I add the whole object to the array. But it
won't work, since I can't add items to the array while iterating through it.

It sounds like you should actually have two different lists (or rather,
a list and a map) - one for the input, and one for the output.
Something like (untested code)

foreach (Account account in inputList)
{
if (outputMap.ContainsKey(account.Name))
{
((Account)outputMap[account.Name]).Amount += account.Amount;
}
else
{
Account copy = new Account (account.Name, account.Amount);
outputMap[copy.Name]=copy;
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top