Linq Divide Object into multiple object by Property - c#

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

Related

Can I use LINQ GroupBy to do this more cleanly?

In this contrived example, which closely resembles my real-world problem, I have a data set coming from an external source. Each record from the external source takes the following form:
[Classification] NVARCHAR(32),
[Rank] INT,
[Data] NVARCHAR(1024)
I am looking to build an object where the Rank and Data are patched into a single instance of a response object that contains list properties for the three hard-coded Classification values, ordered by Rank.
I have something that works, but I can't help but think that it could be done better. This is what I have:
public static void Main()
{
IEnumerable<GroupingTestRecord> records = new List<GroupingTestRecord>
{
new GroupingTestRecord { Classification = "A", Rank = 1, Data = "A1" },
new GroupingTestRecord { Classification = "A", Rank = 2, Data = "A2" },
new GroupingTestRecord { Classification = "A", Rank = 3, Data = "A3" },
new GroupingTestRecord { Classification = "B", Rank = 1, Data = "B1" },
new GroupingTestRecord { Classification = "B", Rank = 2, Data = "B2" },
new GroupingTestRecord { Classification = "B", Rank = 3, Data = "B3" },
new GroupingTestRecord { Classification = "C", Rank = 1, Data = "C1" },
new GroupingTestRecord { Classification = "C", Rank = 2, Data = "C2" },
new GroupingTestRecord { Classification = "C", Rank = 3, Data = "C3" },
};
GroupTestResult r = new GroupTestResult
{
A = records.Where(i => i.Classification == "A").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
B = records.Where(i => i.Classification == "B").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
C = records.Where(i => i.Classification == "C").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
};
The source record DTO:
public class GroupingTestRecord
{
public string Classification { get; set; }
public int? Rank { get; set; }
public string Data { get; set; }
}
The destination single class:
public class GroupTestResult
{
public IEnumerable<GroupTestResultItem> A { get; set; }
public IEnumerable<GroupTestResultItem> B { get; set; }
public IEnumerable<GroupTestResultItem> C { get; set; }
}
The distination child class:
public class GroupTestResultItem
{
public int? Rank { get; set; }
public string Data { get; set; }
}
Ouput
{
"A":[
{
"Rank":1,
"Data":"A1"
},
{
"Rank":2,
"Data":"A2"
},
{
"Rank":3,
"Data":"A3"
}
],
"B":[
{
"Rank":1,
"Data":"B1"
},
{
"Rank":2,
"Data":"B2"
},
{
"Rank":3,
"Data":"B3"
}
],
"C":[
{
"Rank":1,
"Data":"C1"
},
{
"Rank":2,
"Data":"C2"
},
{
"Rank":3,
"Data":"C3"
}
]
}
Fiddle
Is there a better way to achieve my goal here?
The same JSON output was achieved using GroupBy first on the Classification and applying ToDictionary on the resulting IGrouping<string, GroupingTestRecord>.Key
var r = records
.GroupBy(_ => _.Classification)
.ToDictionary(
k => k.Key,
v => v.Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank).ToArray()
);
var json = JsonConvert.SerializeObject(r);
Console.WriteLine(json);
which should easily deserialize to the destination single class (for example on a client)
var result = JsonConvert.DeserializeObject<GroupTestResult>(json);
is it possible to get the top level result into a GroupTestResult object?
Build the result from the dictionary
var result = new GroupTestResult {
A = r.ContainsKey("A") ? r["A"] : Enumerable.Empty<GroupTestResultItem>();,
B = r.ContainsKey("B") ? r["B"] : Enumerable.Empty<GroupTestResultItem>();,
C = r.ContainsKey("C") ? r["C"] : Enumerable.Empty<GroupTestResultItem>();,
};
Or this
var result = records.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, x => x.Select(y => new {y.Rank, y.Data})
.OrderBy(y => y.Rank));
Console.WriteLine(JsonConvert.SerializeObject(result));
Full Demo Here

How to get same results from select as foreach when using GroupBy()

I would like to be able to attain the same results that I can get by using foreach on a grouping when using the select method and an anonymous method.
public class ExportData
{
public int Id { get; set; }
public string Colour { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Money { get; set; }
}
public class ExportDataDictionary
{
public IDictionary<string, object> ColumnData { get; set; } = new Dictionary<string, object>();
}
Given the two classes above as an example.
I create some data..
var dataCollection = new List<ExportData>
{
new ExportData { Name = "Name1", Age = 1, Colour = "Blue", Id = 1, Money = 10 },
new ExportData { Name = "Name1", Age = 2, Colour = "Red", Id = 2, Money = 20 },
new ExportData { Name = "Name1", Age = 2, Colour = "Green", Id = 3, Money = 30 },
new ExportData { Name = "Name2", Age = 1, Colour = "Yellow", Id = 4, Money = 40 },
new ExportData { Name = "Name3", Age = 2, Colour = "Blue", Id = 5, Money = 50 },
new ExportData { Name = "Name4", Age = 3, Colour = "Blue", Id = 6, Money = 10 }
};
Next I group this data by, for example, two properties as follows..
var dataGrouping = dataCollection.GroupBy(g => new { g.Name, g.Age });
I then create a list of ExportDataDictionaries and foreach through each group in the grouping, creating a new ExportDataDictionary each time and adding both of the keys to the dictionary.
var data = new List<ExportDataDictionary>();
foreach (var grouping in dataGrouping)
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", grouping.Key.Name);
datadictionary.ColumnData.Add("AGE", grouping.Key.Age);
data.Add(datadictionary);
}
The result is a collection of 5 ExportDataDictionaries with 2 Columns in each one that contain the pair of keys that correspond to each of the groupings.
My attempt to achieve the same with the Select method is shown below.
var data2 = new List<ExportDataDictionary>();
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
data2.Add(datadictionary);
return data2;
});
The result is of the type:
mydata = {System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<<>f__AnonymousType0<string, int>, ConsoleApp2.Program.ExportData>, System.Collections.Generic.List<ConsoleApp2.Program.ExportDataDictionary>>}
and it contains 5 items and each item contains 10 dictionaries. The 5 dictionaries that I expect are there with the same values as when using foreach but then there are 2 copies of each. I believe that this must be because it is creating the dictionaries for both of the keys used in the grouping. So, I am wondering how to only do this for one of the keys or just each group in the collection?
The requirement is that mydata should contain the same result as obtained by foreach in data variable
Any help much appreciated :)
Just Add .ToList() at the end of your last statement remove the data2.Add(datadictionary); statement and only return the datadictionary return datadictionary; like this
var mydata = dataGrouping.Select(d =>
{
var datadictionary = new ExportDataDictionary();
datadictionary.ColumnData.Add("NAME", d.Key.Name);
datadictionary.ColumnData.Add("AGE", d.Key.Age);
return datadictionary;
}).ToList();
I have run your code and checked and saw that mydata contains 5 items, and each item contains 2 ColumnData members.
Actually, your Linq query is only executed when you call the .ToList() function

(List<Dictionary<Object, Object>> in Linq to extract data

I have a data definition
I Deserialize JSON to this object
#return is JSON
JsonConvert.DeserializeObject<List<Dictionary<Object, Object>>>(utils.RemoveJsonOuterClass("GetTable", JsonConvert.DeserializeObject(#return).ToString()));
olist = [
[{
"item": 1
"Name "One"
}],
[{
"item": 2
"Name "Two"
}],
[{
"item": 1
"Name "One Two"
}]
];
This is a List<Dictionary<Object, Object>>
I need to find all of the items where "item" == 1.
Can I Use Linq? or is there any other way while using a large amount of data?
First: Your json is not correct fix that.
A colon should be present between Name and value.
A comma should be present after item value
and then change your code as below
//Create a class matching response object
public class ResponseItem
{
[JsonProperty("item")]
public int Item { get; set; }
public string Name { get; set; }
}
var responseJson = utils.RemoveJsonOuterClass("GetTable",
JsonConvert.DeserializeObject(#return).ToString();
var responseData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<List<ResponseItem, ResponseItem>>>(responseJson);
Then use foreach with Where and apply condition
foreach (var responseObject in responseData.Where(x=>x.First().Item.Equals(1)))
{
}
Where is deferred execution and on each loop, it returns an object.
Here is the screenshot of my local execution.
Don't know if u're right with the object type. But the task is easy to solve:
static void Main(string[] args)
{
// Build the object
List<Dictionary<int, TestObject>> list = new List<Dictionary<int, TestObject>>();
// fill it with dictionaries
list.Add(new List<TestObject>()
{
new TestObject(){ Id = 1, Name = "One" },
new TestObject() { Id = 2, Name = "Two" },
new TestObject() { Id = 3, Name = "Three" }
}.ToDictionary(d => d.Id));
list.Add(new List<TestObject>()
{
new TestObject() { Id = 2, Name = "Two" },
new TestObject() { Id = 3, Name = "Three" }
}.ToDictionary(d => d.Id));
list.Add(new List<TestObject>()
{
new TestObject(){ Id = 1, Name = "One" },
new TestObject() { Id = 2, Name = "Two" }
}.ToDictionary(d => d.Id));
// Let's build a single list to work with
IEnumerable<TestObject> completeList = list.SelectMany(s => s.Values);
// aaaand filter it
IEnumerable<TestObject> filteredList = completeList.Where(l => l.Id == 1);
}
public class TestObject
{
public int Id { get; set; }
public string Name { get; set; }
}
Most part is initialization ;-)

Search inside a collection based on a given list of items

Lets say I am given a list as input:
IList<string> availableFruits = {orange, banana}
I have a fruit entity that looks like this:
public Fruit
{
public int ID { get; set; }
public string name { get; set; }
}
we define some fruits:
var f1 = new Fruit { Id = 1, name = "apple" };
var f2 = new Fruit { Id = 2, name = "banana" };
var f3 = new Fruit { Id = 3, name = "orange" };
var f4 = new Fruit { Id = 4, name = "pineapple" };
Finally, we have a menu where these fruits go:
public class Menu
{
public int ID { get; set; }
public ICollection<Fruit> fruits;
}
var menus = new List<Menu>
{
new Menu {ID = 0, fruits = new List<Fruit> { f1, f2 } },
new Menu {ID = 1, fruits = new List<Fruit> { f1, f3, f4 } },
new Menu {ID = 2, fruits = new List<Fruit> { f1, f4 } },
new Menu {ID = 3, fruits = new List<Fruit> { f2 } },
};
I need a LINQ query that searches in the menus and returns all menus that contains 'ANY' of the fruits passed in the avaiableFruits list. Thus in this case it will return menus with ID's 0, 1 and 3.
See it in this way: keeps the menus that any of the fruit in fruits property is in availableFruits:
var availables = menus.Where(m => m.fruits.Any(f => availableFruits.Contains(f.name)));
See the code below:
public IEnumerable<Menu> select_menus(IEnumerable<Menu> menus, IEnumerable<Fruit> fruits) {
return menus.Where(m => m.fruits.Intersect(fruits).Any());
}

Intersect two generic lists by dynamic properties

i have two generic lists with a few properties to compare but i want that the key identifiers are dynamic by a List<string>.
So lets say we have the class:
class A
{
string Name { get; set; }
string Color1 { get; set; }
string Color2 { get; set; }
string Length { get; set; }
}
The user now can select from an user interface which properties of two lists of those objects need to overlap so that a correct pair is selected. This is stored in a List<string>. As example, if the list string contains "Name" and "Color1" there will be only objects returned where "Name" and "Color1" are overlapping.
I was trying to write a function, but unfortunately i'm not sure which collection i should cast the generic lists to and how do i apply the names of the properties on those? If the name of the "identificators" were always the same, it wouldn't be a problem with Linq/Lambda ;)
Thanks in advance
You need to use reflection for this. This works:
public class A
{
public string Name { get; set; }
public string Color1 { get; set; }
public string Color2 { get; set; }
public string Length { get; set; }
public static IEnumerable<A> Intersecting(IEnumerable<A> input, List<string> propertyNames)
{
if(input == null)
throw new ArgumentNullException("input must not be null ", "input");
if (!input.Any() || propertyNames.Count <= 1)
return input;
var properties = typeof(A).GetProperties();
var validNames = properties.Select(p => p.Name);
if (propertyNames.Except(validNames, StringComparer.InvariantCultureIgnoreCase).Any())
throw new ArgumentException("All properties must be one of these: " + string.Join(",", validNames), "propertyNames");
var props = from prop in properties
join name in validNames.Intersect(propertyNames, StringComparer.InvariantCultureIgnoreCase)
on prop.Name equals name
select prop;
var allIntersecting = input
.Select(a => new {
Object = a,
FirstVal = props.First().GetValue(a, null),
Rest = props.Skip(1).Select(p => p.GetValue(a, null)),
})
.Select(x => new {
x.Object, x.FirstVal, x.Rest,
UniqueValues = new HashSet<object>{ x.FirstVal }
})
.Where(x => x.Rest.All(v => !x.UniqueValues.Add(v)))
.Select(x => x.Object);
return allIntersecting;
}
}
Sample data:
var aList = new List<A> {
new A { Color1 = "Red", Length = "2", Name = "Red" }, new A { Color1 = "Blue", Length = "2", Name = "Blue" },
new A { Color1 = "Red", Length = "2", Name = "A3" }, new A { Color1 = "Blue", Length = "2", Name = "A3" },
new A { Color1 = "Red", Length = "3", Name = "Red" }, new A { Color1 = "Blue", Length = "2", Name = "A6" },
};
var intersecting = A.Intersecting(aList, new List<string> { "Color1", "Name" }).ToList();

Categories