How to pivot a list in C# - c#

I have a list with following structure,
TimeStamp Name Value
---------------------------------------
01/May/2018 Prop1 5
07/May/2018 Prop1 9
01/May/2018 Prop2 8
07/May/2018 Prop2 11
01/May/2018 Prop3 18
07/May/2018 Prop3 7
.....................................................
......................................................
07/May/2018 Prop(N) (N)Value
And I am trying to pivot/convert it to following structure,
TimeStamp Prop1 Prop2 Prop3 .... PropN Value
---------------------------------------------------------------------------------------------
01/May/2018 5 8 18 (N)th Value 31+(N)th Value
07/May/2018 9 11 7 (N)th Value 27+(N)th Value
Edit:
In reality the list is quite big and can have 100 of Names and won't be knowing there values till it's loaded in memory. So it's not possible to check for the names as mentioned by #TheGeneral, though it works if we have limited sets of values and we know them before hand.
I attempted doing this using grouping , but it is probably not the approach.
var pivotList= customersList.GroupBy(cust => new { cust.Name, cust.Timestamp, cust.Value }).Select(x => new {
TimeStamp = x.Key.Timestamp,
Circuit = x.Key.Name,
NumData = x.Sum(y=>x.Key.Value)
});
I have pasted the actual format and not the C# lists for brevity.

If you know how many props you have beforehand, you could do something like this
var query = myList
.GroupBy(c => c.TimeStamp)
.Select(g => new {
TimeStamp = g.Key,
Prop1 = g.Where(x => x.Name == "Prop1").Sum(x => x.Value),
Prop2 = g.Where(x => x.Name == "Prop2").Sum(x => x.Value),
Prop3 = g.Where(x => x.Name == "Prop3").Sum(x => x.Value),
Value = g.Sum(c => c.Value)
});
If you this list of props is dynamic, you are going to have to call pivot at the server and query this manually. or use a sublist and groupby Name

I'll add my 5 cents to this question. I have implemented a simple pivot table class which does exactly what you need.
public static class PivotTable
{
public static PivotTable<TRow, TColumn, TValue> Create<TRow, TColumn, TValue>(Dictionary<TRow, Dictionary<TColumn, TValue>> dictionary)
where TRow : IComparable, IEquatable<TRow>
where TColumn : IComparable, IEquatable<TColumn>
{
return new PivotTable<TRow, TColumn, TValue>(dictionary);
}
}
public class PivotTable<TRow, TColumn, TValue>
where TRow : IComparable, IEquatable<TRow>
where TColumn : IComparable, IEquatable<TColumn>
{
private readonly Dictionary<TRow, Dictionary<TColumn, TValue>> _dictionary;
public PivotTable(Dictionary<TRow, Dictionary<TColumn, TValue>> dictionary)
{
_dictionary = dictionary;
}
public bool HasValue(TRow row, TColumn col)
{
return _dictionary.ContainsKey(row) && _dictionary[row].ContainsKey(col);
}
public TValue GetValue(TRow row, TColumn col)
{
return _dictionary[row][col];
}
public string Print()
{
var separator = " ";
var padRight = 15;
var rows = _dictionary.Keys;
var columns = _dictionary.SelectMany(x => x.Value.Keys).Distinct().OrderBy(x => x).ToList();
var sb = new StringBuilder();
var columnsRow = new[] { "" }.ToList();
columnsRow.AddRange(columns.Select(x => x.ToString()));
sb.AppendLine(string.Join(separator, columnsRow.Select(x => x.PadRight(padRight))));
foreach (var row in rows.OrderBy(x => x))
{
sb.Append(row.ToString().PadRight(padRight)).Append(" ");
foreach (var col in columns.OrderBy(x => x))
{
var val = HasValue(row, col) ? GetValue(row, col).ToString() : default(TValue).ToString();
sb.Append(val.PadRight(padRight)).Append(" ");
}
sb.AppendLine();
}
return sb.ToString();
}
}
public class Element
{
public DateTime TimeStamp { get; set; }
public string Name { get; set; }
public int Value { get; set; }
}
public static class Extensions
{
public static PivotTable<TRow, TColumn, TValue> ToPivot<TItem, TRow, TColumn, TValue>(
this IEnumerable<TItem> source,
Func<TItem, TRow> rowSelector,
Func<TItem, TColumn> colSelector,
Func<IEnumerable<TItem>, TValue> aggregatFunc
)
where TRow : IComparable, IEquatable<TRow>
where TColumn : IComparable, IEquatable<TColumn>
{
var dic = source
.GroupBy(rowSelector)
.ToDictionary(x => x.Key, x => x.GroupBy(colSelector).ToDictionary(y => y.Key, y => aggregatFunc(y)));
return PivotTable.Create(dic);
}
And then you can call it on any type implementing IEnumerable. You can select how elements should be aggregated. It also supports pretty printing, getting elements by row/col.
In your case you need to do this:
var elements = new List<Element>();
//fill in with some data
var pivot = elements.ToPivot(x => x.TimeStamp, x => x.Name, x => x.Sum(y => y.Value));
The core function is this.
var dic = source
.GroupBy(rowSelector)
.ToDictionary(x => x.Key, x => x.GroupBy(colSelector).ToDictionary(y => y.Key, y => aggregatFunc(y)));
If you don't need the rest of functionality just do this:
var dic = elements //Dictionary<DateTime,Dictionary<string,int>>
.GroupBy(x=>x.TimeStamp)
.ToDictionary(x => x.Key, x => x.GroupBy(x=>x.Name).ToDictionary(y => y.Key, y => y=>y.Sum(z=>z.Value));
And then you can access your data by doing e.g this:
var date = new DateTime(2018,6,6);
var prop = "Prop1";
dic[date][prop] // will give you a sum for this particular date and prop

class Program
{
static void Main(string[] args)
{
var elements = new Element[]
{
new Element
{
TimeStamp = new DateTime(2018,5,1),
Name = "Prop1",
Value = 5,
},
new Element
{
TimeStamp = new DateTime(2018,5,7),
Name = "Prop1",
Value = 9,
},
new Element
{
TimeStamp = new DateTime(2018,5,1),
Name = "Prop2",
Value = 8,
},
new Element
{
TimeStamp = new DateTime(2018,5,7),
Name = "Prop2",
Value = 11,
},
new Element
{
TimeStamp = new DateTime(2018,5,1),
Name = "Prop3",
Value = 18,
},
new Element
{
TimeStamp = new DateTime(2018,5,7),
Name = "Prop3",
Value = 18,
},
};
var pivot = from line in elements
group line by line.TimeStamp into g
select new { g.Key, Props=g.Select(el=>new { el.Name, el.Value }).ToArray(), Total = g.Sum(line => line.Value) };
int propCount = pivot.Max(line => line.Props.Count());
string[] props = pivot.SelectMany(p => p.Props, (parent, c) => c.Name).Distinct().ToArray();
Console.Write($"Date\t");
for (int i = 0; i < propCount; i++)
{
Console.Write($"{props[i]}\t");
}
Console.Write($"Total");
Console.WriteLine();
foreach (var pivotLine in pivot)
{
Console.Write($"{pivotLine.Key.ToShortDateString()}\t");
for (int i = 0; i < propCount; i++)
{
Console.Write($"{pivotLine.Props.FirstOrDefault(p=>p.Name==props[i])?.Value}\t");
}
Console.Write($"{pivotLine.Total}\t");
Console.WriteLine();
}
}
class Element
{
public DateTime TimeStamp { get; set; }
public string Name { get; set; }
public int Value;
}

Related

Code review of this portion of Linq Filters

I am a newbie of c #, I would like to know if I can remove the for each and do a single operation with Linq. I would like to return an IEnumerable with already filtered. is it possible to do this? Every suggestion is welcome, thank you very much
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Linq
{
class Oggetto
{
public int Id { get; set; }
public string MyProperty { get; set; }
public int Deleted { get; set; }
}
class Program
{
static void Main(string[] args)
{
IEnumerable<Oggetto> lista = new List<Oggetto> {
new Oggetto(){ Id = 1, MyProperty = "Propr1", Deleted = 0 },
new Oggetto(){ Id = 1, MyProperty = "Propr2", Deleted = 1 },
new Oggetto(){ Id = 2, MyProperty = "Prop3", Deleted = 0 },
new Oggetto(){ Id = 3, MyProperty = "Propr4", Deleted = 0 },
new Oggetto(){ Id = 3, MyProperty = "Prop5", Deleted = 1 }
};
foreach (var item in lista.Where(x => x.Deleted == 1).GroupBy(x => x.Id).Select(g => g.First()))
{
item.MyProperty = string.Join(",", lista.Where(t => t.Id == item.Id).Select(x => x.MyProperty).ToArray());
Console.WriteLine(item.Id);
Console.WriteLine(item.MyProperty);
}
Console.ReadLine();
}
}
}
You can use projection for this.
var orderedList = lista.GroupBy(x => x.Id)
.Where(x => x.Any(y => y.Deleted == 1))
.Select(x => new Oggetto
{
Id = x.Key, MyProperty = string.Join(",", x.Select(v => v.MyProperty))
});
foreach (var item in orderedList)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.MyProperty);
}
Anyway, as #Alex said you shoud replace Deleted field type to bool and as said by #Marco Salerno start programming in English you'll not regret.
First of all I would avoid the groupBy statement. This is a lot of unneded overhead. You can use distinct instead. This will give you all the IDs you need to know.
var ids = lista.Where(x => x.Deleted).Select(x => x.Id).Distinct();
You can then select all the elements that you need with:
var items = ids.Select(i => lista.Where(x => x.Id == i));
which results in a List of Lists. For the ease of use I would convert this to a Dictionary<K, V> (int this case it's Dictionary<long, List<string>> as a final step:
var dictionary = items.ToDictionary(l => l.First().Id, l => l.Select(o => o.MyProperty).ToList());
You now got a "nice and filtered" collection you can use any way you like (or just output it)
foreach (var item in dictionary)
{
Console.WriteLine($"Id: {item.Key}");
Console.WriteLine($"Properties: {string.Join(", ", item.Value)}");
}
I also changed your class a little bit to:
class Oggetto
{
public int Id { get; set; }
public string MyProperty { get; set; }
// bool instead of int - Deleted has only 2 states
public bool Deleted { get; set; }
}
First of all STOP programming in Italian, start doing it in English.
Anyway, this should be a better approach:
class Program
{
static void Main(string[] args)
{
List<Item> items = new List<Item> {
new Item{ Id = 1, MyProperty = "Propr1", Deleted = 0 },
new Item{ Id = 1, MyProperty = "Propr2", Deleted = 1 },
new Item{ Id = 2, MyProperty = "Prop3", Deleted = 0 },
new Item{ Id = 3, MyProperty = "Propr4", Deleted = 0 },
new Item{ Id = 3, MyProperty = "Prop5", Deleted = 1}
};
foreach (IGrouping<int,Item> group in items.GroupBy(x => x.Id).ToList())
{
List<Item> groupItems = group.ToList();
Item deletedItem = groupItems.Where(x => x.Deleted == 1).FirstOrDefault();
if(deletedItem != null)
{
deletedItem.MyProperty = string.Join(",", groupItems.Select(x => x.MyProperty).ToArray());
Console.WriteLine(deletedItem.Id);
Console.WriteLine(deletedItem.MyProperty);
}
}
}
}
class Item
{
public int Id { get; set; }
public string MyProperty { get; set; }
public int Deleted { get; set; }
}

Linq Recursive Sum

I have the following data strucure:
List<Item> Items = new List<Item>
{
new Item{ Id = 1, Name = "Machine" },
new Item{ Id = 3, Id_Parent = 1, Name = "Machine1"},
new Item{ Id = 5, Id_Parent = 3, Name = "Machine1-A", Number = 2, Price = 10 },
new Item{ Id = 9, Id_Parent = 3, Name = "Machine1-B", Number = 4, Price = 11 },
new Item{ Id = 100, Name = "Item" } ,
new Item{ Id = 112, Id_Parent = 100, Name = "Item1", Number = 5, Price = 55 }
};
I want to build a query that gets the sum of all children price in its parent (items are related by Id_Parent).
For example, for Item Id = 100, I have 55, because thats the value of the its child .
For Item Id = 3 I have 21, becaue Item Id = 5 and Id = 9 all sum to that.
So far soo good.
What I am strugling to get is for Item Id = 1 I should also have the sum = 21, because Id = 3 is a child of Id = 1 and it has a sum of 21.
Here is my code:
var result = from i in items
join item in item on i.Id_Parent equals item.Id
select new
{
Name = prod.Nome,
Sum =
(from it in items
where it.Id_Parent == item.Id
group it by new
{
it.Id_Parent
}
into g
select new
{
Sum = g.Sum(x => x.Price)
}
).First()
};
Help appreciated.
Create a recursive function to find all the children of a parent:
public static IEnumerable<Item> ItemDescendents(IEnumerable<Item> src, int parent_id) {
foreach (var item in src.Where(i => i.Id_Parent == parent_id)) {
yield return item;
foreach (var itemd in ItemDescendents(src, item.Id))
yield return itemd;
}
}
Now you can get the price for any parent:
var price1 = ItemDescendants(Items, 1).Sum(i => i.Price);
Note if you know that the children of an item are always greater in id value than their parent, you don't need recursion:
var descendents = Items.OrderBy(i => i.Id).Aggregate(new List<Item>(), (ans, i) => {
if (i.Id_Parent == 1 || ans.Select(a => a.Id).Contains(i.Id_Parent))
ans.Add(i);
return ans;
});
For those that prefer to avoid recursion, you can use an explicit stack instead:
public static IEnumerable<Item> ItemDescendentsFlat(IEnumerable<Item> src, int parent_id) {
void PushRange<T>(Stack<T> s, IEnumerable<T> Ts) {
foreach (var aT in Ts)
s.Push(aT);
}
var itemStack = new Stack<Item>(src.Where(i => i.Id_Parent == parent_id));
while (itemStack.Count > 0) {
var item = itemStack.Pop();
PushRange(itemStack, src.Where(i => i.Id_Parent == item.Id));
yield return item;
}
}
I included PushRange helper function since Stack doesn't have one.
Finally, here is a variation that doesn't use any stack, implicit or explicit.
public IEnumerable<Item> ItemDescendantsFlat2(IEnumerable<Item> src, int parent_id) {
var children = src.Where(s => s.Id_Parent == parent_id);
do {
foreach (var c in children)
yield return c;
children = children.SelectMany(c => src.Where(i => i.Id_Parent == c.Id)).ToList();
} while (children.Count() > 0);
}
You can replace the multiple traversals of the source with a Lookup as well:
public IEnumerable<Item> ItemDescendantsFlat3(IEnumerable<Item> src, int parent_id) {
var childItems = src.ToLookup(i => i.Id_Parent);
var children = childItems[parent_id];
do {
foreach (var c in children)
yield return c;
children = children.SelectMany(c => childItems[c.Id]).ToList();
} while (children.Count() > 0);
}
I optimized the above based on the comments about too much nested enumeration, which improved performance vastly, but I was also inspired to attempt to remove SelectMany which can be slow, and collect IEnumerables as I've seen suggested elsewhere to optimize Concat:
public IEnumerable<Item> ItemDescendantsFlat4(IEnumerable<Item> src, int parent_id) {
var childItems = src.ToLookup(i => i.Id_Parent);
var stackOfChildren = new Stack<IEnumerable<Item>>();
stackOfChildren.Push(childItems[parent_id]);
do
foreach (var c in stackOfChildren.Pop()) {
yield return c;
stackOfChildren.Push(childItems[c.Id]);
}
while (stackOfChildren.Count > 0);
}
#AntonínLejsek's GetDescendants is still fastest, though it is very close now, but sometimes simpler wins out for performance.
The easy way would be to use a local function, like this:
int CalculatePrice(int id)
{
int price = Items.Where(item => item.Id_Parent == id).Sum(child => CalculatePrice(child.Id));
return price + Items.First(item => item.Id == id).Price;
}
int total = CalculatePrice(3); // 3 is just an example id
Another, cleaner solution instead would be to use the Y combinator to create a closure that can be called inline. Assuming you have this
/// <summary>
/// Implements a recursive function that takes a single parameter
/// </summary>
/// <typeparam name="T">The Type of the Func parameter</typeparam>
/// <typeparam name="TResult">The Type of the value returned by the recursive function</typeparam>
/// <param name="f">The function that returns the recursive Func to execute</param>
/// <returns>The recursive Func with the given code</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
Func<T, TResult> g = null;
g = f(a => g(a));
return g;
}
Then you can just get your result like so:
int total = Y<int, int>(x => y =>
{
int price = Items.Where(item => item.Id_Parent == y).Sum(child => x(child.Id));
return price + Items.First(item => item.Id == y).Price;
})(3);
What's nice about this is that it allows you to quickly declare and call a recursive function in a functional-fashion, which is especially handy in situations like this one, where you only need "throwaway" functions that you'll use just once. Also, since this function is quite small, using the Y combinator further reduces the boilerplate of having to declare a local function and call it on another line.
There are so many solutions that it is worth to make a benchmark. I added my solution to the mix too, it is the last function. Some functions include the root node and some not, but apart from this they return the same result. I tested wide tree with 2 children per parent and narrow tree with just one children per parent (depth is equal to number of items). And the results are:
---------- Wide 100000 3 ----------
ItemDescendents: 9592ms
ItemDescendentsFlat: 9544ms
ItemDescendentsFlat2: 45826ms
ItemDescendentsFlat3: 30ms
ItemDescendentsFlat4: 11ms
CalculatePrice: 23849ms
Y: 24265ms
GetSum: 62ms
GetDescendants: 19ms
---------- Narrow 3000 3 ----------
ItemDescendents: 100ms
ItemDescendentsFlat: 24ms
ItemDescendentsFlat2: 75948ms
ItemDescendentsFlat3: 1004ms
ItemDescendentsFlat4: 1ms
CalculatePrice: 69ms
Y: 69ms
GetSum: 915ms
GetDescendants: 0ms
While premature optimalization is bad, it is important to know what the asymptotic behaviour is. Asymptotic behaviour determines if the algorithm would scale or would die.
And the code follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
public class Test
{
public static IEnumerable<Item> ItemDescendents(IEnumerable<Item> src, int parent_id)
{
foreach (var item in src.Where(i => i.Id_Parent == parent_id))
{
yield return item;
foreach (var itemd in ItemDescendents(src, item.Id))
yield return itemd;
}
}
public static IEnumerable<Item> ItemDescendentsFlat(IEnumerable<Item> src, int parent_id)
{
void PushRange<T>(Stack<T> s, IEnumerable<T> Ts)
{
foreach (var aT in Ts)
s.Push(aT);
}
var itemStack = new Stack<Item>(src.Where(i => i.Id_Parent == parent_id));
while (itemStack.Count > 0)
{
var item = itemStack.Pop();
PushRange(itemStack, src.Where(i => i.Id_Parent == item.Id));
yield return item;
};
}
public IEnumerable<Item> ItemDescendantsFlat2(IEnumerable<Item> src, int parent_id)
{
var children = src.Where(s => s.Id_Parent == parent_id);
do
{
foreach (var c in children)
yield return c;
children = children.SelectMany(c => src.Where(i => i.Id_Parent == c.Id));
} while (children.Count() > 0);
}
public IEnumerable<Item> ItemDescendantsFlat3(IEnumerable<Item> src, int parent_id)
{
var childItems = src.ToLookup(i => i.Id_Parent);
var children = childItems[parent_id];
do
{
foreach (var c in children)
yield return c;
children = children.SelectMany(c => childItems[c.Id]);
} while (children.Count() > 0);
}
public IEnumerable<Item> ItemDescendantsFlat4(IEnumerable<Item> src, int parent_id)
{
var childItems = src.ToLookup(i => i.Id_Parent);
var stackOfChildren = new Stack<IEnumerable<Item>>();
stackOfChildren.Push(childItems[parent_id]);
do
foreach (var c in stackOfChildren.Pop())
{
yield return c;
stackOfChildren.Push(childItems[c.Id]);
}
while (stackOfChildren.Count > 0);
}
public static int GetSum(IEnumerable<Item> items, int id)
{
// add all matching items
var itemsToSum = items.Where(i => i.Id == id).ToList();
var oldCount = 0;
var currentCount = itemsToSum.Count();
// it nothing was added we skip the while
while (currentCount != oldCount)
{
oldCount = currentCount;
// find all matching items except the ones already in the list
var matchedItems = items
.Join(itemsToSum, item => item.Id_Parent, sum => sum.Id, (item, sum) => item)
.Except(itemsToSum)
.ToList();
itemsToSum.AddRange(matchedItems);
currentCount = itemsToSum.Count;
}
return itemsToSum.Sum(i => i.Price);
}
/// <summary>
/// Implements a recursive function that takes a single parameter
/// </summary>
/// <typeparam name="T">The Type of the Func parameter</typeparam>
/// <typeparam name="TResult">The Type of the value returned by the recursive function</typeparam>
/// <param name="f">The function that returns the recursive Func to execute</param>
/// <returns>The recursive Func with the given code</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
Func<T, TResult> g = null;
g = f(a => g(a));
return g;
}
public IEnumerable<Item> GetDescendants(IEnumerable<Item> items, int key)
{
var lookup = items.ToLookup(i => i.Id_Parent);
Stack<Item> st = new Stack<Item>(lookup[key]);
while (st.Count > 0)
{
var item = st.Pop();
yield return item;
foreach (var i in lookup[item.Id])
{
st.Push(i);
}
}
}
public class Item
{
public int Id;
public int Price;
public int Id_Parent;
}
protected Item[] getItems(int count, bool wide)
{
Item[] Items = new Item[count];
for (int i = 0; i < count; ++i)
{
Item ix = new Item()
{
Id = i,
Id_Parent = wide ? i / 2 : i - 1,
Price = i % 17
};
Items[i] = ix;
}
return Items;
}
public void test()
{
Item[] items = null;
int CalculatePrice(int id)
{
int price = items.Where(item => item.Id_Parent == id).Sum(child => CalculatePrice(child.Id));
return price + items.First(item => item.Id == id).Price;
}
var functions = new List<(Func<Item[], int, int>, string)>() {
((it, key) => ItemDescendents(it, key).Sum(i => i.Price), "ItemDescendents"),
((it, key) => ItemDescendentsFlat(it, key).Sum(i => i.Price), "ItemDescendentsFlat"),
((it, key) => ItemDescendantsFlat2(it, key).Sum(i => i.Price), "ItemDescendentsFlat2"),
((it, key) => ItemDescendantsFlat3(it, key).Sum(i => i.Price), "ItemDescendentsFlat3"),
((it, key) => ItemDescendantsFlat4(it, key).Sum(i => i.Price), "ItemDescendentsFlat4"),
((it, key) => CalculatePrice(key), "CalculatePrice"),
((it, key) => Y<int, int>(x => y =>
{
int price = it.Where(item => item.Id_Parent == y).Sum(child => x(child.Id));
return price + it.First(item => item.Id == y).Price;
})(key), "Y"),
((it, key) => GetSum(it, key), "GetSum"),
((it, key) => GetDescendants(it, key).Sum(i => i.Price), "GetDescendants" )
};
System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
var testSetup = new[]
{
new { Count = 10, Wide = true, Key=3}, //warmup
new { Count = 100000, Wide = true, Key=3},
new { Count = 3000, Wide = false, Key=3}
};
List<int> sums = new List<int>();
foreach (var setup in testSetup)
{
items = getItems(setup.Count, setup.Wide);
Console.WriteLine("---------- " + (setup.Wide ? "Wide" : "Narrow")
+ " " + setup.Count + " " + setup.Key + " ----------");
foreach (var func in functions)
{
st.Restart();
sums.Add(func.Item1(items, setup.Key));
st.Stop();
Console.WriteLine(func.Item2 + ": " + st.ElapsedMilliseconds + "ms");
}
Console.WriteLine();
Console.WriteLine("checks: " + string.Join(", ", sums));
sums.Clear();
}
Console.WriteLine("---------- END ----------");
}
}
static void Main(string[] args)
{
Test t = new Test();
t.test();
}
}
}
For future readers who may experience a StackOverflowException, the alternative I use is in the following example: (dotnetfiddle example)
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var items = new List<Item>
{
new Item{ Id = 1, Name = "Machine" },
new Item{ Id = 3, Id_Parent = 1, Name = "Machine1"},
new Item{ Id = 5, Id_Parent = 3, Name = "Machine1-A", Number = 2, Price = 10 },
new Item{ Id = 9, Id_Parent = 3, Name = "Machine1-B", Number = 4, Price = 11 },
new Item{ Id = 100, Name = "Item" } ,
new Item{ Id = 112, Id_Parent = 100, Name = "Item1", Number = 5, Price = 55 }
};
foreach(var item in items)
{
Console.WriteLine("{0} {1} $" + GetSum(items, item.Id).ToString(), item.Name, item.Id);
}
}
public static int GetSum(IEnumerable<Item> items, int id)
{
// add all matching items
var itemsToSum = items.Where(i => i.Id == id).ToList();
var oldCount = 0;
var currentCount = itemsToSum.Count();
// it nothing was added we skip the while
while (currentCount != oldCount)
{
oldCount = currentCount;
// find all matching items except the ones already in the list
var matchedItems = items
.Join(itemsToSum, item => item.Id_Parent, sum => sum.Id, (item, sum) => item)
.Except(itemsToSum)
.ToList();
itemsToSum.AddRange(matchedItems);
currentCount = itemsToSum.Count;
}
return itemsToSum.Sum(i => i.Price);
}
public class Item
{
public int Id { get; set; }
public int Id_Parent { get; set; }
public int Number { get; set; }
public int Price { get; set; }
public string Name { get; set; }
}
}
Result:
Machine 1 $21
Machine1 3 $21
Machine1-A 5 $10
Machine1-B 9 $11
Item 100 $55
Item1 112 $55
Basically we create a list with the initial items matching the id passed. If the id doesn't match we have no items and we skip the while loop. If we do have items, then we join to find all items that have a parent id of the items we currently have. From that list we then exclude the ones already in the list. Then append what we've found. Eventually there are no more items in the list that have matching parent id's.

casting error when casting a list of object to a dictionary of enum as key and list of objects as value

I have enum/classes as follows
public enum TestType
{
Automatic,
Manual,
SemiManual,
SemiAutomatic
}
public class TestCase
{
public int Id { get; set; }
}
public class TestData
{
public TestType TestType { get; set; }
public int Id { get; set; }
}
Its used as follows
static void Main(string[] args)
{
List<TestData> data = new List<TestData>()
{
new TestData(){ TestType = TestType.Automatic, Id = 21 },
new TestData(){ TestType = TestType.SemiAutomatic, Id = 34 },
new TestData(){ TestType = TestType.SemiManual, Id = 13 },
new TestData(){ TestType = TestType.Manual, Id = 14 },
new TestData(){ TestType = TestType.Automatic, Id = 45 },
new TestData(){ TestType = TestType.Automatic, Id = 56 }
};
}
I need to convert this list into a Dictionary<TestType, List<TestCase>>. For that I have code as follows.
Dictionary<TestType, List<TestCase>> dataAsDictionary =
data.GroupBy(x => x.TestType)
.ToDictionary(k => k.Key, v => v.Select( f=>
new TestCase() { Id = f.Id }));
Error CS0029 Cannot implicitly convert type
'System.Collections.Generic.Dictionary<ConsoleApp2.TestType,
System.Collections.Generic.IEnumerable<ConsoleApp2.TestCase>>' to
'System.Collections.Generic.Dictionary<ConsoleApp2.TestType,
System.Collections.Generic.List<ConsoleApp2.TestCase>>' ConsoleApp2
How can I resolve this error? I try various combinations of casting but that doesn't seem to help
Add a ToList call:
Dictionary<TestType, List<TestCase>> dataAsDictionary =
data.GroupBy(x => x.TestType)
.ToDictionary(k => k.Key, v => v.Select( f=>
new TestCase() { Id = f.Id }).ToList());
It's needed because Select returns IEnumerable<T> and not List<T>.
You get this error because Select() returns IEnumerable<T>, while your Dictionary wants values of type List<T>.
Add a call of ToList() to fix this problem:
Dictionary<TestType,List<TestCase>> dataAsDictionary = data
.GroupBy(x => x.TestType)
.ToDictionary(
k => k.Key
, v => v.Select( f=> new TestCase() { Id = f.Id }).ToList()
); // ^^^^^^^^^

Select a restricted subset of items based on child property

I'm just wondering if there's a better way to write this code, basically the source object contains a mix of items with a boolean property however the destination object has two lists which should contain the true/false items independently.
I've written it in Linq and it works just fine but it feels as though there's a better way. Any suggestions?
void Main()
{
var s = new ResponseObject()
{
Results = new List<GroupedObject>()
{
new GroupedObject()
{
Name = "List A",
List=new List<DetailObject>()
{
new DetailObject{ Name = "Allowed", AllowedAccess = true},
new DetailObject{ Name = "Restricted", AllowedAccess = false}
}
},
new GroupedObject()
{
Name = "List B",
List=new List<DetailObject>()
{
new DetailObject{ Name = "Allowed", AllowedAccess = true},
new DetailObject{ Name = "Restricted", AllowedAccess = false}
}
}
}
};
var d = new ResponseViewModel();
d.AllowedResults = FilterObjectsByAccess(s.Results, true);
d.RestrictedResults = FilterObjectsByAccess(s.Results, false);
// Other stuff
}
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source.Where(i => i.List.Any(c => c.AllowedAccess == allowAccess))
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess)
});
}
public class ResponseObject
{
public IEnumerable<GroupedObject> Results { get; set; }
}
public class ResponseViewModel
{
public IEnumerable<GroupedObject> AllowedResults { get; set; }
public IEnumerable<GroupedObject> RestrictedResults { get; set; }
}
public class GroupedObject
{
public string Name { get; set; }
public IEnumerable<DetailObject> List { get; set; }
}
public class DetailObject
{
public string Name { get; set; }
public bool AllowedAccess { get; set; }
}
One change that may be worth benchmarking would be changing:
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source.Where(i => i.List.Any(c => c.AllowedAccess == allowAccess))
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess)
});
}
to:
public IEnumerable<GroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source, bool allowAccess)
{
return source
.Select(i => new GroupedObject()
{
Name = i.Name,
List = i.List.Where(c => c.AllowedAccess == allowAccess).ToList() // `ToList` here is optional - it is a trade-off between RAM and CPU
})
.Where(z => z.List.Any());
}
Your original code, with the use of Any then Where would enumerate i.List twice. The above change would likely improve that.
Another approach, which would likely involve even higher memory consumption could be to switch to using ToLookup:
var d = new ResponseViewModel
{
AllowedResults =
FilterObjectsByAccess(s.Results)
.Select(z => new GroupedObject() { Name = z.Name, List = z.GroupedList[false] })
.Where(z => z.List.Any()),
RestrictedResults =
FilterObjectsByAccess(s.Results)
.Select(z => new GroupedObject() { Name = z.Name, List = z.GroupedList[true] })
.Where(z => z.List.Any())
};
// Other stuff
}
public List<SpecialGroupedObject> FilterObjectsByAccess(IEnumerable<GroupedObject> source)
{
return source.Select(i => new SpecialGroupedObject()
{
Name = i.Name,
GroupedList = i.List.ToLookup(c => c.AllowedAccess)
}).ToList();
}
I can suggest you to use ToDictionary() like this:
var result = new[] {true, false}.ToDictionary(k => k,
v =>
s.Results.Where(w => w.List.Any(x => x.AllowedAccess == v))
.Select(c => new GroupedObject {Name = c.Name, List = c.List.Where(l => l.AllowedAccess == v)}));
var allowedResults = result[true];
var restrictedResults = result[false];
Or this:
var result = s.Results
.SelectMany(c => c.List, (b, c) => new {b.Name, DObj = c})
.GroupBy(g => g.DObj.AllowedAccess)
.ToDictionary(k=> k.Key,
c =>
new {
c.Key,
List =
c.GroupBy(cg => cg.Name)
.Select(
x => new GroupedObject {Name = x.Key, List = x.Select(l => l.DObj).ToList()})
.ToList()
});

Is it possible to bind a List to a ListView in WinForms?

I'd like to bind a ListView to a List<string>. I'm using this code:
somelistview.DataBindings.Add ("Items", someclass, "SomeList");
I'm getting this exception: Cannot bind to property 'Items' because it is read-only.
I don't know how I should bind if the Items property is readonly?
The ListView class does not support design time binding. An alternative is presented in this project.
I use the following technique to bind data to a ListView.
It supports proper (not text-based) sorting. In the case above, by string, DateTime and integer.
The ListView above was generated with this code:
var columnMapping = new List<(string ColumnName, Func<Person, object> ValueLookup, Func<Person, string> DisplayStringLookup)>()
{
("Name", person => person.Name, person => person.Name),
("Date of birth", person => person.DateOfBirth, person => $"{person.DateOfBirth:dd MMM yyyy}"),
("Height", person => person.HeightInCentimetres, person => Converter.CentimetresToFeetInchesString(person.HeightInCentimetres))
};
var personListview = new ListViewEx<Person>(columnMapping)
{
FullRowSelect = true,
View = View.Details,
Left = 20,
Top = 20,
Width = 500,
Height = 300,
};
var people = new[]
{
new Person("Cathy Smith", DateTime.Parse("1980-05-15"), 165),
new Person("Bill Wentley", DateTime.Parse("1970-10-30"), 180),
new Person("Alan Bridges", DateTime.Parse("1990-03-22"), 190),
};
personListview.AddRange(people);
Controls.Add(personListview);
In the column mapping, you'll notice that you have to specify how to get the item's value (for sorting) as well as a string (for displaying).
Full source:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace GUI
{
public class ListViewEx<T> : ListView
{
public ListViewEx(IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> columnInfo) : base()
{
ColumnInfo = columnInfo;
DoubleBuffered = true;
//Create the columns
columnInfo
.Select(ci => ci.ColumnName)
.ToList()
.ForEach(columnName =>
{
var col = Columns.Add(columnName);
col.Width = -2;
});
//Add the sorter
lvwColumnSorter = new ListViewColumnSorter<T>(columnInfo);
ListViewItemSorter = lvwColumnSorter;
ColumnClick += ListViewEx_ColumnClick;
}
IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> ColumnInfo { get; }
private readonly ListViewColumnSorter<T> lvwColumnSorter;
public void Add(T item)
{
var lvi = Items.Add("");
lvi.Tag = item;
RefreshContent();
}
public void AddRange(IList<T> items)
{
foreach (var item in items)
{
Add(item);
}
}
public void Remove(T item)
{
if (item == null) return;
var listviewItem = Items
.Cast<ListViewItem>()
.Select(lvi => new
{
ListViewItem = lvi,
Obj = (T)lvi.Tag
})
.FirstOrDefault(lvi => item.Equals(lvi.Obj))
.ListViewItem;
Items.Remove(listviewItem);
RefreshContent();
}
public List<T> GetSelectedItems()
{
var result = SelectedItems
.OfType<ListViewItem>()
.Select(lvi => (T)lvi.Tag)
.ToList();
return result;
}
public void RefreshContent()
{
var columnsChanged = new List<int>();
Items
.Cast<ListViewItem>()
.Select(lvi => new
{
ListViewItem = lvi,
Obj = (T)lvi.Tag
})
.ToList()
.ForEach(lvi =>
{
//Update the contents of this ListViewItem
ColumnInfo
.Select((column, index) => new
{
Column = column,
Index = index
})
.ToList()
.ForEach(col =>
{
var newDisplayValue = col.Column.DisplayStringLookup(lvi.Obj);
if (lvi.ListViewItem.SubItems.Count <= col.Index)
{
lvi.ListViewItem.SubItems.Add("");
}
var subitem = lvi.ListViewItem.SubItems[col.Index];
var oldDisplayValue = subitem.Text ?? "";
if (!oldDisplayValue.Equals(newDisplayValue))
{
subitem.Text = newDisplayValue;
columnsChanged.Add(col.Index);
}
});
});
columnsChanged.ForEach(col => { Columns[col].Width = -2; });
//AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
//AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
}
private void ListViewEx_ColumnClick(object sender, ColumnClickEventArgs e)
{
if (e.Column == lvwColumnSorter.ColumnToSort)
{
if (lvwColumnSorter.SortOrder == SortOrder.Ascending)
{
lvwColumnSorter.SortOrder = SortOrder.Descending;
}
else
{
lvwColumnSorter.SortOrder = SortOrder.Ascending;
}
}
else
{
lvwColumnSorter.ColumnToSort = e.Column;
lvwColumnSorter.SortOrder = SortOrder.Ascending;
}
Sort();
}
}
public class ListViewColumnSorter<T> : IComparer
{
public ListViewColumnSorter(IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> columnInfo)
{
ColumnInfo = columnInfo;
}
public int Compare(object x, object y)
{
if (x == null || y == null) return 0;
int compareResult;
var listviewX = (ListViewItem)x;
var listviewY = (ListViewItem)y;
var objX = (T)listviewX.Tag;
var objY = (T)listviewY.Tag;
if (objX == null || objY == null) return 0;
var valueX = ColumnInfo[ColumnToSort].ValueLookup(objX);
var valueY = ColumnInfo[ColumnToSort].ValueLookup(objY);
compareResult = Comparer.Default.Compare(valueX, valueY);
if (SortOrder == SortOrder.Ascending)
{
return compareResult;
}
else if (SortOrder == SortOrder.Descending)
{
return -compareResult;
}
else
{
return 0;
}
}
public int ColumnToSort { get; set; } = 0;
public SortOrder SortOrder { get; set; } = SortOrder.Ascending;
public IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> ColumnInfo { get; }
}
}
Nice binding implementation for ListView
http://www.interact-sw.co.uk/utilities/bindablelistview/source/
Alternatively, you can use DataGridView if you want data binding. Using BindingList and BindingSource will update your DataGrid when new item is added to your list.
var barcodeContract = new BarcodeContract { Barcode = barcodeTxt.Text, Currency = currencyTxt.Text, Price = priceTxt.Text };
list.Add(barcodeContract);
var bindingList = new BindingList<BarcodeContract>(list);
var source = new BindingSource(bindingList, null);
dataGrid.DataSource = source;
And data model class
public class BarcodeContract
{
public string Barcode { get; set; }
public string Price { get; set; }
public string Currency { get; set; }
}
Adding to the answer by #Fidel
If you just want quick auto-mapped columns, add this code to the ListViewEx class:
using System.Reflection;
public ListViewEx() : this(AutoMapColumns()) { }
private static List<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> AutoMapColumns()
{
var mapping = new List<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)>();
var props = typeof(T).GetTypeInfo().GetProperties();
foreach (var prop in props)
{
mapping.Add((
prop.Name,
(T t) => prop.GetValue(t),
(T t) => prop.GetValue(t)?.ToString()
));
}
return mapping;
}
Alternative to Reflection
After some testing, I discovered that using Reflection like in the code above is much slower than direct property access.
In my test, I did 100,000,000 iterations of each. Reflection took 8.967 seconds, direct access took 0.465 seconds.
So I wrote this method to generate the code for the ListViewEx ColumnMapping.
// Given an object, generate columnMapping suitable for passing to the constructor of a ListViewEx control
// Usage: AutoMapColumns_CodeGen(new Person());
private static string AutoMapColumns_CodeGen<T>(T source)
{
var info = typeof(T).GetTypeInfo();
var props = info.GetProperties();
var columns = new List<string>();
foreach (var prop in props)
columns.Add($"\t(\"{prop.Name}\", o => o.{prop.Name}, o=> o.{prop.Name}?.ToString())");
string code = string.Join("\n",
$"var columnMapping = new List<(string ColumnName, Func<{info.Name}, object> ValueLookup, Func<{info.Name}, string> DisplayStringLookup)>() {{",
string.Join(",\n",columns),
"};"
);
return code;
}
Output
var columnMapping = new List<(string ColumnName, Func<Person, object> ValueLookup, Func<Person, string> DisplayStringLookup)>() {
("Name", o => o.Name, o=> o.Name?.ToString()),
("DateOfBirth", o => o.DateOfBirth, o=> o.DateOfBirth?.ToString()),
("HeightInCentimetres", o => o.HeightInCentimetres, o=> o.HeightInCentimetres?.ToString())
};

Categories

Resources