Cast<> for hierarchical data structure - c#

Here is an example
class A
{
public string Text = "";
public IEnumerable<A> Children = new List<A>();
}
class B
{
public string Text;
public IEnumerable<B> Children = new List<B>();
}
static void Main(string[] args)
{
// test
var a = new A[]
{
new A() { Text = "1", Children = new A[]
{
new A() { Text = "11"},
new A() {Text = "12"},
}},
new A() { Text = "2", Children = new A[]
{
new A() {Text = "21", Children = new A[]
{
new A() {Text = "211"},
new A() {Text = "212"},
}},
new A() {Text = "22"},
}},
};
B[] b = a ...; // convert a to b type
}
My original problem is hierarchical data A in Model, which has to be displayed in the View, thus I have to create B type in ViewModel with INotifyPropertyChanged, etc. Converting from A to B for hierarchical data seems more complicated than a simple linq Cast<>.
If somebody is going to try to code, then here is the nice flattering function, can be used like this:
foreach (var item in b.Flatten(o => o.Children).OrderBy(o => o.Text))
Console.WriteLine(item.Text);

Use a recursive function that can translate an A into a B.
B BFromA(A a) {
return new B { Text = a.Text, Children = a.Children.Select(BFromA).ToList() };
}
Usage:
B[] b = a.Select(BFromA).ToArray(); // convert a to b type

Related

Linq Divide Object into multiple object by Property

I am just wondering if there is a way to split a list item into two list items based on an object property using C# LINQ.
For example:
{
"ItemName": "Test Item",
"SubItemName": [
{
"Sub-Item-1",
"Sub-Item-2",
}
]
}
how to split this object by subItemName like:
{
"ItemName": "Test Item Name",
"SubItemName": "Sub-Item-1"
},
{
"ItemName": "Test Item Name",
"SubItemName": "Sub-Item-2",
}
Yes, that is SelectMany. Let's look at a simple example:
class C
{
public string Name { get; set; }
public IEnumerable<string> Subs { get; set; }
}
Now let's make a bunch of them.
var cs = new List<C> {
new C { Name = "Alice", Subs = new List<string> { "Orange", "Green" }},
new C { Name = "Bobby", Subs = new List<string> { "Red", "Blue" }}
}
That is, we have
Alice
Orange
Green
Bobby
Red
Blue
You want a new list that is
Alice
Orange
Alice
Green
Bobby
Red
Bobby
Blue
Right?
Any time you want to "flatten" a list-of-lists you use SelectMany. Either in comprehension form:
var query =
from c in cs
from s in c.Subs
select new C { Name = c.Name, Subs = new List<string> { s } };
Or in fluent form
var query =
cs.SelectMany(
c => c.Subs,
(c, s) => new C { Name = c.Name, Subs = new List<string> { s } });
which as you can see has the same structure, just a little harder to read.
If you then need a list out the other end:
var newList = query.ToList();
And you're done.
A single item:
var item = new {
ItemName = "Test Item",
SubItemName = new string[]
{
"Sub-Item-1",
"Sub-Item-2",
}
};
I guess you have a collection of such items:
var items = new [] { item, /* other items */ };
And here is the LINQ:
var result =
from i in items
from si in i.SubItemName
select new
{
ItemName = i.ItemName,
SubItemName = si
};
use the LINQ .Select() Method on the collection
You can use the .Select() Method to project the things in your SubItems collection into a completely new form.
Since you didn't post the code of what you have so far, I will answer in general. Check out this code. :
public class WhateverYouHaveThere
{
public string Name { get; set; }
public List<string> SubItemName { get; set; }
}
class Program
{
static void Main(string[] args)
{
//the thing you have
var theThingYouHave = new WhateverYouHaveThere() {
Name = "Test Item",
SubItemName = new List<string>() {
"Sub-Item 1",
"SubItem 2" }
};
//split the thing into a new list
var theResultingListYouWant = theThingYouHave.SubItemName
.Select(p => new WhateverYouHaveThere() {
Name = theThingYouHave.Name,
SubItemName = new List<string>() { p } }
).ToList();
}
}
please accept as answer if that worked for you
greetings, Mike

What does a GroupBy KeySelector function look like?

I would like to break the KeySelector function of GroupBy into its own method. The KeySelector code isn't right. One major problem is Widget.foo isn't matching on "Green" but I think it should be.
Widget.cs
public class Widget
{
public string foo { get; set; }
public double bar { get; set; }
public bool fee { get; set; }
}
Program.cs
static void Main(string[] args)
{
var widgets = new List<Widget>()
{
new Widget() { foo = "red" , bar = 1.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = false },
new Widget() { foo = "green", bar = 3.0, fee = false },
new Widget() { foo = "blue" , bar = 4.0, fee = true }
};
var gb = widgets.GroupBy(
w => GenerateGroupByKey(),
w => w,
(prop, groupedWidgets) => new
{
GroupedWidgets = groupedWidgets
}
).ToList();
}
KeySelector
static Func<Widget, object> GenerateGroupByKey()
{
Func<Widget, object> s = delegate(Widget widget)
{
return new { widget.foo };
};
return s;
}
You must pass delegate to parameters, so just call the function and return the delegate.
var gb = widgets.GroupBy(
GenerateGroupByKey(),
w => w,
(prop, groupedWidgets) => new
{
GroupedWidgets = groupedWidgets
}
).ToList();
Group by you using have parameters like below
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
Func<TKey, IEnumerable<TElement>, TResult> resultSelector
when you are writing w => GenerateGroupByKey()
you are creating a new FUNC with input parameter as GenerateGroupByKey()
But GenerateGroupByKey() itself is a FUNC
static Func<Widget, object> GenerateGroupByKey()
{
Func<Widget, object> s = delegate(Widget widget)
{
return new { widget.foo };
};
return s;
}
And you are not invoking that FUNC
Solution:
Do not create another FUNC. Pass GenerateGroupByKey() directly :)
static void Main(string[] args)
{
var widgets = new List<Widget>()
{
new Widget() { foo = "red" , bar = 1.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = false },
new Widget() { foo = "green", bar = 3.0, fee = false },
new Widget() { foo = "blue" , bar = 4.0, fee = true }
};
var gb = widgets.GroupBy(
GenerateGroupByKey(), // Your FUNC method
w => w,
(prop, groupedWidgets) => new
{
GroupedWidgets = groupedWidgets
}
).ToList();
}
Your grouping does not work because inside GenerateGroupByKey() method you create new object.
Objects are equals if they have equal memory references. You can review more details about Equals here.
For what you create new object like new { widget.foo }?
You can just return string instead of object.
Try to use delegate like it:
static Func<Widget, object> GenerateGroupByKey()
{
Func<Widget, object> s = delegate(Widget widget)
{
return widget.foo;
};
return s;
}

How to populate a List of Class of string?

A dumb C# question, if I have this code:
public class singleString
{
public string ss { get; set; }
}
List<singleString> manyString = new List<singleString>();
How can I populate manyString to something like {"1", "2", "3"} ?
You can do something like this:
List<singleString> manyString = new List<singleString>()
{
new singleString(){ss="1"},
new singleString(){ss="2"},
new singleString(){ss="3"},
};
Define implicit conversion operator
public class singleString {
public string ss { get; set; }
public static implicit operator singleString(string s) {
return new singleString { ss = s };
}
}
Then, use List initializer
var manyString = new List<singleString>() { "1", "2", "3" };
You can also initialize an array using the same operator
singleString[] manyString = { "1", "2", "3" };

Linq - group by using the elements inside an array property

I have a number of objects and each object has an array, I would like to group these objects by the values inside the array, so conceptually they look as follows:
var objects = new []{
object1 = new object{
elements = []{1,2,3}
},
object2 = new object{
elements = []{1,2}
},
object3 = new object{
elements = []{1,2}
},
object4 = new object{
elements = null
}
}
after grouping:
group1: object1
group2: object2,object3
group3: object4
somethings that I have tried:
actual classes:
public class RuleCms
{
public IList<int> ParkingEntitlementTypeIds { get; set; }
}
var rules = new List<RuleCms>()
{
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1}
},
new RuleCms()
{
ParkingEntitlementTypeIds = null
}
};
var firstTry = rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds, rules = g })
.Where(x => x.Key.entitlementIds !=null && x.Key.entitlementIds.Equals(x.Key.rules.ParkingEntitlementTypeIds));
var secondTry =
rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds ?? new List<int>(), rules = g })
.GroupBy(x => !x.Key.entitlementIds.Except(x.Key.rules.ParkingEntitlementTypeIds ?? new List<int>()).Any());
You can use IEqualityComparer class. Here is the code:
class MyClass
{
public string Name { get; set; }
public int[] Array { get; set; }
}
class ArrayComparer : IEqualityComparer<int[]>
{
public bool Equals(int[] x, int[] y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(int[] obj)
{
return string.Join(",", obj).GetHashCode();
}
}
Then
var temp = new MyClass[]
{
new MyClass { Name = "object1", Array = new int[] { 1, 2, 3 } },
new MyClass { Name = "object2", Array = new int[] { 1, 2 } },
new MyClass { Name = "object3", Array = new int[] { 1, 2 } },
new MyClass { Name = "object4", Array =null }
};
var result = temp.GroupBy(i => i.Array, new ArrayComparer()).ToList();
//Now you have 3 groups
For simple data that really is as simple as your example you could do this:
.GroupBy(x => string.Join("|", x.IDS))
.Select(x => new
{
IDS = x.Key.Split('|').Where(s => s != string.Empty).ToArray(),
Count = x.Count()
});

Linq to return a new object with a selection from the list

I have the following object structure:
public class A
{
public string ID { get; set; }
public IList<B> Values { get; set; }
}
public class B
{
public string Code { get; set; }
public string DisplayName { get; set; }
}
public List<A> IDs;
I would like to use Linq to query B and return a single instance of A with the single element of B in values. Is that possible? I currently do this with a foreach but I am thinking Linq would be neater.
foreach (A a in IDs)
{
foreach (B b in a.Values)
{
if (b.Code == code)
{
return (new A()
{
ID = a.ID,
Values = new List<B>()
{
new B()
{
Code = b.Code,
DisplayName = b.DisplayName
}
}
});
}
}
}
Try this:
IDs.Where(a=>a.ID = id)
.Select(a => new A()
{
ID = a.ID,
Values = new List<B>()
{
new B()
{
Code = a.Values.First().Code,
DisplayName = a.Values.First().DisplayName
}
}
});
In LINQ with the query-syntax:
return (from a in IDs
from b in a.Values
where b.Code == code
select (new A
{
ID = a.ID, Values = new List<B>
{
new B
{
Code = b.Code,
DisplayName = b.DisplayName
}
}
})).FirstOrDefault();
Run the following in LinqPad (LinqPad.com)
void Main()
{
List<A> IDs= new List<A>() {
new A() { ID = "1", Values = new List<B>() {
new B { Code = "1", DisplayName = "1"},
new B { Code = "2", DisplayName = "2"},
new B { Code = "3", DisplayName = "3"} } },
new A() { ID = "4", Values = new List<B>() {
new B { Code = "4", DisplayName = "4"},
new B { Code = "5", DisplayName = "5"},
new B { Code = "6", DisplayName = "6"} } },
new A() { ID = "7", Values = new List<B>() {
new B { Code = "7", DisplayName = "7"},
new B { Code = "8", DisplayName = "8"},
new B { Code = "9", DisplayName = "9"} } }
};
A result = IDs.Where(a => a.Values.Any(b=> b.Code == "4")).FirstOrDefault();
result.Dump();
result = IDs.FirstOrDefault(a => a.Values.Any(b=> b.Code == "8"));
result.Dump();
}
// Define other methods and classes here
public class A
{
public string ID { get; set; }
public IList<B> Values { get; set; }
}
public class B
{
public string Code { get; set; }
public string DisplayName { get; set; }
}
You get this:
Prior edits follow:
With the edit to the question:
A result = IDs.Where(a => a.Values.Any(b=> b.Code == code)).FirstOrDefault();
Original answer below
The following will return the first A element where ID = id
A result = IDs.Where(a => a.ID == id).FirstOrDefault();
This makes it a list
List<A> result = IDs.Where(a => a.ID == id).FirstOrDefault().ToList();

Categories