Continuing from the Q&As dealing with looping through an object's properties (Using LINQ to loop through inner class properties in outer class collection), is it possible to populate a dictionary:
Dictionary<ComplexKey,IEnumerable<double>> answer;
For example,
answer[1,1,1,"MeasurementA"] = {2.0, 2.1}
answer[1,1,1,"MeasurementB"] = {3.0, 3.1}
answer[1,1,1,"MeasurementC"] = {4.0, 4.1}
answer[1,1,2,"MeasurementA"] = {5.0, 5.1}
Given the structure:
class MainClass {
List<Class1> list
}
class Class1 {
// a number of fields including...
int PropertyA { get; set; }
int PropertyB { get; set; }
Dictionary<int, Class2> dict { get; set; }
}
class Class2 {
// a number of fields all of type double...
double MeasurementA { get; set; }
double MeasurementB { get; set; }
double MeasurementC { get; set; }
}
struct ComplexKey {
public int class1PropA;
public int class1PropB;
public int class1DictKey;
public string class2PropName;
}
Given data:
MainClass mainClass = new MainClass();
mainClass.list = new List<Class1>() {
new Class1() {
PropertyA = 1,
PropertyB = 1,
dict = new Dictionary<int,Class2>() {
{ 1, new Class2() { MeasurementA = 2.0, MeasurementB = 3.0, MeasurementC = 4.0 }},
{ 2, new Class2() { MeasurementA = 5.0, MeasurementB = 6.0, MeasurementC = 7.0 }}
}
},
new Class1() {
PropertyA = 1,
PropertyB = 1,
dict = new Dictionary<int,Class2>() {
{ 1, new Class2() { MeasurementA = 2.1, MeasurementB = 3.1, MeasurementC = 4.1 }},
{ 2, new Class2() { MeasurementA = 5.1, MeasurementB = 6.1, MeasurementC = 7.1 }}
}
}
};
Noting in this example, Class1.PropertyA and Class1.PropertyB are consistently set to "1" for brevity. (This is not the case in the real world data set).
I believe populating the Dictionary "answer" would require, grouping the mainclass.list by PropertyA, PropertyB, and dict.Key before accessing the properties and values within dict.Values (i.e. instances of Class2).
I thought LINQ was the answer but have been stuck for many weeks. Any direction would be appreciated.
Thanks & regards
Shannon
Based on the question you linked, it seems you're looking for something like this:
var answer =
typeof(Class2).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.SelectMany(pi => mainClass.list.SelectMany(c1 =>
c1.dict.Select(i => new {
c1.PropertyA,
c1.PropertyB,
i.Key,
i.Value}))
.Select(p => new {
Key = new ComplexKey {
class1PropA = p.PropertyA,
class1PropB = p.PropertyB,
class1DictKey = p.Key,
class2PropName = pi.Name},
Value = (double)pi.GetValue(p.Value)}))
.GroupBy(grp => grp.Key)
.ToDictionary(grp => grp.Key, grp => grp.Select(x => x.Value));
answer is now
Note: If you use a custom type (like ComplexKey) as key in a Dictionary, you should implement a proper GetHashCode and Equals method, since these methods are used to distinguish the keys inside the Dictionary.
Related
I have the following enums and class:
public enum SymbolTypeEnum
{
[Description("Forex")]
forex = 0,
[Description("Metals")]
metals = 1,
[Description("Commodities")]
commodities = 2,
[Description("Indices")]
indices = 3,
[Description("Cryptocurrency")]
cryptocurrency = 4,
[Description("Other")]
other = 5
}
public enum SymbolClassificationEnum
{
[Description("None")]
none = 0,
[Description("Major")]
major = 1,
[Description("Minor")]
minor = 2,
[Description("Exotic")]
exotic = 3
}
public class SymbolSettingsModel
{
[PrimaryKeyAttribute, Unique]
public string symbolName { get; set; }
public SymbolTypeEnum symbolType { get; set; }
public SymbolClassificationEnum symbolClassification { get; set; }
}
I have a List<SymbolSettingsModel> symbolSettings;
My goal is to generate a Dictionary<SymbolTypeEnum, List<SymbolDescr>> typeSymbolsSettingsData;
Where SymbolDescr is:
class SymbolDescr
{
public string symbol { get; set; }
public int classification { get; set; }
}
The idea is to group them by symbolType and use it as a Key for the dictionary, and generate a list of SymbolDescr. Here is my code so far:
typeSymbolsSettingsData = symbolSettings.GroupBy(p => p.symbolType)
.ToDictionary(p => p.Key, p => p.ToList());
I am stuck at this point, do you have any ideas how I can do this ?
You could use Select to project your SymbolSettingsModel instances into SymbolDescr instances:
typeSymbolsSettingsData = symbolSettings
.GroupBy(p => p.symbolType)
.ToDictionary(
p => p.Key,
p => p.Select(x => new SymbolDescr
{
symbol = x.symbolName,
classification = (int)x.symbolClassification
})
.ToList());
You should also try to comply with the .Net property naming convention which mandates the use of PascalCase.
I have below class structure and a list of CollectionInstance
public class CollectionInstance
{
public string Name { get; set; }
public List<CollectionProperty> CollectionProperties { get; set; }
}
public class CollectionProperty
{
public string Name { get; set; }
public object Value { get; set; }
public string DataType { get; set; }
}
Here is list of CollectionInstance. Currently it has only two data types double and string, but I have more data types
var lstCollectionInstances = new List<CollectionInstance>
{
new CollectionInstance
{
Name = "A",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P1", Value = 10, DataType = "Double"}
}
},
new CollectionInstance
{
Name = "A",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P2", Value = "H1", DataType = "String"}
}
},
new CollectionInstance
{
Name = "B",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P1", Value = 20, DataType = "Double"}
}
},
new CollectionInstance
{
Name = "B",
CollectionProperties = new List<CollectionProperty>
{
new CollectionProperty {Name = "P2", Value = "H2", DataType = "String"}
}
},
};
Now my goal to fetch all the different data type and filter list of CollectionInstance based on the data type. May be a dictionary or could be other collection as well, where I should store data type as key and filtered CollectionInstance as a value.
I tried below, but what could be the best way?
var dictionary = new Dictionary<string, List<CollectionInstance>>();
var dataTypesGroups = lstCollectionInstances
.SelectMany(x => x.CollectionProperties).GroupBy(x => x.DataType);
foreach (var dataType in dataTypesGroups)
{
dictionary.Add(dataType.Key, GetFilterData(lstCollectionInstances, dataType.Key));
}
private static List<CollectionInstance> GetFilterData(IEnumerable<CollectionInstance> lst, string dataType)
{
return lst.Where(x => x.CollectionProperties.Any(y => y.DataType == dataType)).ToList();
}
You could keep reference to parent CollectionInstance when grouping and reuse that when selecting results:
lstCollectionInstances
.SelectMany(x => x.CollectionProperties, (i, c) => new {CollectionInstance = i, CollectionProperty = c})
.GroupBy(x => x.CollectionProperty.DataType)
.ToDictionary(c => c.Key, c => c.Select(d => d.CollectionInstance) )
UPD
here we leverage this overload of .SelectMany(). So Instead of List<CollectionProperty> you end up having List<Tuple<CollectionInstance,CollectionProperty>> (well, i opted for anonymous type, but this does not matter much). You basically enhance each child object with reference to its parent. And since all these are just references - you don't trade a lot of memory for having it.
And when you group it - you get an option to not select the CollectionProperty, but rather the parent object directly.
I hope this makes sense
Build a dictionary that for each data type stores a list of instances with a property of the key data type.
var result = instances
.SelectMany(x => x.Properties)
.Select(x => x.DataType)
.Distict()
.ToDictionary(x => x, x => GetInstancesWithPropertyOfType(x, instances));
Given the following is defined:
public class Instance
{
public string Name { get; set; }
public List<Property> Properties { get; set; }
}
public class Property
{
public string Name { get; set; }
public object Value { get; set; }
public string DataType { get; set; }
}
List<Instance> GetInstancesWithPropertyOfType(string dataType, IEnumerable<Instance> instances) =>
instances.Where(x => x.Properties.Any(y => y.DataType == dataType)).ToList();
Personally think that using LINQ on this just makes it more unreadable and harder to understand. This is basically a two loop operation; for each x in instances/foreach y in x.properties/add x to dictionary indexed by y.z and would be most easily understood by keeping it as such. This minimizes the amount of work done by the framework too; here we create no unnecessary extra objects, lists, groupings etc in the quest to enumerate a 2-deep object hierarchy and create a dictionary, and even a coder who never saw LINQ can understand it:
var dictionary = new Dictionary<string, List<CollectionInstance>>();
foreach (var ci in lstCollectionInstances){
foreach(var cp in ci.CollectionProperties){
if(!dictionary.ContainsKey(cp.DataType))
dictionary[cp.Key] = new List<CollectionInstance>();
dictionary[cp.Key].Add(ci);
}
}
LINQ is a hammer; not every problem is a nail
I have a list for example List<string> ListProviderKeys that has some values in it.
I also have a second list from a class below, for example List<ChangesSummary> SecondList;
public class ChangesSummary
{
public string TableName { get; set; }
public string ProviderKey { get; set; }
public string ProviderAdrsKey { get; set; }
public string ProviderSpecialtyKey { get; set; }
public string FieldName{ get; set; }
}
Imagine the values that first list holds is the same kind of values we put in ProviderKey field in the second list.
Now What I want is to trim down the second list to only have values that their ProviderKey IS NOT already in the first list.
How Can I do that? I know the operator Except but not sure how to apply it in this situation!
The best I can think of is :
A) Create dictionary and use its fast lookups
B) Use LINQ .Where method with .ContainsKey() on this dictionary which internally uses Hashtable and performs quick lookups.
This should reduce search complexity to almost O(1) rather than O(N) ro worse (when we use LINQ .Where() with .Any() or .Contains() and that leads to nested loops).
From MSDN page :
The Dictionary generic class provides a mapping from a set of keys to
a set of values. Each addition to the dictionary consists of a value
and its associated key. Retrieving a value by using its key is very
fast, close to O(1), because the Dictionary class is implemented as a
hash table.
So what we can do is :
Dictionary<string, string> dict = ListProviderKeys.ToDictionary(s => s);
var newList = SecondList.Where(e => !dict.ContainsKey(e.ProviderKey)).ToList();
Here is a very simple, short, but complete example illustrating it and also testing its performance :
class Person
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<int> ints = new List<int>();
List<Person> People = new List<Person>(1000);
for (int i = 0; i < 7500; i++)
{
ints.Add(i);
ints.Add(15000 - i - 1);
}
for (int i = 0; i < 45000; i++)
People.Add(new Person() { Id = i });
Stopwatch s = new Stopwatch();
s.Start();
// code A (feel free to uncomment it)
//Dictionary<int, int> dict = ints.ToDictionary(p => p);
//List<Person> newList = People.Where(p => !dict.ContainsKey(p.Id)).ToList();
// code B
List<Person> newList = People.Where(p => !ints.Contains(p.Id)).ToList();
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds);
Console.WriteLine("Number of elements " + newList.Count);
Console.ReadKey();
}
On release mode results are :
Both code A & code B outputs 30 000 elements but :
It took more than 2000 ms with code B and only 5 ms with code A
public class Programm
{
public static void Main()
{
List<ChangesSummary> summaries = new List<ChangesSummary>();
summaries.Add(new ChangesSummary()
{
FieldName = "1",
ProviderKey = "Test1",
});
summaries.Add(new ChangesSummary()
{
FieldName = "2",
ProviderKey = "Test2",
});
summaries.Add(new ChangesSummary()
{
FieldName = "3",
ProviderKey = "Test3",
});
List<string> listProviderKeys = new List<string>();
listProviderKeys.Add("Test1");
listProviderKeys.Add("Test3");
var res = summaries.Where(x => !listProviderKeys.Contains(x.ProviderKey));
res.ToList().ForEach(x => Console.WriteLine(x.ProviderKey));
Console.ReadLine();
}
}
public class ChangesSummary
{
public string TableName { get; set; }
public string ProviderKey { get; set; }
public string ProviderAdrsKey { get; set; }
public string ProviderSpecialtyKey { get; set; }
public string FieldName { get; set; }
}
I think in this case simple Where would be easier and more readable to apply.
var first = new List<string> { "a" };
var second = new List<ChangesSummary>()
{
new ChangesSummary() { ProviderKey = "a" },
new ChangesSummary() { ProviderKey = "b" }
};
var result = second.Where(item => !first.Contains(item.ProviderKey));
// result
// .ToList()
// .ForEach(item => Console.WriteLine(item.ProviderKey));
I believe this will work:
List<ChangesSummary> ExceptionList = SecondList.
Where(x => !ListProviderKeys.Any(key => x.ProviderKey == key)).ToList();
I am reading many sites to get a better idea of Linq -Group Join.
var customers = new Customer[]
{
new Customer{Code = 5, Name = "Sam"},
new Customer{Code = 6, Name = "Dave"},
new Customer{Code = 7, Name = "Julia"},
new Customer{Code = 8, Name = "Sue"}
};
// Example orders.
var orders = new Order[]
{
new Order{KeyCode = 5, Product = "Book"},
new Order{KeyCode = 6, Product = "Game"},
new Order{KeyCode = 7, Product = "Computer"},
new Order{KeyCode = 7, Product = "Mouse"},
new Order{KeyCode = 8, Product = "Shirt"},
new Order{KeyCode = 5, Product = "Underwear"}
};
var query = customers.GroupJoin(orders,
c => c.Code,
o => o.KeyCode,
(c, result) => new Result(c.Name, result));//why mention c here??
// Enumerate results.
foreach (var result in query)
{
Console.WriteLine("{0} bought...", result.Name);
foreach (var item in result.Collection)
{
Console.WriteLine(item.Product);
}
}
I couldnt understand why it gives (c, result) ? what if wrote as (c,o) ?
Can anyone share ideas on this?
These are just names of arguments passed to Func. You can use any name you want if that makes code more clear for you ie:
var query = customers.GroupJoin(orders,
c => c.Code,
o => o.KeyCode,
(something1, something2) => new Result(something1.Name, something2));
as it will just pass arguments from two previous Funcs into last one that is Func<TOuter, IEnumerable<TInner>, TResult>, so in that case Func<Customer, IEnumerable<Order>, Result>.
It's the same as with such situation:
public Result DoStuff(Order nameMeAnyWayYouWant, Customer meToo)
{
//do stuff here
}
Code from question is from: http://www.dotnetperls.com/groupjoin
I'm adding model classes that author skipped if anyone wants to elaborate and in case dotnetperls.com went down:
class Customer
{
public int Code { get; set; }
public string Name { get; set; }
}
class Order
{
public int KeyCode { get; set; }
public string Product { get; set; }
}
class Result
{
public string Name { get; set; }
public IEnumerable<Order> Collection { get; set; }
public Result(string name, IEnumerable<Order> collection)
{
this.Name = name;
his.Collection = collection;
}
}
The following code takes a collection of class C and creates a collection consisting of the values of two properties A and B. A and B are put inside the same collection:
class A
{
public int x { get; set; }
}
class B
{
public int x { get; set; }
}
class C
{
public A A { get; set; }
public B B { get; set; }
}
..........
var items = new List<C>()
{
new C()
{
A = new A() {x = 1},
B = new B() {x = 2}
},
new C()
{
A = new A() {x = 3},
B = new B() {x = 4}
},
};
var qA = from item in items
select (object)item.A;
var qB = from item in items
select (object)item.B;
var qAll = qA.Concat(qB);
Is it possible to do this with one query?
If you really want to flatten properties like that, you can feed arrays to SelectMany():
var qAll = items.SelectMany(item => new object[] { item.A, item.B });
You could use a ForEach:
var qAll = new List<object>();
items.ForEach(item => { qAll.Add(item.A); qAll.Add(item.B) });