I am trying to remove information that is in the third level of a dictionary and can only use it after the removal of this information.
But I can not, what am I doing wrong?
public class Person
{
int id;
string name;
public string Name
{
get { return name; }
set { name = value; }
}
public int ID
{
get { return id; }
set { id = value; }
}
public List<Product> ListProd;
}
public class Product
{
public int idProd;
public string Description;
public List<Tax> listTax;
}
public class Tax
{
public int idTax;
public string Value;
}
//Method
public void SomeMethod()
{
Dictionary<int, List<int>> dicRemove = new Dictionary<int, List<int>>();
List<int> listTaxToRemove = new List<int>();
for (int m = 8; m < 10; m++)
{
listTaxToRemove.Add(m);
}
dicRemove.Add(10, listTaxToRemove);
Dictionary<int, List<Person>> dic = new Dictionary<int, List<Person>>();
List<Person> list = new List<Person>();
for (int i = 0; i < 2; i++)
{
Person d = new Person();
d.ID = i;
d.Name = "Person " + i;
d.ListProd = new List<Product>();
for (int j = 3; j < 6; j++)
{
Product p = new Product();
p.idProd = j;
p.Description = "Product " + j;
d.ListProd.Add(p);
p.listTax = new List<Tax>();
for (int m = 7; m < 10; m++)
{
Tax t = new Tax();
t.idTax = m;
t.Value = "Tax " + m;
p.listTax.Add(t);
}
}
list.Add(d);
}
dic.Add(10, list);
var q = dic.Select(s => s.Value
.Select(s1 => s1.ListProd
.Select(s2 => s2.listTax
.RemoveAll(r =>!dicRemove[s.Key].Contains(r.idTax))))).ToList();
}
I tried a number of ways, through iterations, this approach had just deleting unnecessary records.
Thank you!
RemoveAll is not invoked due to deferred execution of Select
you have 3 Select and only one ToList()
q has type System.Collections.Generic.List'1[System.Collections.Generic.IEnumerable'1[System.Collections.Generic.IEnumerable'1[System.Int32]]]
there are nested IEnumerable object which were not enumerated
here is a working variant but absolutely unreadable, don't do it like this
var q = dic.Select(s => s.Value.Select(s1 => s1.ListProd.Select(s2 => s2.listTax.RemoveAll(r=>!dicRemove[s.Key].Contains(r.idTax)))
.ToList())
.ToList())
.ToList();
improved variant
foreach(var pair in dic)
foreach(var person in pair.Value)
foreach(var prod in person.ListProd)
prod.listTax.RemoveAll(t=>!dicRemove[pair.Key].Contains(t.idTax));
Related
I Had to duplicate this method (made by another person) but I wanna know if there is a way to unify it,
this one receives an IList<ViewModelA>,
I need to pass to it an IList<ViewModelB>
with different properties, but the ones used in the method are in both are the same like fatherId or Items
I wanna know if there is a way to unify those methods with inheritance or interface or something else.
private IList<ViewModelA> TrasnformToTree(IEnumerable<ViewModelA> source)
{
var modelGroups = source.GroupBy(i => i.fatherId);
var roots = modelGroups.FirstOrDefault(g => g.Key.HasValue == false).ToList();
if (roots.Count > 0)
{
var dict = modelGroups.Where(g => g.Key.HasValue)
.ToDictionary(g => g.Key.Value, g => g.ToList());
for (int i = 0; i < roots.Count; i++)
{
if (dict.ContainsKey(roots[i].Id))
{
roots[i].Items = dict[roots[i].Id];
for (int j = 0; j < roots[i].Items.Count; j++)
AddChildren(roots[j].Items[j], dict);
}
else
{
roots[i].Items = new List<ViewModelA>();
}
}
}
return roots;
}
this should work for the code i'm seeing:
private interface ISomeInterface<T>
{
int Id { get; set; }
int? fatherId { get; set; }
List<T> Items { get; set; }
}
private IList<T> TrasnformToTree<T>(IEnumerable<T> source) where T : ISomeInterface<T>
{
var modelGroups = source.GroupBy(i => i.fatherId);
var roots = modelGroups.FirstOrDefault(g => g.Key.HasValue == false).ToList();
if (roots.Count > 0)
{
var dict = modelGroups.Where(g => g.Key.HasValue)
.ToDictionary(g => g.Key.Value, g => g.ToList());
for (int i = 0; i < roots.Count; i++)
{
if (dict.ContainsKey(roots[i].Id))
{
roots[i].Items = dict[roots[i].Id];
for (int j = 0; j < roots[i].Items.Count; j++)
AddChildren(roots[j].Items[j], dict);
}
else
{
roots[i].Items = new List<T>();
}
}
}
return roots;
}
I have written a code for students class to Rank them according to their marks. The code works accurately and gives the result as
Name:B Marks:30 Rank:1
Name:C Marks:30 Rank:1
Name:A Marks:20 Rank:3
Name:D Marks:10 Rank:4
But I need it to be optimized so it wont take too much of time to be processed. Below is the code
public List<Students> GetRanks(List<Students> students)
{
List<Students> rStudents = new List<Students>();
students = students.OrderByDescending(a => a.marks).ToList();
for (int i = 0; i < students.Count; i++)
{
Students stu = new Students();
stu.Id = students[i].Id;
stu.Name = students[i].Name;
stu.marks = students[i].marks;
if (i > 0 && students[i].marks == students[i - 1].marks)
{
stu.rank = rStudents.Select(a => a.rank).LastOrDefault();
}
else
{
stu.rank = i + 1;
}
rStudents.Add(stu);
}
return rStudents;
}
List<Students> students = new List<Students>() {
new Students() { Id = 1, Name = "A", marks = 20 },
new Students() { Id = 1, Name = "B", marks = 30 },
new Students() { Id = 1, Name = "C", marks = 30 },
new Students() { Id = 1, Name = "D", marks = 10 },
};
List<Students> rStudents = GetRanks(students);
public class Students
{
public int Id { get; set; }
public string Name { get; set; }
public double marks { get; set; }
public int rank { get; set; }
}
foreach (Students s in GetRanks(students))
{
Console.WriteLine($"Name:{s.Name}\tMarks:{s.marks}\tRank:{s.rank}");
}
Remove unnecessary resource.and using yield will have performance impact in large data. Fewer line and more readable.
If you are fetching data from DataBase it's better idea sort them there then fetch them.
public static IEnumerable<Students> GetRanks(List<Students> students)
{
List<Students> rStudents = students.OrderByDescending(a => a.marks).ToList();
for (int i = 0; i < rStudents.Count; i++)
{
if (i > 0 && rStudents[i].marks == rStudents[i - 1].marks)
{
rStudents[i].rank = rStudents[i - 1].rank;
}
else
{
rStudents[i].rank = i + 1;
}
yield return rStudents[i];
}
}
This should be pretty darn fast:
var rank = 1;
students
.GroupBy(x => x.marks)
.OrderByDescending(x => x.Key)
.ToList()
.ForEach(xs =>
{
xs.ToList().ForEach(x => x.rank = rank);
rank += xs.Count();
});
With your sample data I get:
Here's a non-destructive version of the code:
public static List<Students> GetRanks(List<Students> students)
{
var rank = 1;
return
students
.OrderByDescending(x => x.marks)
.GroupBy(x => x.marks)
.SelectMany(xs =>
{
var r = rank;
rank += xs.Count();
return xs.Select(x => new Students()
{
Id = x.Id,
Name = x.Name,
marks = x.marks,
rank = r,
});
})
.ToList();
}
I measured this using 100_000 randomly created students. My code completed in 110 milliseconds. The original code in the question took 64_119 milliseconds.
Yes, this code was nearly 600x faster.
More then 50% improvement if have large collect with a new method called GetRanksV1
namespace StudentPerformance
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
List<Students> students = new List<Students>();
Random _random = new Random();
for (int i = 1; i < 10001; i++)
{
students.Add(new Students() { Id = i, Name = "A" + i, marks = _random.Next(10, 50) });
}
var watch = System.Diagnostics.Stopwatch.StartNew();
var tmp = GetRanks(students);
watch.Stop();
Console.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms");
watch = System.Diagnostics.Stopwatch.StartNew();
var tmp1 = GetRanksV1(students);
watch.Stop();
Console.WriteLine($"Execution Time V1: {watch.ElapsedMilliseconds} ms");
foreach (Students s in tmp1)
{
Console.WriteLine($"Name:{s.Name}\tMarks:{s.marks}\tRank:{s.rank}");
}
Console.ReadLine();
}
public static List<Students> GetRanks(List<Students> students)
{
List<Students> rStudents = new List<Students>();
students = students.OrderByDescending(a => a.marks).ToList();
for (int i = 0; i < students.Count; i++)
{
Students stu = new Students();
stu.Id = students[i].Id;
stu.Name = students[i].Name;
stu.marks = students[i].marks;
if (i > 0 && students[i].marks == students[i - 1].marks)
{
stu.rank = rStudents.Select(a => a.rank).LastOrDefault();
}
else
{
stu.rank = i + 1;
}
rStudents.Add(stu);
}
return rStudents;
}
public static List<Students> GetRanksV1(List<Students> students)
{
int Srno = 0;
var tmp = students.GroupBy(a => a.marks).OrderByDescending(o => o.Key).Select(s => new { Mark = s.Key, Rand = ++Srno, });
var values = from s in students
join t in tmp on s.marks equals t.Mark
select new Students { Id = s.Id, Name = s.Name, marks = s.marks, rank = t.Rand };
return values.ToList();
}
}
public class Students
{
public int Id { get; set; }
public string Name { get; set; }
public double marks { get; set; }
public int rank { get; set; }
}
}
The problem I'm having is that I'm not sure how to translate my double array into a Dictionary. I have some LINQ Where statements that are pretty heavy, so I wanted to use the keys of my Dictionary to look up a specific value. I have two classes with getters and setters and then my calculator.
I've fiddled around a bit with trying to make either a lookup or a Dictionary, but didn't have any luck.
public class Product
{
public int EarliestOriginYear { get; set; }
public int NoOfDevelopmentYears { get; set; }
public string ProductName { get; set; }
public IEnumerable<ProductIncrementalValue> ProductIncrementalValues { get; set; }
}
public class ProductIncrementalValue
{
public string ProductName { get; set; }
public int OriginYear { get; set; }
public int DevelopmentYear { get; set; }
public double IncrementalValue { get; set; }
}
public IList<double> Calculate(Product product)
{
IList<double> cumulativeDataTriangle = new List<double>();
if (!product.ProductIncrementalValues.Any())
return cumulativeDataTriangle;
for (int i = 0; i < product.NoOfDevelopmentYears; i++)
{
// This is what I want to change (where statements)
var incrementalValues = product.ProductIncrementalValues
.Where(v => v.OriginYear == product.EarliestOriginYear + i)
.ToList();
double previous = 0;
for (int j = 0; j < product.NoOfDevelopmentYears - i; j++)
{
// This is what I want to change
double incrementalValue = incrementalValues.Where(val =>
val.DevelopmentYear == val.OriginYear + j)
.Select(x => x.IncrementalValue)
.FirstOrDefault();
var tmp = incrementalValue + previous;
cumulativeDataTriangle.Add(tmp);
previous = tmp;
}
}
return cumulativeDataTriangle;
}
You could group the products by OriginYear and DevelopmentYear before the loops. Something like this might help:
public IList<double> Calculate(Product product)
{
IList<double> cumulativeDataTriangle = new List<double>();
if (!product.ProductIncrementalValues.Any())
return cumulativeDataTriangle;
var lookup = product.ProductIncrementalValues.ToLookup(v => (v.OriginYear, v.DevelopmentYear), v => v.IncrementalValue);
for (int i = 0; i < product.NoOfDevelopmentYears; i++)
{
var originYear = product.EarliestOriginYear + i;
double previous = 0;
for (int j = 0; j < product.NoOfDevelopmentYears - i; j++)
{
var developmentYear = originYear + j;
var incrementalValues = lookup[(originYear, developmentYear)];
double incrementalValue = incrementalValues.FirstOrDefault();
var tmp = incrementalValue + previous;
cumulativeDataTriangle.Add(tmp);
previous = tmp;
}
}
return cumulativeDataTriangle;
}
Thanks to the previous answer, I managed to implement a Dictionary based on OriginYear and DevelopmentYear, which returns an IncrementalValue.
public IList<double> Calculate(Product product)
{
IList<double> cumulativeDataTriangle = new List<double>();
if (!product.ProductIncrementalValues.Any())
return cumulativeDataTriangle;
var lookup = product.ProductIncrementalValues.
ToDictionary(v => (v.OriginYear, v.DevelopmentYear), v => v.IncrementalValue);
for (int i = 0; i < product.NoOfDevelopmentYears; i++)
{
var originYear = product.EarliestOriginYear + i;
double previous = 0;
for (int j = 0; j < product.NoOfDevelopmentYears - i; j++)
{
var developmentYear = originYear + j;
double incrementalValue;
lookup.TryGetValue((originYear, developmentYear), out incrementalValue);
var tmp = incrementalValue + previous;
cumulativeDataTriangle.Add(tmp);
previous = tmp;
}
}
return cumulativeDataTriangle;
}
I'm assigning values to a class by using ObservableCollection.Class contains MainItems and it's SubItems. Now how can I read all SubItems for each input of MainItem?
public class MainItems
{
public string ItemName { get; set; }
public ObservableCollection<SubItems> SubItemsList { get; set; }
}
public class SubItems
{
public string SubItemName { get; set; }
}
ObservableCollection<MainItems> _data = new ObservableCollection<MainItems>();
for (int i = 1; i <= 5; i++)
{
MainItems _mainItems = new MainItems();
_mainItems.ItemName = "Main" + i.ToString();
_mainItems.SubItemsList = new ObservableCollection<SubItems>();
for (int j = 1; j <= 3; j++)
{
SubItems _subItems = new SubItems()
{
SubItemName = "SubItem" + i.ToString()
};
_mainItems.SubItemsList.Add(_subItems);
}
_data.Add(_mainItems);
}
The foreach loop alway honors the collections(List, Array, Dictionary(special), ...) boundaries and iterates over all Elements, so its the shortest way to achieve what you want. It disallows you to add/remove elements from the currently iterated collection. In this case the classic for loop is your friend.
Full description from Microsoft:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/foreach-in
Based on Fangs comment:
// foreach Version
foreach (MainItems mainItem in _data)
{
foreach (SubItems subItems in mainItem.SubItemsList)
{
Console.WriteLine($"{mainItem.ItemName} has a child {subItems.SubItemName}!");
}
}
// for Version
for (int i = 0; i < _data.Count; i++)
{
MainItems mainItem = _data[i];
for (int k = 0; k < mainItem.SubItemsList.Count; k++)
{
SubItems subItem = mainItem.SubItemsList[k];
Console.WriteLine($"{mainItem.ItemName} has a child {subItem.SubItemName}!");
}
}
// For Enumerator version
// get the input main item
string input = "Main1";
IEnumerable<ObservableCollection<SubItems>> ItemsforSelectedMainIem = _data.Where(x => x.ItemName == input).Select(x => x.SubItemsList);
var e = ItemsforSelectedMainIem.GetEnumerator();
while (e.MoveNext())
{
var v = e.Current.Select(x=>x.SubItemName).ToList();
foreach (var item in v)
{
Console.WriteLine(item);
}
}
I have created an object with the attributes String, and the other is a List<String>.
I have also created a static List<MyObject> where i add then all my Objects.
Now my Problem is the second attribute is getting overridden.
For example I have 3 Objects:
Object1: "Name"; List with 3 Strings
Object2: "Name2"; List with 2 Strings
Object3: "Name3"; List with 5 Strings
If i add them now to my Object List, it looks like so
Name; List with 5 Strings
Name2; List with 5 Strings
Name3; List with 5 Strings
It override the second attributes to all the other Objects in the List.
Code:
for (int i = 0; i < 100; i++)
{
if (elo.ReadObjMask(i) > 0)
{
var iRet = elo.PrepareObjectEx(0, 0, i);
maskenname = elo.ObjMName();
if (maskenname != "")
{
for (int e = 0; e < 50; e++)
{
string eigenschaft = elo.GetObjAttribName(e);
if (eigenschaft != "" && eigenschaft != "-")
{
eigenschaften.Add(eigenschaft);
}
}
allMasks.Add(maskenname);
}
else
{
// Do nothing
}
EloMask emask = new EloMask(maskenname, eigenschaften);
staticVariables.allMask.Add(emask);
eigenschaften.Clear();
}
}
Here is my object class:
public class EloMask
{
public string name;
public List<String> eigenschaften;
public EloMask(string iname, List<String> ieigenschaften)
{
name = iname;
eigenschaften = ieigenschaften;
}
}
The List<string> always points to the same instance because you are passing a reference to the list, not a copy. As a result, the list is cleared and filled again for each EloMask that you pass that list into.
To fix your issue, create a new list instead:
if (elo.ReadObjMask(i) > 0)
{
var iRet = elo.PrepareObjectEx(0, 0, i);
maskenname = elo.ObjMName();
// create a new list here!!!
var eigenschaften = new List<string>();
if (maskenname != "")
{
for (int e = 0; e < 50; e++)
{
string eigenschaft = elo.GetObjAttribName(e);
if (eigenschaft != "" && eigenschaft != "-")
{
eigenschaften.Add(eigenschaft);
}
}
allMasks.Add(maskenname);
}
EloMask emask = new EloMask(maskenname, eigenschaften);
staticVariables.allMask.Add(emask);
// clearing the list is no longer needed
}
Here is an example how you can do what you want:
public static List<Person> PersonsList = new List<Person>();
public static Random rd = new Random();
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
List<string> tmpAbilities = new List<string>() {((char)rd.Next(255)).ToString(), ((char)rd.Next(255)).ToString() , ((char)rd.Next(255)).ToString() };
Person tmpPerson = new Person("TmpName_"+i,tmpAbilities);
PersonsList.Add(tmpPerson);
}
foreach (var persona in PersonsList)
{
Console.WriteLine(persona);
}
}
public class Person
{
public string Name { get; set; }
public List<string> Abilities;
public Person(string name,List<string> abilities)
{
Name = name;
Abilities = abilities;
}
public override string ToString()
{
string retVal = $"Name: {Name}\n";
foreach (var ability in Abilities)
{
retVal += $"Ability : {ability}\n";
}
return retVal;
}
}
for (int i = 0; i < 100; i++)
{
if (elo.ReadObjMask(i) > 0)
{
// Create a new listin here
eigenschaften = new List<string>();
var iRet = elo.PrepareObjectEx(0, 0, i);
maskenname = elo.ObjMName();
if (maskenname != "")
{
for (int e = 0; e < 50; e++)
{
string eigenschaft = elo.GetObjAttribName(e);
if (eigenschaft != "" && eigenschaft != "-")
{
eigenschaften.Add(eigenschaft);
}
}
allMasks.Add(maskenname);
}
else
{
// Do nothing
}
EloMask emask = new EloMask(maskenname, eigenschaften);
staticVariables.allMask.Add(emask);
}
}