How to avoid writing three loops in this example? - c#

In the below code example, how can I avoid writing 3 loops. I'm not sure how to keep the code readable and still optimize it. This is not the exact function I'm working on, but for brevity I updated it so it's for you guys to read.
int OrderQuantity = 5;
List<LineItem> Items = GetLineItems();
List<int> UniqueOrderIDs = new List<int>();
foreach (LineItem i in Items) {
if (UniqueOrderIDs.Contains(i.OrderID) == false) {
PurchaseOrder Order = GetOrder(i.OrderId);
Order.ModifiedDate = Now;
UpdateOrder(Order);
UniqueOrderIDs.Add(i.OrderID);
}
}
foreach (int id in UniqueOrderIDs) {
decimal TaxableAmount = 0;
foreach (LineItem i in Items) {
If(i.OrderID == id){
i.OrderQuantity = OrderQuantity;
UpdateItem(i)
TaxableAmount += i.Cost;
}
}
UpdateTaxAmount(id, TaxableAmount);
}

You can do, something like (I omit bounds checking here)
List<LineItem> items = GetLineItems(OrderID);
var sorted = items.OrderBy(i=>i.OrderID);
int id = sorted.First().OrderID; //get 1st element's id
foreach (LineItem i in sorted.Skip(1)) //skip 1st one
{
if(i.OrderID == id)
{
i.OrderQuantity = OrderQuantity;
UpdateItem(i)
TaxableAmount += i.Cost;
}
else
id = i.OrderID;
}
....
You sort LineItem list by OrderID, after simply iterate over it, and while it is the same, do whatever you need to do.
There will be someone that, probably, would opt for LINQ solution, but to my taste, this looks much simpler and clear then LINQ solution might be in this situation.

I am probably missing something (getting late here) but why can't you do like this:
var orderIdAndTaxableAmount = new Dictionary<int, decimal>();
foreach (LineItem i in Items) {
if (UniqueOrderIDs.Contains(i.OrderID) == false) {
PurchaseOrder Order = GetOrder(i.OrderId);
Order.ModifiedDate = Now;
UpdateOrder(Order);
UniqueOrderIDs.Add(i.OrderID);
}
i.OrderQuantity = OrderQuantity;
UpdateItem(i);
if (!orderIdAndTaxableAmount.ContainsKey(i.OrderId))
{
orderIdAndTaxableAmount.Add(i.OrderId, 0.0);
}
orderIdAndTaxableAmount[i.OrderId] += i.Cost;
}
foreach (var kvp in orderIdAndTaxableAmount)
{
UpdateTaxAmount(kvp.Key, kvp.Value);
}

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;
}

Array of string management

I have an array of string, I want to take all the string in an interval of this array until string does not contains something.
Something like:
string [] arrayReading = {
"e","x","a","takefromhere",
"keeptaking","keeptaking","dont'ttakefromhere","m","p","l","e"
};
I have tried:
List<string> result = null;
for (int i = 0; i < arrayReading.Length; i++)
{
if (arrayReading[i].Contains("takefromhere"))
{
result.Add(arrayReading[i]);
if (!arrayReading[i + 1].Contains("dont'ttakefromhere"))
{
result.Add(arrayReading[i + 1]);
if (!arrayReading[i + 2].Contains("dont'ttakefromhere"))
{
rescription.Add(arrayReading[i + 1]);
}
}
}
}
Seems working but it's not really dynamic as I want it, because maybe I need to take 20 values between "takefromhere" and "don'ttakefromhere".
When querying you can try Linq:
using System.Linq;
...
List<string> result = arrayReading
.SkipWhile(item => item != "takefromhere")
.TakeWhile(item => item != "dont'ttakefromhere")
.ToList();
Or if you want good old loop solution:
List<string> result = new List<string>();
bool taking = false;
foreach (string item in arrayReading) {
if (!taking)
taking = item == "takefromhere";
if (taking) {
if (item == "dont'ttakefromhere")
break;
result.Add(item);
}
}
Let's have a look:
Console.Write(string.Join("; ", result));
Outcome:
takefromhere; keeptaking; keeptaking

Comparing on one element of on object in a list?

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.

c# get a specific element of a HashSet

I want to get an element of a HashSet only if it contains a specific string in it. i tried
the code below, but i dont get anything... like no matching. but this cant happen cause the UnKnown counter is always 0.
if (!IsbnAuth.Contains(RecTitle))
{
Unknown++;
}
else
{
for (int i = 0; i < IsbnAuth.Count(); i++)
{
if (IsbnAuth.ElementAt(i).Contains(RecTitle))
{
System.Console.WriteLine(IsbnAuth.ElementAt(i));
//isbn = IsbnAuth.ElementAt(i).Substring(0, IsbnAuth.ElementAt(i).IndexOf("\t"));
isbn = IsbnAuth.ElementAt(i).Split(' ')[0];
break;
}
}
}
Any ideas? the problem is not at the RecTitle cause even if it was just a single char instead, the result would be the same.
IsbnAuth is the HashSet.
EDIT: IsbnAuth declaration
HashSet<String> IsbnAuth = new HashSet<String>();
foreach (String line in IsbnAuthors)
{
IsbnAuth.Add(line.Trim());
}
System.Console.WriteLine(IsbnAuth.Count);
This is the first problem:
if (!IsbnAuth.Contains(RecTitle))
{
Unknown++;
}
That checks whether the set contains the whole string, as a complete element. It sounds like it doesn't.
I suspect you really want:
bool found = false;
foreach (String element in IsbnAuth)
{
if (element.Contains(RecTitle))
{
isbn = element.Split(' ')[0];
found = true;
break;
}
}
if (!found)
{
Unknown++;
}
Or even better:
string isbn = IsbnAuth.Where(x => x.Contains(RecTitle))
.Select(x => x.Split(' ')[0])
.FirstOrDefault();
if (isbn == null)
{
Unknown++;
}
It's worth being aware that a HashSet is in a fundamentally unpredictable order - so if there are multiple matches here, you'll end up with an arbitrary result. Is that really what you want?
It seems to me that you are storing mulitple informations held in one string in your Hastable. I would do it in that way:
public class Info
{
public string ISBN { get; set; }
public string Title { get; set; }
}
later in code:
List<Info> isbnAuth = new List<Info>();
foreach (String line in IsbnAuthors)
{
isbnAuth.Add(new Info { ISDN = line.Split(' ')[0], Title = line.Split(' ')[1] });
}
You can search an item like this:
var itemFound = isbnAuth.FirstOrDefault(item => item.Title == RecTitle);
if (itemFound != null)
{
isbn = itemFound.ISBN;
}

how do I check if an entity is the first element of a foreach loop

Say I have a foreach loop.
I have to do something with the first object of the loop that I don't have to do with any of the other objects.
How do I check if the item that's currently in the loop is the first object.
I like the Linq way, but without the Skip(1), this way you can also use it for the last item in a list and your code remains clean imho :)
foreach(var item in items)
{
if (items.First()==item)
item.firstStuff();
else if (items.Last() == item)
item.lastStuff();
item.otherStuff();
}
There are several ways that you could do that.
Use a for loop instead
Set a Boolean flag
Use Linq to get the list.First() and then foreach over list.Skip(1)
Something like this:
bool first = true;
foreach(var item in items)
{
if (first)
{
item.firstStuff();
first = false;
}
item.otherStuff();
}
Here's a performant solution:
using (var erator = enumerable.GetEnumerator())
{
if (erator.MoveNext())
{
DoActionOnFirst(erator.Current);
while (erator.MoveNext())
DoActionOnOther(erator.Current);
}
}
EDIT: And here's a LINQ one:
if (enumerable.Any())
{
DoActionOnFirst(enumerable.First());
foreach (var item in enumerable.Skip(1))
DoActionOnOther(item);
}
EDIT: If the actions on the items have signatures assignable to Func<TItem, TResult>, you can do:
enumerable.Select((item, index) => index == 0 ? GetResultFromFirstItem(item) : GetResultFromOtherItem(item));
bool first = true;
foreach(var foo in bar)
{
if (first)
{
// do something to your first item
first = false;
}
// do something else to the rest
}
In my opinion this is the simplest way
foreach (var item in list)
{
if((list.IndexOf(item) == 0)
{
// first
}
// others
}
try this one
bool IsFirst = true;
foreach(DataRow dr in dt.Rows)
{
if (IsFirst)
{
// do some thing
IsFirst = false;
}
}
Can't think of anything but
var processedFirst = false;
foreach(var x in items) {
if(!processedFirst) {
ProcessFirst(x);
processedFirst = true;
}
This is more of a general solution for getting index along with each object in an array. Should work testing if it's the first.
List<String> entries = new List<string>();
entries.Add("zero");
entries.Add("one");
entries.Add("two");
Dictionary<int, String> numberedEntries = new Dictionary<int, string>();
int i = 0;
entries.ForEach(x => numberedEntries.Add(i++, x));
foreach (KeyValuePair<int, String> pair in numberedEntries) {
Console.WriteLine(pair.Key + ": " + pair.Value);
}
In this setup, the Key of the KeyValuePair is the index and the value is the object at that index, in my example a string, but any object could be placed there. It adds a little overhead, but can be used to determine any object in the list's index when needed.
You can also write:
foreach ((T item, bool isFirst) in items.Select((item, index) => (item, index == 0)))
{
if (isFirst)
{
...
}
else
{
...
}
}
If you need this a lot, you can write an extension method to replace the Select and make your code shorter.

Categories

Resources