Using ForEach method of Generic List Object.

D

Doug

I have another question that has to deal with the methods that the
Generic List object provides that require delegates but the delegates
do not allow you to pass in parameters. I'll explain first and then
try to provide an example.

I have an object that I put into the generic list, with a set of data
like below. I need to loop through this data and identify duplicates
(these have been grouped together because they are at a detail level
and at the header level they've been identified as "potential"
duplicates.) If the Destination, Origin, Mileage and Rate match for
a
given LocationRateId and LocationRateSequence to at least one other
given LocationRateId and LocationRateSequence then it's a duplicate.
But if for another LocationRateSequence for that same LocationRateId,
there is no match to the LocationRateId and LocationRateSequence that
was matched earlier, then both rows are not considered duplicates and
both are scrapped (yes, I know this sounds confusing).


LocationRateId LocationRateSequence
Destination Origin
Mileage Rate
12345
1
678 901
100 5.50
12345
2
901 678
100 5.50
67890
1
123 456
95 6.00
67890
2
456 123
95 6.00
99990
1
123 456
95 6.00
99990
2
456 123
95 6.00
55555
1
678 901
100 5.50
55555
2
901 678
100 5.50
88888
1
678 901
100 5.50
88888
2
888 777
150 9.50


If you were to look at this data, LocationRateId 12345 is a duplicate
of 55555 and LocationRateId 67890 is a duplicate of 99990.
LocationRateId 88888 is not a match, because even though
LocationRateSequence 1 for that LocationRateId matches LocationRateId
12345 for it's LocationRateSequence 1, the two don't match for
LocationRateSequence of 2.


Ok, I would like to use the ForEach method in the GenericList object
to do this comparison. But I can't figure out how, because it takes
a
delegate that takes no parameters. In order to do this, I would need
to have the overall Generic List passed into the method as well, to
compare with the one object that I'm looking at.


So, here's what I'm doing now, but I'm assuming this is not the best
option performance wise (it sure seems slow)...


Note, I'm including the code for my object, but am not including the
code for getting it filled and put into a generic list...


public class LocationRateDetail
{


private bool _markedForDeletion = false;
private decimal _legCost = decimal.MinValue;
private int _distance = int.MinValue;
private int _legSequenceNumber = int.MinValue;
private int _locationRatesId = int.MinValue;
private string _destinationLocationId = string.Empty;
private string _originLocationId = string.Empty;


public LocationRateDetail()
{ }


public LocationRateDetail(decimal legCost, int distance, int
legSequenceNumber, int locationRatesId, string destinationLocationId,
string originCustomerLocationId,
string originLocationId)
{
_legCost = legCost;
_distance = distance;
_legSequenceNumber = legSequenceNumber;
_locationRatesId = locationRatesId;
_destinationLocationId = destinationLocationId;
_originLocationId = originLocationId;
}


public bool MarkedForDeletion
{
get
{
return _markedForDeletion;
}
set
{
_markedForDeletion = value;
}
}
public decimal LegCost
{
get
{
return _legCost;
}
}
public int Distance
{
get
{
return _distance;
}
}
public int LegSequenceNumber
{
get
{
return _legSequenceNumber;
}
}
public int LocationRatesId
{
get
{
return _locationRatesId;
}
}
public string DestinationLocationId
{
get
{
return _destinationLocationId;
}
}
public string OriginLocationId
{
get
{
return _originLocationId;
}
}



}


Once my generic list has been filled with data, this is the method I
use to try to filter out not duplicates.

public static List<LocationRateDetail>
FilterOutNonDuplicates(List<LocationRateDetail> details)
{
bool keepChecking = true;
int numberDeleted = 0;
LocationRateDetail currentDetail = null;
string locationRatesToDelete = string.Empty;
while (keepChecking == true)
{
numberDeleted = 0;
for (int index = 0; index < details.Count; index++)
{
currentDetail = details[index];
if (currentDetail.MarkedForDeletion == false)
{
if
(locationRatesToDelete.IndexOf(currentDetail.LocationRatesId + ",")
0)
{
currentDetail.MarkedForDeletion = true;
}
else
{
if (CheckForDuplicate(currentDetail, details)
== false)
{
currentDetail.MarkedForDeletion = true;
locationRatesToDelete +=
currentDetail.LocationRatesId + ",";
numberDeleted++;
}
}
}
}


if ((numberDeleted == 0) ||
(details.Find(FindUnmarkedRate) == null))
{
keepChecking = false;
}


}


details.RemoveAll(FindMarkedRate);


return details;



}


private static bool FindUnmarkedRate(LocationRateDetail detail)
{
return !detail.MarkedForDeletion;


}


private static bool FindMarkedRate(LocationRateDetail detail)
{
return detail.MarkedForDeletion;


}


private static bool CheckForDuplicate(LocationRateDetail
currentDetail, List<LocationRateDetail> details)
{
bool foundDuplicate = false;
bool stopChecking = true;
LocationRateDetail detail = null;

for (int index = 0; index < details.Count; index++)
{
detail = details[index];
//Don't check any further if this is the same detail as the
current detail.
stopChecking = detail.Equals(currentDetail);
if ((stopChecking == false) &&
(CompareProperties(currentDetail, detail)))
{
foundDuplicate = true;
break;
}
}


return foundDuplicate;



}


private static bool CompareProperties(LocationRateDetail
currentDetail, LocationRateDetail detailToCheck)
{
bool propertiesMatch = false;

if ((currentDetail.LegSequenceNumber ==
detailToCheck.LegSequenceNumber) &&
(currentDetail.OriginLocationId.Trim() ==
detailToCheck.OriginLocationId.Trim()) &&
(currentDetail.DestinationLocationId.Trim() ==
detailToCheck.DestinationLocationId.Trim()) &&
(currentDetail.Distance == detailToCheck.Distance) &&
(currentDetail.LegCost == detailToCheck.LegCost))
{
propertiesMatch = true;
}


return propertiesMatch;
 
N

Nicholas Paldino [.NET/C# MVP]

Doug,

I would change the FilterOutNonDuplicates method in the following ways:

First, create a class that implements the IEqualityComparer<T>
interface. In this case, you are implementing
IEqualityComparer<LocationRateDetail>. Implement the Equals and GetHashCode
methods to compare the two items that are passed to each method. Make sure
that for a method where Equals returns true, the call to GetHashCode should
produce the same hash code.

Then, in your FilterOutNonDuplicates method, create a
Dictionary<LocationRateDetail, bool> instance, passing the implementation of
IEqualityComparer<LocationRateDetail> to the constructor. Then cycle
through the elements in your list. Call the ContainsKey method on each
element. If the element does not exist in the dictionary, then add it to
your return value and insert the item into the dictionary, with the key
being the LocationRateDetail instance, and the value being whatever you wish
(the value isn't important here, just the existence of the key). If the
value already exists as a key in the dictionary, then do nothing.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Doug said:
I have another question that has to deal with the methods that the
Generic List object provides that require delegates but the delegates
do not allow you to pass in parameters. I'll explain first and then
try to provide an example.

I have an object that I put into the generic list, with a set of data
like below. I need to loop through this data and identify duplicates
(these have been grouped together because they are at a detail level
and at the header level they've been identified as "potential"
duplicates.) If the Destination, Origin, Mileage and Rate match for
a
given LocationRateId and LocationRateSequence to at least one other
given LocationRateId and LocationRateSequence then it's a duplicate.
But if for another LocationRateSequence for that same LocationRateId,
there is no match to the LocationRateId and LocationRateSequence that
was matched earlier, then both rows are not considered duplicates and
both are scrapped (yes, I know this sounds confusing).


LocationRateId LocationRateSequence
Destination Origin
Mileage Rate
12345
1
678 901
100 5.50
12345
2
901 678
100 5.50
67890
1
123 456
95 6.00
67890
2
456 123
95 6.00
99990
1
123 456
95 6.00
99990
2
456 123
95 6.00
55555
1
678 901
100 5.50
55555
2
901 678
100 5.50
88888
1
678 901
100 5.50
88888
2
888 777
150 9.50


If you were to look at this data, LocationRateId 12345 is a duplicate
of 55555 and LocationRateId 67890 is a duplicate of 99990.
LocationRateId 88888 is not a match, because even though
LocationRateSequence 1 for that LocationRateId matches LocationRateId
12345 for it's LocationRateSequence 1, the two don't match for
LocationRateSequence of 2.


Ok, I would like to use the ForEach method in the GenericList object
to do this comparison. But I can't figure out how, because it takes
a
delegate that takes no parameters. In order to do this, I would need
to have the overall Generic List passed into the method as well, to
compare with the one object that I'm looking at.


So, here's what I'm doing now, but I'm assuming this is not the best
option performance wise (it sure seems slow)...


Note, I'm including the code for my object, but am not including the
code for getting it filled and put into a generic list...


public class LocationRateDetail
{


private bool _markedForDeletion = false;
private decimal _legCost = decimal.MinValue;
private int _distance = int.MinValue;
private int _legSequenceNumber = int.MinValue;
private int _locationRatesId = int.MinValue;
private string _destinationLocationId = string.Empty;
private string _originLocationId = string.Empty;


public LocationRateDetail()
{ }


public LocationRateDetail(decimal legCost, int distance, int
legSequenceNumber, int locationRatesId, string destinationLocationId,
string originCustomerLocationId,
string originLocationId)
{
_legCost = legCost;
_distance = distance;
_legSequenceNumber = legSequenceNumber;
_locationRatesId = locationRatesId;
_destinationLocationId = destinationLocationId;
_originLocationId = originLocationId;
}


public bool MarkedForDeletion
{
get
{
return _markedForDeletion;
}
set
{
_markedForDeletion = value;
}
}
public decimal LegCost
{
get
{
return _legCost;
}
}
public int Distance
{
get
{
return _distance;
}
}
public int LegSequenceNumber
{
get
{
return _legSequenceNumber;
}
}
public int LocationRatesId
{
get
{
return _locationRatesId;
}
}
public string DestinationLocationId
{
get
{
return _destinationLocationId;
}
}
public string OriginLocationId
{
get
{
return _originLocationId;
}
}



}


Once my generic list has been filled with data, this is the method I
use to try to filter out not duplicates.

public static List<LocationRateDetail>
FilterOutNonDuplicates(List<LocationRateDetail> details)
{
bool keepChecking = true;
int numberDeleted = 0;
LocationRateDetail currentDetail = null;
string locationRatesToDelete = string.Empty;
while (keepChecking == true)
{
numberDeleted = 0;
for (int index = 0; index < details.Count; index++)
{
currentDetail = details[index];
if (currentDetail.MarkedForDeletion == false)
{
if
(locationRatesToDelete.IndexOf(currentDetail.LocationRatesId + ",")
0)
{
currentDetail.MarkedForDeletion = true;
}
else
{
if (CheckForDuplicate(currentDetail, details)
== false)
{
currentDetail.MarkedForDeletion = true;
locationRatesToDelete +=
currentDetail.LocationRatesId + ",";
numberDeleted++;
}
}
}
}


if ((numberDeleted == 0) ||
(details.Find(FindUnmarkedRate) == null))
{
keepChecking = false;
}


}


details.RemoveAll(FindMarkedRate);


return details;



}


private static bool FindUnmarkedRate(LocationRateDetail detail)
{
return !detail.MarkedForDeletion;


}


private static bool FindMarkedRate(LocationRateDetail detail)
{
return detail.MarkedForDeletion;


}


private static bool CheckForDuplicate(LocationRateDetail
currentDetail, List<LocationRateDetail> details)
{
bool foundDuplicate = false;
bool stopChecking = true;
LocationRateDetail detail = null;

for (int index = 0; index < details.Count; index++)
{
detail = details[index];
//Don't check any further if this is the same detail as the
current detail.
stopChecking = detail.Equals(currentDetail);
if ((stopChecking == false) &&
(CompareProperties(currentDetail, detail)))
{
foundDuplicate = true;
break;
}
}


return foundDuplicate;



}


private static bool CompareProperties(LocationRateDetail
currentDetail, LocationRateDetail detailToCheck)
{
bool propertiesMatch = false;

if ((currentDetail.LegSequenceNumber ==
detailToCheck.LegSequenceNumber) &&
(currentDetail.OriginLocationId.Trim() ==
detailToCheck.OriginLocationId.Trim()) &&
(currentDetail.DestinationLocationId.Trim() ==
detailToCheck.DestinationLocationId.Trim()) &&
(currentDetail.Distance == detailToCheck.Distance) &&
(currentDetail.LegCost == detailToCheck.LegCost))
{
propertiesMatch = true;
}


return propertiesMatch;
 

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