Select Value from Dictionary if Key exists using LINQ - c#

I have a dictionary where I have a List as value. I want to select specific elements from the list that belongs to a specific key. I tried this so far:
Dictionary<int, List<bool>> dic = new Dictionary<int, List<bool>>();
dic.Add(1, new List<bool> { true, true, false });
var works = dic.Where(x => x.Key == 1).SingleOrDefault().Value.Where(x => x == true).ToList();
var doesNotWork = dic.Where(x => x.Key == 2).SingleOrDefault().Value.Where(x => x == true).ToList();
The first LINQ works because there is a key equal to 1. Thus I get a List<bool> with two elements.
The second LINQ does not work because Value is null. How can I rewrite that LINQ such that if there is no suitable key in the dictionary I get an empty List<bool>?
I thought my approach would work because I thought the default element had an empty list instead of null as Value.

Disclaimer: This only works on C# 6.0 and later (VS 2015+)
If you really want to do it in a single line using linq you can use the ?. operator (null-conditional operator) and get a line like this:
var shouldWork = dic.Where(x => x.Key == 2)?.SingleOrDefault().Value.Where(x => x == true).ToList() ?? new List<bool>();
This will set shouldWork to either the result of the linq query or an empty list. You can replace new List<bool>() with anything you want
See MSDN post here and Github post here for information on the new features in C# 6.0 specifically this example from the github site:
int length = customers?.Length ?? 0; // 0 if customers is null
and description of how it works
The null-conditional operator exhibits short-circuiting behavior,
where an immediately following chain of member accesses, element
accesses and invocations will only be executed if the original
receiver was not null
Edit: Since ?. checks for null, you could simplify the above linq query to this:
var shouldWork = dic[key]?.Where(x => x == true).ToList() ?? new List<bool>();
where key is some variable holding your key

You shouldn't be using LINQ to find a key in a Dictionary - the Dictionary has more efficient methods for doing that - ContainsKey/ indexer pair or more optimal TryGetValue.
For instance:
int key = 2;
(A)
var result = dic.ContainsKey(key) ? dic[key].Where(x => x == true).ToList() : new List<bool>();
(B)
List<bool> values;
var result = dic.TryGetValue(key, out values) ? values.Where(x => x == true).ToList() : new List<bool>();

Why does it need to be LINQ?
List<bool> works1 = dic.ContainsKey(1) ? dic[1] : new List<bool>();

Simplest solution would be to use Dictionary<T>.TryGetValue so that you don't check twice for a value, ie:
Dictionary<int, List<bool>> dic = new Dictionary<int, List<bool>>();
dic.Add(1, new List<bool> { true, true, false });
List<bool> match = null;
var found = dic.TryGetValue(2, out match);
if (!found) match = new List<bool>();

You want to get a strange result, but anyway this code help you:
var reslt = (dic.FirstOrDefault(x => x.Key == 2).Value ?? new List<bool>(0))
.Where(x => x)
.ToList();
Using method SingleOrDefault() isn't correct, because key in Dictionary is unique.
And x=>x==true is strange too.

Try this
Dictionary<int, List<bool>> dic = new Dictionary<int, List<bool>>();
dic.Add(1, new List<bool> { true, true, false });
var works = !dic.ContainsKey(1)? new List<bool>(): dic[1].Where(x => x == true).ToList();
var doesNotWork = !dic.ContainsKey(2) ? new List<bool>(): dic[2].Where(x => x == true).ToList();

We don't know why it need to be LINQ, but this could be a option:
var nowItWork = dic.Where(x => x.Key == 2).SelectMany(x => x.Value).Where(x => x).ToList();

Related

Improving O(n^2) algorithm

I have the following piece of code:
var keywordItems = adwordsService
.ParseReport(report)
.Where(e => e.Keyword.IndexOf('+') == -1);
var keywordTranslations = keywordTranslationService
.GetKeywordTranslationsByClient(id);
model.KeywordItems = keywordItems
.Where(e =>
{
int lastUnderscore = e.CampaignName.LastIndexOf('_');
var identifer = e.CampaignName.Substring(lastUnderscore + 1);
var translation = keywordTranslations
.FirstOrDefault(t => t.translation == e.Keyword &&
t.LocalCombination_id == identifer);
return translation == null;
})
.OrderBy(e => e.Keyword);
It receives an array and then filters each of these element based on whether or not they've already been seen before.
However, this runs pretty slow, as there's a lot of new elements, so I would like it, if someone can point me in the right direction regarding the best algorithm to use in this case.
Simple join will do the job - it uses hashset for matching between collections, which gives you O(1) for search operation:
from k in keywordItems
let identifer = k.CampaignName.Substring(k.CampaignName.LastIndexOf('_') + 1)
join t in keywordTranslations on
new { k.Keyword, Id = identifer } equals
new { Keyword = t.translation, Id = t.LocalCombination_id } into g
where !g.Any()
orderby k.Keyword
select k
To further improve performance you can move identifier extraction directly to the key creation. Thus you will omit introducing new range variable.
I suggest using hashing, e.g. HashSet<T> or Dictionary<T>. Providing that translation as well as LocalCombination_id are of type string:
HashSet<Tuple<string, int>> keywordTranslations =
new HashSet<Tuple<string, string>>(keywordTranslationService
.GetKeywordTranslationsByClient(id)
.Select(t => new Tuple<string, int>(t.translation, t.LocalCombination_id)));
model.KeywordItems = keywordItems
.Where(e => !keywordTranslations.Contains(new Tuple<string, string>(
e.Keyword,
e.CampaignName.Substring(e.CampaignName.LastIndexOf('_') + 1))))
.OrderBy(e => e.Keyword);

Use List<Tuple<int, int>> to return data in Linq

Given:
List<int> myList;
If I wanted to return data where the record ID was contained in this list I would simply do:
var q = db.Table.Where(c=> myList.Contains(c.ID));
However, given:
List<Tuple<int, int>> myList;
How would I write a Linq query to return records where both conditions are met? With one data point I would write:
var q = db.Table.Where(c=>
c.ID == myList.Item1
&& c.AnotherValue == myList.Item2);
How would I convert the above statement to work on a List<Tuple<int, int>>?
A Tuple is a structure that can't not be translated to sql by your Linq Provider. A solution could be making a switch to Linq to Objects
var q = db.Table.AsEnumerable()
.Where(c=> myList.Any(tuple => c.ID == tuple.Item1 &&
c.AnotherValue == tuple.Item2));
But the bad thing about this solution is that you're going to load all the rows from that table to filter in memory.
Another solution could be using Linqkit:
var predicate = PredicateBuilder.False<Table>();
foreach (string t in myList)
{
predicate = predicate.Or(c =>c.ID == t.Item1 && c.AnotherValue == t.Item2));
}
db.Table.AsExpandable().Where(predicate);
You will find more info about this last solution in this link
var q = db.Table.AsEnumerable().Where(c => myList.Any(tuple => c.ID == tuple.Item1 &&
c.AnotherValue == tuple.Item2));
With Any you can check if there is at least one element in myList the matches your condition.
But as #octaviocci pointed out, this is not translatable to SQL, so you would need to call AsEnumerable() before and do the filtering locally, which may not be what you want if there are a lot of irrelevant records.
Here is some sample code that illustrates one approach:
DataTable dt = new DataTable("demo");
// hydrate your table here...
List<Tuple<int, int>> matches = new List<Tuple<int, int>>();
Func<List<Tuple<int,int>>, DataRow, bool> RowMatches = (items, row) => {
var rowValue1 = (int)row["Id"];
var rowValue2 = (int)row["SomeOtherValue"];
return items.Any(item => item.Item1 == rowValue1 && item.Item2 == rowValue2);
};
var results = dt.Rows.Cast<DataRow>().Where(r => RowMatches(matches, r));
Console.WriteLine(results.Any());
See code below:
List<Tuple<int, int>> myList;
var set = new HashSet(myList);
var q = db.Table.AsEnumerable().Where(c=> set.Contains(new Tuple(c.ID, c.AnotherValue)));
Note that hash set is used to performance-optimize the execution of Where clause for large myList.
Since Tuple cannot be used in Linq to Entities, you can try something like this:
List<int> items1 = myList.Select(t => t.Item1).ToList();
List<int> items2 = myList.Select(t => t.Item2).ToList();
var q = db.Table.GroupBy(m => { m.ID, m.AnotherValue })
.Where(g => items1.Contains(g.Key.ID) &&
items2.Contains(g.Key.AnotherValue))
.SelectMany(g => g);

How do I optimize checking for any added, deleted or updated items on a list with Linq to Objects?

I need to check whether or not any operation (insert, delete or update) happened on items existing on a generic collection. I have a list with the original objects and a list with the new ones. The references for two objects with the same Id won't be the same.
Right now I was able to solve the problem, but with 3 operations being executed. Here's my code:
var oldList = new List<MyClass>();
var newList = new List<MyClass>();
//Search for items on the new list that are not present on the old one
var addedItems = newList.Where(item => !oldList.Select(x => x.Id).Contains(item.Id));
//Search for items on the old list that are not present on the new one
var deletedItems = oldList.Where(item => !newList.Select(x => x.Id).Contains(item.Id));
//Search for items present on both lists, but with any property changed
var updatedItems = from x in oldList
join y in newList on x.Id equals y.Id
where x.Name != y.Name ||
x.Description != y.Description ||
x.Quantity != y.Quantity
select new { OriginalEntity = x, NewEntity = y };
bool anyChanges = addedItems.Count() > 0 ||
deletedItems.Count() > 0 ||
updatedItems.Count() > 0;
This code works, but I would like to know if it's possible to achieve the result in a cleaner or faster (less operations) way.
I know I could implement IEquatable<T> on MyClass but please consider that this is not possible for now (only because I want to find out if there's a solution for when that really isn't possible). In any case, that would allow me to use "Except" and detect easily inserts or deletes but not updates (unless I'm missing something).
Also, I just wanted to point out that there is a lot of questions on SO with similar questions but I haven't found any related to detect insert, delete and update simultaneously (reason why I'm posting this).
Thanks in advance!
Mostly you just need caching + better data structures.
var oldList = new List<MyClass>();
var newList = new List<MyClass>();
// O(1) lookups vs O(N) lookups
var oldListIds = new HashSet<int>(oldList.Select(x => x.Id));
var newListIds new HashSet<int>(newList.Select(x => x.Id));
//Search for items on the new list that are not present on the old one
var addedItems = newList.Where(item => !oldListIds.Contains(item.Id));
//Search for items on the old list that are not present on the new one
var deletedItems = oldList.Where(item => !newListIds.Contains(item.Id));
//Search for items present on both lists, but with any property changed
var updatedItems = from x in oldList
join y in newList on x.Id equals y.Id
where x.Name != y.Name ||
x.Description != y.Description ||
x.Quantity != y.Quantity
select new { OriginalEntity = x, NewEntity = y };
// Use .Any() instead of .Count() so we stop after first item
bool anyChanges = addedItems.Any() ||
deletedItems.Any() ||
updatedItems.Any();
How about something like this:
// determines if evey item in listA has as a match in listB
static bool Matches(List<MyClass> listA, List<MyClass> listB)
{
var matches = listA.GroupJoin(listB, a => a.Id, b => b.Id,
(a, b) => b.Any(c => c.Name == a.Name && c.Description == a.Description && c.Quantity == a.Quantity));
return matches.All(m => m);
}
This does a GroupJoin() returning a bool if B joins with A. Then it does All() to see if all joins return true.
Then you could call it like this:
bool equal = Matches(oldList, newList) && Matches(newList, oldList);
This returns a bool indicating if everything in oldList joins to something newList AND everything in newList joins to something in oldList.
I'm not sure if this will be faster than the other suggestions, but it certainly is less code.

NameValueCollection returns Length Property instead of Name Value

Could someone shed some light on this my NameValueCollection returns the Length property instead of Name and Value could some show me what im doing wrong here. I can't set the DataTextField or DataValueField for the dropdownlist it just gives me length.
public NameValueCollection GetDisplayForumGroups()
{
using (CMSEntities db = new CMSEntities())
{
var forums = (from x in db.Forums where x.ParentID == null select new { Name = x.Title, Value = x.ForumID });
NameValueCollection collection = new NameValueCollection();
foreach (var forum in forums)
{
collection.Add(forum.Name, forum.Value.ToString());
}
return collection;
}
}
public Dictionary<string, int> GetDisplayForumGroups()
{
using (CMSEntities db = new CMSEntities())
{
Dictionary<string, int> forums = (from x in db.Forums where x.ParentID == null select x).ToDictionary(x => x.Title, x => x.ForumID);
return forums;
}
}
You can't bind directly to a NameValueCollection since it doesn't provide a suitable enumerator. The standard enumerator enumerates through the keys only.
Then again you shouldn't be using a NameValueCollection for this in the first place, you should use the generic Dictionary unless you need multiple values per key (and even then there are better alternatives for most cases). There's even a Linq method for automagically making a dictionary:
Dictionary<string, int> forums = (from x
in db.Forums
where x.ParentID == null
select x)
.ToDictionary(x => x.Title, x => x.ForumID);

How can I filter a dictionary using LINQ and return it to a dictionary from the same type

I have the following dictionary:
Dictionary<int,string> dic = new Dictionary<int,string>();
dic[1] = "A";
dic[2] = "B";
I want to filter the dictionary's items and reassign the result to the same variable:
dic = dic.Where (p => p.Key == 1);
How can I return the result as a dictionary from the same type [<int,string>] ?
I tried ToDictionary, but it doesn't work.
ToDictionary is the way to go. It does work - you were just using it incorrectly, presumably. Try this:
dic = dic.Where(p => p.Key == 1)
.ToDictionary(p => p.Key, p => p.Value);
Having said that, I assume you really want a different Where filter, as your current one will only ever find one key...

Categories

Resources