I'm currently working on an application in C#. Imagine a store front with a checkout. I have a dictionary structure with an object as Key, and an int object counter as value.
the structure looks like this:
Dictionary<myObject, int> items.
The basic idea is, to pass a dictionary of Items into a method. I'm only adding unique myObjects to the dictionary. The myObject has a counter rule attached. Once the counter rule is full filled I want to do a calculation with all myObects in the dictionary.
The myObject looks like this:
public class myObject
{
string ItemId { get; set; }
Discount Discount { get; set; }
}
public class Discount
{
public int Count { get; set; }
public decimal Price { get; set; }
public IDiscountHandler DiscountHandler => new DiscountHandler();
}
A sample myObject could look like this:
var myObectA = new myObject()
{
ItemId = "A"
};
var discountA = new Discount()
{
Count = 2,
Price = 12 // special price, if 2 myObjects were added to the Dictionary
};
myObjectA.Discount = discountA;
1) I fill the items Dictionary and pass it to the Handler method:
private decimal _totalDiscountedValue { get; set; } = 0;
if (!_items.ContainsKey(myObject))
{
_items.Add(myObject, 1);
}
else
{
_items[myObject]++;
}
_totalDiscountedValue += _discountHandler.CalculateDiscount(_items);
2) In my Handler I try to sum up all the discount values, once a counter rule is full filled. But here I'm struggling unfortunately:
public class DiscountHandler : DiscountHandler
{
private decimal _totalDiscount { get; set; } = 0;
public override decimal CalculateDiscount(IDictionary<myObject, int> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
// I'm struggeling here:
// check if Dictionary[i].Dicount.Count = Dictionary.Value
// then _totalDiscount += Dictionary[i].Discount.Price
return _totalDiscount;
}
}
Do you know how to solve this issue, or do you have an idea on how to possibly solve this?
Thank you very much !!
You could just iterate through the Dictionary using foreach as follows:
public override decimal CalculateDiscount(IDictionary<myObject, int> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
foreach (var kvp in items)
{
if (kvp.Key.Discount.Count == kvp.Value)
_totalDiscount += kvp.Key.Discount.Price;
}
return _totalDiscount;
}
Using Linq
//check if yourDictonary is not null
var sum = yourDictonary.Select(x => x.Key.Discount.Count == x.Value).Sum(x => x.Value)
IF I understood the problem properly, maybe doing this would work
foreach (var item in items)
{
if (item.Key.Discount.Count == item.Value)
_totalDiscount += item.Key.Discount.Price;
}
return __totalDiscount;
Related
I have this entities
public class Counter
{
public int DocEntry { get; set; }
public int LineId { get; set; }
public string Item { get; set; }
public decimal Quantity { get; set; }
public string FromWarehouse { get; set; }
public string ToWarehouse { get; set; }
public virtual List<Batch> batchs { get; set; }
}
public class Batch
{
public string BatchNumber { get; set; }
public decimal Quantity { get; set; }
}
And I have List count,I should group the elements of the list based on the Item value, FromWarehouse, ToWarehouse, and the result should be grouped with summed quantity and the list of Batch merged, I tried cycling the Counter list using the foreach method and inserting in a new list the first element, in the subsequent iterations if the current row reflected the values of the one already in the list I summarized the quantities and added to the Batch list the elements of the Batch list of the i-th line, otherwise I added a new line, this approach I it seemed too complicated, not being very expert on linq or in any case is there an easier way to manage the group by?
This is my method:
public static List<Counter> GroupBy(List<Counter> list)
{
List<Counter> rows = new List<Counter>();
int i = 0;
foreach (Counter elem in list)
{
if (list.First() == elem)
{
rows.Add(elem);
i++;
}
else
{
if (elem.Item == rows.ElementAt(i).Item &&
elem.FromWarehouse == rows.ElementAt(i).FromWarehouse &&
elem.ToWarehouse == rows.ElementAt(i).ToWarehouse)
{
rows.First().Quantity += elem.Quantity;
rows.First().batchs.Add(elem.batchs.First());
}
else
{
rows.Add(elem);
i++;
}
}
}
return rows;
}
This is the solution:
public static List<Counter> GroupBy(List<Counter> list)
{
List<Counter> rows = new List<Counter>();
foreach (Counter elem in list)
{
if (rows.Any(x=>x.Item == elem.Item &&
x.FromWarehouse == elem.FromWarehouse &&
x.ToWarehouse == elem.ToWarehouse))
{
rows.First().Quantity += elem.Quantity;
rows.First().batchs.Add(elem.batchs.First());
}
else
{
rows.Add(elem);
}
}
return rows;
}
Let's try to write the Linq, step by step.
"I have List count":
List<Counter> count = ...
"I should group the elements of the list based on the Item value, FromWarehouse, ToWarehouse":
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
"result should be grouped with summed quantity and the list of Batch merged", i.e. you should Aggregate items within each chunk
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
.Select(chunk => new {
key = chunk.Key,
summary = chunk.Aggregate(
Tuple.Create(0m, new List<Batch>()), // initial empty
(s, a) => Tuple.Create(s.Item1 + a.Quantity, // add
s.Item2.Concat(a.batchs).ToList()) // merge
})
Finally, let's represent the result in more convenient (readable) format:
var result = count
.GroupBy(item => new {
item.Item,
item.FromWarehouse
item.ToWarehouse
})
.Select(chunk => new {
key = chunk.Key,
summary = chunk.Aggregate(
Tuple.Create(0m, new List<Batch>()),
(s, a) => Tuple.Create(s.Item1 + a.Quantity,
s.Item2.Concat(a.batchs).ToList())
})
.Select(item => new {
Item = item.key.Item,
FromWarehouse = item.key.FromWarehouse,
ToWarehouse = item.key.ToWarehouse,
Quantity = item.summary.Item1,
Batches = item.summary.Item2
}); // Add ToArray() if you want to materialize
I am trying to update a List which is a List of Interfaces to concrete classes.
I add to the List each Market type i am interested in, for this Example these Markets are A and B
I loop over all the markets, (sample provided with 3 markets A B & C, we are only interested in A and B) And determine which is of interest to us.
Once found we pass this to an extraction method too do its work and create an instance of the Correct Market_ class type.
This all works fine, but when i try to update the list with the Updates it does not get reflected in the List.
Code below, any Suggestions?
Thanks
public class Test
{
public Test()
{
TheMarkets MarketsToUpdate = new TheMarkets();
List<SpecificCompanyMarket> lstMarks = new List<SpecificCompanyMarket>();
lstMarks.Add(new SpecificCompanyMarket(1234, "A", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(5874, "B", "Some HTML DATA HERE"));
lstMarks.Add(new SpecificCompanyMarket(2224, "C", "Some HTML DATA HERE"));
foreach (var item in lstMarks)
{
if (MarketsToUpdate.IsMarketWeAreInterestedIn(item.MarketName))
{
ITheMarkets MarkToUpdate = ExtractMarketData(item);
var obj = MarketsToUpdate.MarketsWeAreInterestedIn.FirstOrDefault(x => x.MarketName() == "A");
if (obj != null)
{
obj = MarkToUpdate;
}
}
}
//Look At MarketsToUpdate Now and the item has not changed, still original values
//I was expecting to see the new values for the fields in A, not the default 0's
}
public ITheMarkets ExtractMarketData(SpecificCompanyMarket item)
{
ITheMarkets market = null;
if (item.MarketName.ToUpper() == "A")
{
Market_A marketType = new Market_A();
marketType.SomeValue1 = 123;
marketType.SomeValue2 = 158253;
market = marketType;
}
//Other Market extractions here
return market;
}
}
public class SpecificCompanyMarket
{
public int MarketId { get; set; }
public string MarketName { get; set; }
public string MarketDataHTML { get; set; }
public SpecificCompanyMarket(int MID, string MName, string MData)
{
MarketId = MID;
MarketName = MName;
MarketDataHTML = MData;
}
}
public class TheMarkets
{
public List<ITheMarkets> MarketsWeAreInterestedIn = new List<ITheMarkets>();
public TheMarkets()
{
Market_A A = new Market_A();
Market_B B = new Market_B();
MarketsWeAreInterestedIn.Add(A);
MarketsWeAreInterestedIn.Add(B);
}
public bool IsMarketWeAreInterestedIn(string strMarketName)
{
bool blnRetVal = false;
foreach (var item in MarketsWeAreInterestedIn)
{
if (item.MarketName().ToUpper().Trim().Equals(strMarketName.ToUpper().Trim()))
{
blnRetVal = true;
break;
}
}
return blnRetVal;
}
}
public interface ITheMarkets
{
string MarketName();
}
public class Market_A : ITheMarkets
{
public string LabelType { get; private set; }
public double SomeValue1 { get; set; }
public double SomeValue2 { get; set; }
public double SomeValue3 { get; set; }
public Market_A()
{
LabelType = "A";
}
public string MarketName()
{
return LabelType;
}
}
public class Market_B : ITheMarkets
{
public string LabelType { get; private set; }
public List<string> SomeList { get; set; }
public double SomeValue { get; set; }
public Market_B()
{
LabelType = "B";
}
public string MarketName()
{
return LabelType;
}
}
This is a short example to get you going. Loop through your list, find the object you want to update, create a new object of that type and then find the original objects index in the list and overwrite it in place. You are essentially just replacing the object in the list with a new one not mutating the existing one.
foreach (var item in lstMarks)
{
//your code to get an object with data to update
var yourObjectToUpdate = item.GetTheOneYouWant();
//make updates
yourObjectToUpdate.SomeProperty = "New Value";
int index = lstMarks.IndexOf(item);
lstMarks[index] = yourObjectToUpdate;
}
You are extracting an obj from marketWeAreInterestedIn list using LINQ's firstOrDefault extension. This is a new object and not a reference to the obj in that list. Therefore, no updates will be reflected in the object inside that list. Try using 'indexof'
You are not storing "list of interfaces" in your list. List<T> stores an array of pointers to objects that support T interface. Once you enumerate (with Linq in your case) your list, you copy a pointer from list, which is not associated with list itself in any way. It is just a pointer to your instance.
To do what you want, you will have to build new list while enumerating the original one, adding objects to it, according to your needs, so the second list will be based on the first one but with changes applied that you need.
You can also replace specific instance at specific index instead of building new list in your code, but to do this you will need to enumerate your list with for loop and know an index for each item:
list[index] = newvalue;
But there is a third solution to update list item directly by Proxying them. This is an example
class ItemProxy : T { public T Value { get; set; } }
var list = new List<ItemProxy<MyClass>>();
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
list.Insert(new ItemProxy { Value = new MyClass() });
foreach(var item in list)
if(item // ...)
item.Value = new MyClass(); // done, pointer in the list is updated.
Third is the best case for perfomance, but it will be better to use this proxying class for something more than just proxying.
I have two classes:
public class ShopClass
{
public string shop_id { get; set; }
public List<CurrencyAmount> amount { get; set; }
}
public class CurrencyAmount
{
public string currency { get; set; }
public int? amount { get; set; }
}
I can add values to the class ShopClass like this:
ShopClass shop = new ShopClass();
shop.shop_id = "100";
List<CurrencyAmount> ca = new List<CurrencyAmount>();
ca.Add(new CurrencyAmount() { currency = "USD", amount = 1000});
shop.amount = ca;
I can get the value of shop_id like this:
Console.WriteLine(shop.shop_id);
But how can I get the value of the amount?
Many thanks for any suggestions.
You can do something like:
foreach (var v in shop.amount)
{
Console.WriteLine(v.currency + " " + v.amount);
}
Or alternatively, you can avoid string concatenation (thanks PC Luddite) and use:
foreach (var v in shop.amount)
{
Console.WriteLine("{0} {1}", v.currency, v.amount);
}
This snippet will print out all of the values in shop.amount. Since shop.amount refers to a list, you can't get the values by using Console.WriteLine(shop.amount), as this will just print out a reference to the list. Instead, you need to iterate through all of the items in shop.amount in order to get their values.
There is no "the" amount, you have a list of amounts. So you need to iterate or project the items in the amount property, for example using a foreach loop:
foreach (var amount in shop.amount)
{
Console.WriteLine("{0} {1}", amount.currency, amount.amount);
}
Your example can easily be reduced to a dictionary for direct lookup. I'm assuming you're looking to find the value for a specific currency and not print all values.
Store currencies and their values.
using System;
using System.Collections.Generic;
public class ShopClass
{
public ShopClass()
{
Amounts = new Dictionary<string, int>();
}
public string ShopID { get; set; }
public Dictionary<string, int> Amounts { get; private set; }
public void AddAmount(string currency, int amount)
{
if (Amounts.ContainsKey(currency))
{
Amounts[currency] = amount;
}
else
{
Amounts.Add(currency, amount);
}
}
public int? GetAmount(string currency)
{
if (Amounts.ContainsKey(currency))
{
return Amounts[currency];
}
return null;
}
public void PrintAmounts()
{
foreach (var currency in Amounts.Keys)
{
Console.WriteLine("{0} - {1}: {2}", ShopID, currency, GetAmount(currency));
}
}
}
Print currency values.
ShopClass shop = new ShopClass();
shop.ShopID = "100";
shop.AddCurrency("USD", 1000);
shop.PrintAmounts();
public class ListKeywords
{
public int ID { set; get; }
public string Keyword { set; get; }
public string Language { set; get; }
public int WordCount { set; get; }
}
List<ListKeywords> keywordsList = new List<ListKeywords>();
//
listview1.Add(new ListKeywords() { ID = keywordsList.Count+1, Keyword = line.Trim(), WordCount = countWords(line) });
i want to add 10000 ListKeywords item to ListKeywords, but i just want to display top 1000 item in listview1 control.
Now, if sort listview1 i want to sort all the item (ListKeywords ), not only sort top 1000 item in listview1.
How to do it ?
Sorry my English is not very good
List<ListKeywords> SortedList = SortedList.OrderBy(o=>o.ID).Take(1000).ToList();
You could just bind your listview to a property that does the work for you. Something like:
public List<ListKeywords> Top1000Sorted
{
get
{
return keywordsList.OrderBy(x => x.ID).Take(1000).ToList();
}
}
And bind the ListView using:
listview1.ItemsSource = Top1000Sorted;
If you want to sort by keywrods then use this:
var top1000Items = keywordsList.OrderByDescending(x=>x.Keyword).Take(1000);
If you want to sord by ID then use this:
var top1000Items = keywordsList.OrderByDescending(x=>x.ID).Take(1000);
So if you have 10.000 items and and tier IDs start from 1 and finish to 10000, then if you sort by ID, the top1000Items would have Ids ranging from 9001 to 10000.
Just use lists's OrderBy method prior to selecting the top 1000 objects. Then Take the top 1000 entries.
So, something like this method:
public List<ListKeywords> SortByIdAndSelectRange(List<ListKeywords> list, int range)
{
return list.OrderBy(x => x.ID).Take(range).ToList();
}
Use:
myList = SortAndSelectRange(myList, 1000);
You can modify that method further to decide what you want to sort by.
Here's a very crude sample showing the use of the above method:
class ListKeywords
{
public int ID { set; get; }
}
class Program
{
static void Main(string[] args)
{
var myList = new List<ListKeywords>();
Random rnd = new Random();
for (int i = 0; i < 3000; i++)
{
var entry = new ListKeywords() { ID = rnd.Next(3000, 9999) };
myList.Add(entry);
}
myList = SortByIdAndReturnRange(myList, 1000);
foreach (var entry in myList)
{
Console.WriteLine(entry.ID);
}
}
static List<ListKeywords> SortByIdAndReturnRange(List<ListKeywords> list, int range)
{
return list.OrderBy(x => x.ID).Take(range).ToList();
}
}
Lastly, you can stack sorting:
myList.OrderBy(x => x.Param1).ThenBy(x => x.Param2).ThenBy(x => x.Param3) ... and so on.
I use that when I want to sort a list by date, and then every day to be sorted by time.
I am iterating through a List of objects of Type "prvEmployeeIncident".
The object has the following properties:
public DateTime DateOfIncident { get; set; }
public bool IsCountedAsAPoint;
public decimal OriginalPointValue;
public bool IsFirstInCollection { get; set; }
public bool IsLastInCollection { get; set; }
public int PositionInCollection { get; set; }
public int DaysUntilNextPoint { get; set; }
public DateTime DateDroppedBySystem { get; set; }
public bool IsGoodBehaviorObject { get; set; }
My List is sorted by the DateOfIncident property. I would like to find the next object up the list where IsCounted == true and change it to IsCounted = false.
One question:
1) How do I find this object up the list ?
If I understand your question correctly, you can use LINQ FirstOrDefault:
var nextObject = list.FirstOrDefault(x => x.IsCountedAsAPoint);
if (nextObject != null)
nextObject.IsCountedAsAPoint = false;
If I understand correctly this can be solved with a simple foreach loop. I don't exactly understand your emphasis on "up" as you don't really move up a list, you traverse it. Anyways, the following code snippet finds the first Incident where IsCounted is true and changes it to false. If you're starting from a given position change the for each loop to a for loop and start at i = currentIndex with the exit condition being i < MyList.Count. Leave the break statement to ensure you only modify one Incident object.
foreach (prvEmployeeIncident inc in MyList)
{
if (inc.IsCountedAsAPoint)
{
inc.IsCountedAsAPoint = false;
break;
}
}
You can use List(T).FindIndex to search up the list.
Example:
public class Foo
{
public Foo() { }
public Foo(int item)
{
Item = item;
}
public int Item { get; set; }
}
var foos = new List<Foo>
{
new Foo(1),
new Foo(2),
new Foo(3),
new Foo(4),
new Foo(5),
new Foo(6)
};
foreach (var foo in foos)
{
if(foo.Item == 3)
{
var startIndex = foos.IndexOf(foo) + 1;
var matchedFooIndex = foos.FindIndex(startIndex, f => f.Item % 3 == 0);
if(matchedFooIndex >= startIndex) // Make sure we found a match
foos[matchedFooIndex].Item = 10;
}
}
Just be sure you do not modify the list itself since that will throw an exception.