Comparing on one element of on object in a list? - c#

I have a custom class containing 2 public variables: 1 is a string and 1 is an integer. I then make a list of this class, in the list I need the string of the class to be unique, if the string already exists in the list I don't want to add it again but I do want to combine the corresponding integers. here is an example of the custom class and list.
public class myItems
{
public string itemName;
public int count;
}
List<myItems> items = new List<myItems>();
myItems e = new myItems();
e.symbol = "pencil";
e.count = 3;
items.Add(e);
myItems e1 = new myItems();
e1.symbol = "eraser";
e1.count = 4;
items.Add(e1);
myItems e2 = new myItems();
e1.symbol = "pencil";
e1.count = 3;
items.Add(e5);
So for the final list i want to it contain: pencil 7, eraser 4. I have been using the contains function on the list to check if it already exists but it only returns true if both the string and integer are the same.
Is there a way to only match on the string?

Another way to do it would be to use LINQ:
public class myItems
{
public string itemName;
public int count;
}
List<myItems> items = new List<myItems>();
myItems e = new myItems();
e.symbol = "pencil";
e.count = 3;
Add(items, e);
myItems e1 = new myItems();
e1.symbol = "eraser";
e1.count = 4;
Add(items, e1);
myItems e2 = new myItems();
e1.symbol = "pencil";
e1.count = 3;
Add(items, e5);
public void Add(List<myItems> list, myItems newItem)
{
var item = list.SingleOrDefault(x => x.symbol == newItem.symbol);
if(item != null)
{
item.count += newItem.count;
}
else
{
list.Add(newItem);
}
}

A dictionary might be well suited for this problem:
readonly Dictionary<string, int> _dict = new Dictionary<string, int>();
void InsertOrUpdate(string name, int count)
{
int previousCount = 0;
// item already in dictionary?
if (_dict.TryGetValue(name, out previousCount))
{
// add to count
count += previousCount;
}
_dict[name] = count;
}
void Main()
{
InsertOrUpdate("pencil", 3);
InsertOrUpdate("eraser", 3);
InsertOrUpdate("pencil", 4);
// print them
foreach (var item in _dict)
Console.WriteLine(item.Key + " " + item.Value);
}

You could add an Equals method to your class, or use LINQ with something like
items.Where(i => i.itemName == "pencil")
However, if all you are doing is keeping track of how many 'items' you have, would a Dictionary that maps itemNames to counts solve your problem easier? Then you would be able to do things like
// Assuming you want to add a new 'pencil' with a count of 3
int oldCount = 0;
items.TryGetValue("pencil", out oldCount);
items["pencil"] = oldCount + 3;
Usually see something like this called a Bag

Sure, write a custom Equals method
public override bool Equals(object o)
{
MyItems mi = o as MyItems;
if (mi == null)
return false;
if (itemName == null)
return mi.itemName == null;
return itemName.Equals(mi.itemName);
}
public override int HashCode()
{
return (itemName ?? string.Empty).HashCode();
}
That being said, you really should be using a dictionary/hash table instead, since a dictionary provides much faster lookup when you know what you want. A List implementation will cause the list to be searched in its entirety every time you want to add a MyItem to the list.

when you check if it contains and it returs true than you get the index and add the number to it. use that logic. it will work.

Related

Performing operations on a List<> created from ICollection objects - ICollection objects are being changed

I'm trying to create a string with parts and quantities made from data contained in an ICollection. I'm using a List to build the totals I need but when I perform operations on this List it's actually changing the values in the ICollection. I don't want those values changed. Code follows. PartsUsed is the ICollection. Is this because adding individual members of the collection to the list is only pointing to the original data?
private string PartsDetails(out int totalCount, String modtype)
{
totalCount = 0;
var str = new StringBuilder();
var usedParts = new List<PartUsed>();
var indexnum = 0;
foreach (var u in Rma.AssociatedUnits)
{
if (u.PartsUsed != null && u.PartsUsed.Count > 0)
{
if ((modtype == "ALL"))
{
foreach (var rep in u.PartsUsed)
{
if (!usedParts.Exists(x => x.Repair.Name == rep.Repair.Name))
{
usedParts.Add(rep);
}
else
{
usedParts[usedParts.FindIndex(f => f.Repair.Name == rep.Repair.Name)].RepairPartQuantity += rep.RepairPartQuantity;
}
}
}
}
}
foreach (var partsGroup in usedParts)
{
str.AppendFormat(str.Length > 0 ? Environment.NewLine + "{0} - {1}" : "{0} - {1}", partsGroup.RepairPartQuantity, partsGroup.Repair.Name);
totalCount += partsGroup.RepairPartQuantity;
}
return str.ToString();
}
It seems that your PartUsed is a class(e.g. reference type), so u.PartsUsed is actually a collection of references to some objects, so usedParts.Add(rep) is actually adding the same reference(object) to usedParts and when you get and modify one of them in usedParts[usedParts.FindIndex(f => f.Repair.Name == rep.Repair.Name)].RepairPartQuantity += rep.RepairPartQuantity you are actually modifying shared instance. This behavior can be demonstrated like this also:
class MyClass { public int Prop { get; set; } }
var ref1 = new MyClass{Prop = 1};
var ref2 = ref1;
ref2.Prop = 2;
Console.WriteLine(ref2.Prop);// prints 2
One way around would be to create a clone of PartUsed to put into usedParts but in your particular case it seems that you can use Dictionary<string, int>(assuming RepairPartQuantity is int) for usedParts. Something like this:
var usedParts = new Dictionary<string, int>();
.....
foreach (var rep in u.PartsUsed)
{
if (!usedParts.ContainsKey(rep.Repair.Name))
{
usedParts[rep.Repair.Name] = rep.RepairPartQuantity;
}
else
{
usedParts[rep.Repair.Name] += rep.RepairPartQuantity;
}
}
foreach (var kvp in usedParts)
{
str.AppendFormat(str.Length > 0 ? Environment.NewLine + "{0} - {1}" : "{0} - {1}", kvp.Value, kvp.Key);
totalCount += kvp.Value;
}

Return false if type properties equals null or 0

I tried following the method as follows here: Checking if Object has null in every property . However, when instantiating Order newOrder = new Order();. I cannot simple just implement bool props = newOrder.ArePropertiesNotNull(). What am I supposed to add to my Order class? And where do I implement the function for ArePropertiesNotNull<T>(this T obj)? I would like to know if there is a way to return false if value returned equals 0 or null?
Here is my code:
OrderProdRepository.cs
...
public bool ReadFromFile(string _date)
{
taxesFile.ReadFile();
productsFile.ReadFile();
string orderFileName = $"C:\\tempfolder\\Orders_{_date}.txt";
List<string> lines = File.ReadAllLines(orderFileName).ToList();
foreach (var line in lines.Skip(1)) //?? new List<string>(0)
{
List<string> entry = line.Split(',').ToList();
Order newOrder = new Order();
int.TryParse(entry[0], out int orderNumber);
newOrder.OrderNumber = orderNumber;
newOrder.Date = _date;
newOrder.CustomerName = entry[1];
newOrder.State = taxesFile.StateAbbreviation(entry[2]);
newOrder.StateName = taxesFile.StateName(newOrder.State);
decimal.TryParse(entry[3], out decimal taxRate);
newOrder.TaxRate = taxesFile.TaxRate(taxRate);
newOrder.ProductType = productsFile.ProductType(entry[4]);
decimal.TryParse(entry[5], out decimal area);
newOrder.Area = area;
decimal.TryParse(entry[6], out decimal costPerSquareFoot);
newOrder.CostPerSquareFoot = productsFile.CostPerSquareFoot(costPerSquareFoot);
decimal.TryParse(entry[7], out decimal laborCostPerSquareFoot);
newOrder.LaborCostPerSquareFoot = productsFile.LaborCostPerSquareFoot(laborCostPerSquareFoot);
decimal.TryParse(entry[8], out decimal materialCost);
newOrder.MaterialCost = materialCost;
decimal.TryParse(entry[9], out decimal laborCost);
newOrder.LaborCost = laborCost;
decimal.TryParse(entry[10], out decimal tax);
newOrder.Tax = tax;
decimal.TryParse(entry[11], out decimal total);
newOrder.Total = total;
orderList.Add(newOrder);
}
return true;
}
...
I think you need a function to check each line for null and/or 0 values:
private bool IsValidLine(string line)
{
if (line == null)
return false;
var arr = line.Split(',');
//Uncomment this if splitting the line will always return 11 items array.
//if (arr.Length < 11)
// return false;
return arr.Aggregate(0, (n, s) =>
(decimal.TryParse(s, out decimal d) && d == 0) ||
string.IsNullOrWhiteSpace(s) ? n + 1 : n) == 0;
}
You can use it in your code as follows:
public bool ReadFromFile(string _date)
{
var orderFileName = $"C:\\tempfolder\\Orders_{_date}.txt";
var lines = File.ReadAllLines(orderFileName);
foreach (var line in lines.Skip(1))
{
//If parsing any line returns false.
if (!IsValidLine(line))
return false;
//Or if you need to create a list of the valid Order entries.
if (IsValidLine(line))
{
var order = new Order();
//...
orderList.Add(newOrder);
}
}
return true;
}
Alternatives:
Add a static function in the Order class to parse a given line and return a new object of Order type if the line is valid. Something like this.
If its not too late, then consider using a local database or serialization. Something like this and maybe this if you don't mind a vb.net example.
You need to create this method an extension method. It should be defined in static class:
public static class ObjectExtensions
{
public static bool ArePropertiesNotNull<T>(this T obj)
{
return typeof(T).GetProperties().All(propertyInfo => propertyInfo.GetValue(obj) != null);
}
}

C# reference array assignment issue

I'm having a little trouble reading values in from a database and assigning them to an array. It seem to work in my unit tests, but in practice some values are missing.
Here's my database code:
private void GetParameterValuesFromDatabase()
{
this.parameterValues = (from DataRow r in this.database.RunCommand("select * from KST_PARAM_VALUES v join DM_PARM_NAME p on v.PARM_NAME_KEY = p.PARM_NAME_KEY").Rows
where (int)r["SCENARIO_KEY"] == this.scenario.ScenarioKey
select new DatabaseParameter
{
ParameterValuesKey = r.Field<int>(0),
ProfileType = r.Field<string>(1),
ScenarioKey = r.Field<int>(2),
StressEditorKey = r.Field<int>(3),
StressClassKey = r.Field<int>(4),
PeriodKey = r.Field<int>(5),
ParameterNameKey = r.Field<int>(6),
ParameterValue = r.Field<double>(7),
ActiveStress = (r.Field<string>(8) == "Y") ? true : false,
ParameterKey = (int)r["PARM_NUMBER"]
}).ToDictionary(r => r.ParameterValuesKey, r => r);
}
Not having any issues with this part of my code, just showing for completeness.
private void LoadParameters()
{
this.GetParameterValuesFromDatabase();
// TODO: Assuming 9 periods for now, change to allow for variable periods
for (int i = 1; i <= MaxNumberOfStressPeriods; i++)
{
this.parametersByPeriod.Add(i, this.parameterValues.Where(t => t.Value.PeriodKey == i).ToDictionary(t => t.Key, t => t.Value));
}
Log.Instance.LogMessage(LogLevel.Debug, "Created parameter dictionaries from database");
// For every stress editor in the dictionary of stress editors
foreach (KeyValuePair<int, ClassList> ed in this.stressParams)
{
// For every type of class selector
foreach (ClassSelector c in Enum.GetValues(typeof(ClassSelector)))
{
// For each of the classes within each class list within the editor
for (int i = 0; i < ed.Value.ClassLists[c].Count; i++)
{
string className = ed.Value.ClassLists[c][i].Name;
// For each double array in each class
foreach (KeyValuePair<int, double[]> t in ed.Value.ClassLists[c][i].ClassVariables.EditorParameters)
{
double[] values = this.GetParameterValues(t.Key, ed.Key, className);
BasicStressEditorVariables.AddParameters(values, ed.Value, className, t.Key);
}
}
}
}
}
}
Above shows the overall LoadParameters() method.
Below we have some code that selects 9 values from the dictionary constructed from the database, ready to be added to the array.
private double[] GetParameterValues(int paramKey, int editorKey, string className)
{
double[] values = new double[9];
for (int i = 1; i <= MaxNumberOfStressPeriods; i++)
{
Dictionary<int, DatabaseParameter> temp = this.parametersByPeriod[i];
foreach (KeyValuePair<int, DatabaseParameter> d in temp)
{
if (d.Value.ParameterKey == paramKey && d.Value.PeriodKey == i && d.Value.StressEditorKey == editorKey && d.Value.ProfileType == className)
{
values[i - 1] = d.Value.ParameterValue;
}
}
}
return values;
}
Below shows getting the destination array from the dictionary, as indexes cannot be passed by reference
public static void AddParameters(double[] values, ClassList editor, string className, int paramKey)
{
// TODO: Maybe search all lists to eliminate the need for the class selector as a parameter
// TODO: Will throw an exception when nothing is found. Handle it
ParameterClass p = null;
foreach (ClassSelector c in Enum.GetValues(typeof(ClassSelector)))
{
p = editor.ClassLists[c].FirstOrDefault(f => f.Name == className);
if (p != null)
{
break;
}
}
// TODO: Notify that could not be found
if (p == null)
{
Log.Instance.LogMessage(LogLevel.Error, $"Unable to find class {className}");
return;
}
double[] dest = p.ClassVariables.editorParameters[paramKey];
AddParameterValues(values, ref dest);
}
And here's the AddParameterValues() method:
private static void AddParameterValues(double[] values, ref double[] destination)
{
if (values.Length != destination.Length)
{
return;
}
for (int i = 0; i < values.Length; i++)
{
destination[i] = values[i];
}
}
Debugging shows that some values are being loaded into the destination array, but some aren't. Could anyone tell me why this is? Or if not, point me toward some material?
Thank you for your time
I'm not that C# specialist but looking to following code as a C programmer
private double[] GetParameterValues(int paramKey, int editorKey, string className)
{
double[] values = new double[9];
//...
return values;
}
I would assume that the lifetime of values is only within the function GetParameterValues and the function GetParameterValues delivers the caller with reference to a dead variable.
What if you change the prototype to something like
private void GetParameterValues(ref double[] values, int paramKey, int editorKey, string className)

List<T>.addrange() not working

I have property :
public List<RequestCheckListDetail> DocumentChecklistMasterList
{
get
{
if (ViewState["DocumentChecklistMasterList"].IsObjectUsable())
_documentChecklistMasterList = (List<RequestCheckListDetail>)ViewState["DocumentChecklistMasterList"];
else
this._documentChecklistMasterList = new List<RequestCheckListDetail>();
return this._documentChecklistMasterList;
}
set { ViewState["DocumentChecklistMasterList"] = value; }
}
I am trying to add data to it using another list. However another list has different entity, so i am running loop over first list like:
List<RequestCheckListDetail> newList = new List<RequestCheckListDetail>();
int i = 0;
foreach (DocumentCheckListMaster item in list)
{
newList.Add(new RequestCheckListDetail
{
Id = i,
CheckListMaster = item
});
i++;
}
this.DocumentChecklistMasterList.AddRange(newList);
even if newList has items in it, DocumentChecklistMasterList always have 0 items.
I have tried following things:
List<RequestCheckListDetail> newList = new List<RequestCheckListDetail>();
int i = 0;
foreach (DocumentCheckListMaster item in list)
{
this.DocumentChecklistMasterList.Add(new RequestCheckListDetail
{
Id = i,
CheckListMaster = item
});
i++;
}
List<RequestCheckListDetail> newList = new List<RequestCheckListDetail>();
int i = 0;
foreach (DocumentCheckListMaster item in list)
{
this.DocumentChecklistMasterList.Insert(i,
new RequestCheckListDetail {
Id = i,
CheckListMaster = item
});
i++;
}
None of these codes are working properly.
I am still not able to add items to DocumentChecklistMasterList
Please help me:
EDIT:
IsObjectUsable() is extension method i have added to check if object is null
public static bool IsObjectUsable(this object checkObject)
{
bool isUsable = true;
if (checkObject == null || checkObject == DBNull.Value)
{
isUsable = false;
}
return isUsable;
}

Add list of objects to List<List<Objects>> if not contain

I have List<List<Vertex>> , Vertex has a property id. I need to add List<Vertex>> into this list, but not duplicate lists.
public void AddComponent(List<Vertex> list)
{
List<List<Vertex>> components = new List<List<Vertex>>;
//I need something like
if (!components.Contain(list)) components.Add(list);
}
You can use SequenceEqual - (this means the order must also be the same):
if (!components.Any(l => l.SequenceEqual(list)))
components.Add(list);
You could do something like:
public void AddComponent(List<Vertex> list)
{
var isInList = components.Any(componentList =>
{
// Check for equality
if (componentList.Count != list.Count)
return false;
for (var i = 0; i < componentList.Count; i++) {
if (componentList[i] != list[i])
return false;
}
return true;
});
if (!isInList)
components.Add(list);
}

Categories

Resources