How to access the member of an IGrouping? - c#

The two lines below return an IGrouping<string, DataRow>:
var mbVals = GetMBValues_MBvsPU(mbRptDataPkg);
var puVals = GetPUValues_MBvsPU(puRptDataPkg);
I'd though that you could access the grouping's data like this mbVals[StringKey] but that doesn't look possible. I can do a foreach over the DataRows but it just seems to me one should be able to easily access through a linq expression somehow.
What I'd like to do is compare fields in the datarows from one with fields from datarows of the other through the keys.
Any thoughts?
Thanks!

IGrouping<> is IEnumerable<> so you use the ElementAt() extension method.
But for the situation you describe, you may be better off using Zip() to unify the two groups (if the items are in the same order and match exactly) or using Join() if they don't.

An instance that implements IGrouping<T, U> has a (one) key of type T. Since you want to compare based on keys (plural), an IGrouping<string, DataRow> isn't what you need.
You need an IEnumerable<IGrouping<string, DataRow>> or an ILookup<string, DataRow>. Something that has many keys.
ILookup<string, DataRow> source1 = GetSource1();
ILookup<string, DataRow> source2 = GetSource2();
var BothKeyed =
(
from key in source1.Select(g => g.Key).Union(source2.Select(g => g.Key))
select new
{
Key = key,
In1 = source1[key],//note, In1 may be empty.
In2 = source2[key] //note, In2 may be empty.
}
).ToLookup(x => x.Key);

Related

How to do partial search on multiple keys dictionary

I create a multiple keys dictionary as (registrantsFields is enumerable type)
var registrantsRepository = registrantsFields.ToDictionary(c => Tuple.Create(c.RegistrantID, c.FieldID, c.DataID));
I use ContainsKey to search the dictionary as
if (registrantsRepository.ContainsKey(Tuple.Create(registrantId, fieldId, dataId)))
So far it works fine.
But I want to search the dictionary with only 2 keys, i.e. what dictionary contains for certain registrantId and fieldId, but with any dataId. In other word, I like to find all items like
var entries = registrantsRepository(Tuple.Create(registrantId, fieldId, *))
How should it be done (perhaps in Linq)? Thanks.
I'd just create a separate Lookup.
var registrantsByIdAndField = registrantsFields
.ToLookup(r => Tuple.Create(c.RegistrantID, c.FieldID));
Then you still get fast lookups with this:
var entries = registrantsByIdAndField[Tuple.Create(registrantId, fieldId)];
There's no wildcard search like that, but there is a way to ignore those fields that you're not interested in when you're doing your search.
Iterate through the collection of Keys, referencing the properties of your Tuple that you're interested in matching on. You can do this using LINQ's Any method.
if (registrantsRepository.Keys.Any(x => x.Item1 == registrantId && x.Item2 == fieldId)
{
}
All good answers here. Is ToLookup an option for you?
https://msdn.microsoft.com/en-us/library/bb549073(v=vs.100).aspx
Edit: just realized #StriplingWarrior beat me to this!

Dynamically create anonymous object from list values c#

I have a list (or can be array) of strings that I want to dynamically create an anonymous object from. How do I do this?
var dataSet = new DataSet();
dataSet.ReadXml(#"");
var dataTable = dataSet.Tables[0];
var dataRow = dataTable.Rows[0];
var keys = new List<string> {"Column1","Column2"};
var result = new {keys[0] = dataRow[keys[0]], keys[1] = dataRow[keys[1]]}
So that list named "keys" is going to be created outside this method and can contain 1 to many values. I tried creating a dictionary and looping through the list and adding key/value pairs to the dictionary but then I couldnt figure out how to convert the dictionary back to an anonymous type. I also experimented with the expando objects but that didn't seem to get me any farther.
I must be able to return an anonymous type as the result of this method will be using with the GroupBy clause of a LINQ query.
Here is the method I had to dynamically create the dictionary:
public object Key(DataRow dataRow, List<String> keys)
{
var dictionary = new IDictionary<string, object>;
foreach (string key in keys)
{
dictionary.Add(key, dataRow[key]);
}
return dictionary;
}
Here is my LINQ query:
var duplicates = dataTable.AsEnumerable().GroupBy(r => Key(r, keys)).Where(c => c.Count() > 1).ToList();
The GroupBy clause works if I hardcode in an anonymous type from the Key() method. Basically I just need the GroupBy clause to be dynamically set based upon the values in the keys list.
Stripping down your question, what you want is to be able to group a list of items based on a runtime property which could be composed of one or more properties of that item. In essence, it means you need a selector function (which is your Key method) that transforms an item into a key.
In order for GroupBy to work, it needs to be able to compare any two instances of the key to see if they're equal. This means the key needs to implement a meaningful Equals() method, or you need an IEqualityComparer implementation that does the work for you. In this case I wouldn't bother with creating a new Key, just write an Equality Comparer that can compare two DataRows directly:
var duplicates = dataTable
.AsEnumerable()
.GroupBy(r => r, new MyDataRowComparer(keys))
.Where(c => c.Count() > 1)
.ToList();
internal class MyDataRowComparer : IEqualityComparer<DataRow>
{
private readonly string[] _keys;
public MyDataRowComparer(string[] keys)
{
_keys = keys; // keep the keys to compare by.
}
public bool Equals(DataRow x, DataRow y)
{
// a simple implementation that checks if all the required fields
// match. This might need more work.
bool areEqual = true;
foreach (var key in _keys)
{
areEqual &= (x[key] == y[key]);
}
return areEqual;
}
public int GetHashCode(DataRow obj)
{
// Add implementation here to create an aggregate hashcode.
}
}

Making a list distinct in C#

In C#, I have an object type 'A' that contains a list of key value pairs.
The key value pairs is a category string and a value string.
To instantiate object type A, I would have to do the following:
List<KeyValuePair> keyValuePairs = new List<KeyValuePair>();
keyValuePairs.Add(new KeyValuePair<"Country", "U.S.A">());
keyValuePairs.Add(new KeyValuePair<"Name", "Mo">());
keyValuePairs.Add(new KeyValuePair<"Age", "33">());
A a = new A(keyValuePairs);
Eventually, I will have a List of A object types and I want to manipulate the list so that i only get unique values and I base it only on the country name. Therefore, I want the list to be reduced to only have ONE "Country", "U.S.A", even if it appears more than once.
I was looking into the linq Distinct, but it does not do what I want because it I can't define any parameters and because it doesn't seem to be able to catch two equivalent objects of type A. I know that I can override the "Equals" method, but it still doesn't solve the my problem, which is to render the list distinct based on ONE of the key value pairs.
To expand upon Karl Anderson's suggestion of using morelinq, if you're unable to (or don't want to) link to another dll for your project, I implemented this myself awhile ago:
public static IEnumerable<T> DistinctBy<T, U>(this IEnumerable<T> source, Func<T, U>selector)
{
var contained = new Dictionary<U, bool>();
foreach (var elem in source)
{
U selected = selector(elem);
bool has;
if (!contained.TryGetValue(selected, out has))
{
contained[selected] = true;
yield return elem;
}
}
}
Used as follows:
collection.DistinctBy(elem => elem.Property);
In versions of .NET that support it, you can use a HashSet<T> instead of a Dictionary<T, Bool>, since we don't really care what the value is so much as that it has already been hashed.
Check out the DistinctBy syntax in the morelinq project.
A a = new A(keyValuePairs);
a = a.DistinctBy(k => new { k.Key, k.Value }).ToList();
You need to select the distinct property first:
Because it's a list inside a list, you can use the SelectMany. The SelectMany will concat the results of subselections.
List<A> listOfA = new List<A>();
listOfA.SelectMany(a => a.KeyValuePairs
.Where(keyValue => keyValue.Key == "Country")
.Select(keyValue => keyValue.Value))
.Distinct();
This should be it. It will select all values where the key is "Country" and concat the lists. Final it will distinct the country's. Given that the property KeyValuePairs of the class A is at least a IEnumerable< KeyValuePair< string, string>>
var result = keyValuePairs.GroupBy(x => x.Key)
.SelectMany(g => g.Key == "Country" ? g.Distinct() : g);
You can use the groupby statement. From here you can do all kind off cool stuf
listOfA.GroupBy(i=>i.Value)
You can groupby the value and then sum all the keys or something other usefull

C# Linq dynamic where statements combining DataTable and given Dictionary

I know that there were posts regarding dynamic where clauses in c# linq, however, I'm a bit new with linq and don't think that the solutions proposed were relevant to my case.
The problem is as follows:
I have a Dictionary<string, List<string>>. Each value in the dictionary represents a set of values. For example: for a given key "food" the value can be {"apple", "tomato", "soup"}. In addition, I have a DataTable which its columns are the dictionary's keys.
My mission is to build a linq which its where clauses are build according to the dictionary.
Thus, among multiple values, "or" condition will appear and between key's values, "And" or "Or" condition will appear.
I can't write it hard coded since it must change dynamically according to the keys found in the dictionary.
I don't really know how to concatenate multiple where clauses which may match my requirements.
Instead of concatenating linq expressions you can use .Any(), .All() and .Contains() to achieve what you want.
var filters = new Dictionary<string, List<string>> { {"Food", new List<string> { "apple", "tomato", "soup"} },
{"Drink", new List<string> { "tea" }}};
var table = new System.Data.DataTable();
table.Columns.Add("Food");table.Columns.Add("Drink");
table.Rows.Add("apple" , "water");
table.Rows.Add("tomato", "tea");
table.Rows.Add("cake" , "water");
table.Rows.Add("cake" , "tea");
//And: Retrieves only tomato, tea
var andLinq = table.Rows.Cast<System.Data.DataRow>().Where(row => filters.All(filter => filter.Value.Contains(row[filter.Key])));
//Or: Retrieves all except cake, water
var orLinq = table.Rows.Cast<System.Data.DataRow>().Where(row => filters.Any(filter => filter.Value.Contains(row[filter.Key])));
The Contains is equivalent to equals val1 or equals val2.
I updated the answer. That's the most precise that I could pull out of your question.
Dictionary<string, List<string>> filters = GetFilters();
var filteredSource = GetSource();
bool useAndOperator = GetUseAndOperator();
foreach (var filter in filters.Values)
{
Func<myDataRow, bool> predecate = useAndOperator ? s => filter.All(f => f == s["key1"])
: s => filter.Any(f => f == s["key1"]);
filteredSource = filteredSource.Where(predecate);
}
Also this code doesn't makes much sense but it demonstrates the principle not the complete solution and you should update it accordingly to your needs.

IEnumerable<T> to Dictionary<string, IEnumerable<T>> using LINQ

Having IEnumerable<Order> orders, how to get a Dictionary<string, IEnumerable<Order>> using Linq, where the key is Order.CustomerName mapped to a IEnumerable of customer's orders.
orders.ToDictionary(order => order.CustomerName) is not going to work right away, since there could be multiple orders that could have the same CustomerName.
Solution: orders.ToLookup(order => order.CustomerName);
The ILookup interface is designed for this purpose, and represents a dictionary-like structure that contains many values per key. It has similar performance characteristics to those of a dictionary (i.e. it's a hashtable type structure)
You can create an ILookup using the .ToLookup extension method as follows:
ILookup<string, Order> ordersLookup = orders.ToLookup(o => o.CustomerName)
then:
IEnumerable<Order> someCustomersOrders = ordersLookup[someCustomerName];
Just an alternative to #spender's answer, if you really want a type Dictionary<string, IEnumerable<Order>>, you could use:
Dictionary<string, IEnumerable<Order>> dictionary = orders
.GroupBy(order => order.CustomerName)
.ToDictionary(groupedOrders => groupedOrders.Key,
groupedOrders => (IEnumerable<Order>)groupedOrders);
I'm sure there's a nicer way, but that'll do it too.
Or you could probably simply use
orders.ToLookup(o => o.CustomerName).ToDictionary(g => g.Key)
But as Spender's answer indicates, maybe you don't need the last method, ToDictionary.

Categories

Resources