I have 2 Lists of strings. How do I get a bool that tells me if one lists contains atleast one string from the other list ? (Using Lambda) - linq

This should be simple but I could not wrap my head around it.. Here is how I am doing it now but it seems so wasteful.
There is a
List<string> committees
and
List<string> P.committees
I just want to see if one list has any strings that are contained in the other.
List<Person> listFilteredCommitteesPerson = new List<Person>();
foreach (Person p in listFilteredPerson)
{
foreach (string strCommittee in p.Committees)
{
if (committees.Contains(strCommittee))
{
listFilteredCommitteesPerson.Add(p);
}
}
}
listFilteredPerson = listFilteredCommitteesPerson;

For a boolean value:
var match =
committees.Intersect(listFilteredPerson.SelectMany(p => p.Committees)).Any();
If you want a collection of Person that have a match you can use:
var peopleThatMatch =
listFilteredPerson.Where(p => committees.Intersect(p.Committees).Any());
or:
var peopleThatMatch =
listFilteredPerson.Where(p => p.Committees.Any(s => committees.Contains(s)));
You might want to consider another collection type (e.g. HashSet<T>) for performance reasons if you have large collections.

Related

LINQ GroupBy on single property

I am just not understanding the LINQ non-query syntax for GroupBy.
I have a collection of objects that I want to group by a single property. In this case Name
{ Id="1", Name="Bob", Age="23" }
{ Id="2", Name="Sally", Age="41" }
{ Id="3", Name="Bob", Age="73" }
{ Id="4", Name="Bob", Age="34" }
I would like to end up with a collection of all the unique names
{ Name="Bob" }
{ Name="Sally" }
Based on some examples I looked at I thought this would be the way to do it
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
But I ended up with a collection with one item. So I though maybe I was over complicating things with the projection. I tried this
var uniqueNameCollection = Persons.GroupBy(x => x.Name).ToList();
Same result. I ended up with a single item in the collection. What am I doing wrong here? I am just looking to GroupBy the Name property.
var names = Persons.Select(p => p.Name).Distinct().ToList()
If you just want names
LINQ's GroupBy doesn't work the same way that SQL's GROUP BY does.
GroupBy takes a sequence and a function to find the field to group by as parameters, and return a sequence of IGroupings that each have a Key that is the field value that was grouped by and sequence of elements in that group.
IEnumerable<IGrouping<TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> sequence,
Func<TSource, TKey> keySelector)
{ ... }
So if you start with a list like this:
class Person
{
public string Name;
}
var people = new List<Person> {
new Person { Name = "Adam" },
new Person { Name = "Eve" }
}
Grouping by name will look like this
IEnumerable<IGrouping<Person>> groups = people.GroupBy(person => person.Name);
You could then select the key from each group like this:
IEnumerable<string> names = groups.Select(group => group.Key);
names will be distinct because if there were multiple people with the same name, they would have been in the same group and there would only be one group with that name.
For what you need, it would probably be more efficient to just select the names and then use Distinct
var names = people.Select(p => p.Name).Distinct();
var uniqueNameCollection = Persons.GroupBy(x => x.Name).Select(y => y.Key).ToList();
Appears valid to me. .net Fiddle showing proper expected outcome: https://dotnetfiddle.net/2hqOvt
Using your data I ran the following code statement
var uniqueNameCollection = people.GroupBy(x => x.Name).Select(y => y.Key).ToList();
The return results were List
Bob
Sally
With 2 items in the List
run the following statement and your count should be 2.
people.GroupBy(x => x.Name).Select(y => y.Key).ToList().Count();
Works for me, download a nugget MoreLinq
using MoreLinq
var distinctitems = list.DistinctBy( u => u.Name);

Another how do I use LINQ to Enumerate Dictionaries

Thank you to all the folks who answered and contributed to my last question. I have run into another interesting incarnation of a LINQ question... so like before...
I have the following
const String A_CONVERSATION = "AD Channels";
const String NOT_REPUTABLE = "AD Resolution";
const String DO_NOT_KNOW = "Capture Input";
private enum Properties
{ MyHow, Thats, It }
List<String> MyList = new List<string>
{
A_CONVERSATION,
NOT_REPUTABLE,
DO_NOT_KNOW
}
private Dictionary <Properties, String> PropertyToString;
private Dictionary <String, Properies> StringToProperty;
How can I use LINQ to populate each of the dictionaries so that I can use the following? Is there a one line LINQ statement that would populate each?
Properties MyResult1 = StringToProperty[A_CONVERSATION];
String MySResult2 = PropertyToString[Properties.It];
I specifically would like to use the actaull property to index in the second case.
Providing this is what you want...
In two statements (one could be merged I guess via some lookup somehow - but I didn't think it was that relevant - not everything has to be in one line:)
var properties = ((Properties[])Enum.GetValues(typeof(Properties))).ToList();
var propertyToString = properties.Zip(MyList, (p, s) => new { Prop = p, Text = s }).ToDictionary(x => x.Prop, x => x.Text);
var stringToProperty = properties.Zip(MyList, (p, s) => new { Prop = p, Text = s }).ToDictionary(x => x.Text, x => x.Prop);
I deliberately separated the makings of the enum list - for clarity - you can move that into one line if you'd like.

How to combine dictionaries in Linq?

I'm new to Linq. I have code like this:
public class Data
{
public Dictionary<string,int> WordFrequency;
}
List<Data> dataList;
What I want is one aggregated dictionary that does a combined WordFrequency for the whole list of Data objects. I know how to do this using loops (iterate over the List, then iterate over each Dictionary), my question is, what is the Linq syntax for this? Thank you.
EDIT: here is my (untested) looping approach, so you can see what I mean.
public static Dictionary<string, int> Combine()
{
Dictionary<string, int> result;
foreach (Data data in DataList)
{
foreach (string key in data.WordFrequencies.Keys)
{
if(!result.ContainsKey(key))
result[key] = 0;
result[key] += data.WordFrequencies[key];
}
}
}
So you want to flatten all dictionaries into a single one, which has no duplicate keys - of course?
You can use Enumerable.SelectMany to flatten all and Enumerable.GroupBy to group the keys.
Dictionary<string, int> allWordFrequency = dataList
.SelectMany(d => d.WordFrequency)
.GroupBy(d => d.Key)
.ToDictionary(g => g.Key, g => g.Sum(d => d.Value));
I have presumed that you want to sum all frequencies.
Here is a query-based solution identical in most regards to Tim's:
Dictionary<string, int> allWordFrequency =
(from d in dataList
from kvp in d.WordFrequency
group kvp.Value by d.Key)
// ^^^^^^^^^ this grouping projection...
.ToDictionary(g => g.Key, g => g.Sum());
// ...eliminates need for lambda here ^^
I appreciate how the two from clauses mimic the nested foreach loops in the looping-based approach of the post. Like Tim's solution, the query iterates the KeyValuePair's of the Dictionary rather than iterate the Keys collection - this way the query doesn't need to invoke the indexer to get the corresponding integer count value.

Complex foreach loop possible to shorten to linq?

I have a cluttery piece of code that I would like to shorten using Linq. It's about the part in the foreach() loop that performs an additional grouping on the result set and builds a nested Dictionary.
Is this possible using a shorter Linq syntax?
var q = from entity in this.Context.Entities
join text in this.Context.Texts on new { ObjectType = 1, ObjectId = entity.EntityId} equals new { ObjectType = text.ObjectType, ObjectId = text.ObjectId}
into texts
select new {entity, texts};
foreach (var result in q)
{
//Can this grouping be performed in the LINQ query above?
var grouped = from tx in result.texts
group tx by tx.Language
into langGroup
select new
{
langGroup.Key,
langGroup
};
//End grouping
var byLanguage = grouped.ToDictionary(x => x.Key, x => x.langGroup.ToDictionary(y => y.PropertyName, y => y.Text));
result.f.Apply(x => x.Texts = byLanguage);
}
return q.Select(x => x.entity);
Sideinfo:
What basically happens is that "texts" for every language and for every property for a certain objecttype (in this case hardcoded 1) are selected and grouped by language. A dictionary of dictionaries is created for every language and then for every property.
Entities have a property called Texts (the dictionary of dictionaries). Apply is a custom extension method which looks like this:
public static T Apply<T>(this T subject, Action<T> action)
{
action(subject);
return subject;
}
isn't this far simpler?
foreach(var entity in Context.Entities)
{
// Create the result dictionary.
entity.Texts = new Dictionary<Language,Dictionary<PropertyName,Text>>();
// loop through each text we want to classify
foreach(var text in Context.Texts.Where(t => t.ObjectType == 1
&& t.ObjectId == entity.ObjectId))
{
var language = text.Language;
var property = text.PropertyName;
// Create the sub-level dictionary, if required
if (!entity.Texts.ContainsKey(language))
entity.Texts[language] = new Dictionary<PropertyName,Text>();
entity.Texts[language][property] = text;
}
}
Sometimes good old foreach loops do the job much better.
Language, PropertyName and Text have no type in your code, so I named my types after the names...

Extracting text from an IEnumerable<T>

I have a IEnumerable<T> collection with Name, FullName and Address.
The Address looks like this:
Street1=54, Street2=redfer street, Pin=324234
Street1=54, Street2=fdgdgdfg street, Pin=45654
Street1=55, Street2=tryry street, Pin=65464
I want to loop through this collection and print only those Names, FullNames whose Street1=54
How can i do it in LINQ?
Ok I was able to do this to extract Street1 of the Address
coll.Address.Split(",".ToCharArray())[0]returns me Street1=54 .
Now how do I add this to the condition and print only those Name, FullName whose Street1=54
Based on your update, you can adapt Jared Par's code this way:
var result = collection.Where(x => x.Address.Contains("Street1=54"));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
If you want to be able to plug in your Street1 value with a variable, then do this:
var street1 = "54";
var result = collection.Where(x => x.Address.Contains("Street1=" + street1 ));
foreach ( var cur in result ) {
Console.WriteLine(string.Format("{0}, {1}", cur.Name, cur.FullName));
}
BTW, you really should update your question or add a comment to a specific answer rather than adding a new answer that isn't.
Try this
var result = collection.Where(x => x.Address.Street1==54);
foreach ( var cur in result ) {
Console.WriteLine(var.Name);
}
Select the correct list of :
IList<T> matches = myListOfEnumerables.Where(m => m.Street1 == 54).ToList();
Then loop and print.
Actually the record looks like this:
{Name="Jan" FullName="Kathy Jan" Address="Street1=54, Street2=redfer street, Pin=324234"}
I have to loop through this collection and print only those Names, FullNames whose Street1=54
If the updated information is accurate, you should change the way you store the data.
It looks like you've packed the address information into a string. Why not store it as an object. In fact, why not just as more fields in the same object as the Name and FullName? (and why duplicate the first-name information?)
public class Person
{
public string FirstName, LastName, Street1, Street2, Pin;
}
IEnumerable<Person> persons = GetAllPersonsSomehow();
foreach (Person person in persons.Where(p => p.Street1 == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
Assuming you have to keep the address information in a string, you need a parser for it.
public static IDictionary<string, string> GetAddressFields(string address)
{
return address.Split(',').ToDictionary(
s => s.Substring(0, s.IndexOf('=')).Trim(),
s => s.Substring(s.IndexOf('=') + 1).Trim());
}
foreach (Person person in persons.Where(p =>
GetAddressFields(p.Address)["Street1"] == "54"))
Console.WriteLine(person.LastName + ", " + person.FirstName);
So, what you could do is that you could write a generator for parsing the Address field and then enumerating properties of that. This is a fairly common thing in the functional programming world.
To be fair you would want this code to be lazy in that it would only compute a minimal set. I'm gonna suggest some code from the BCL but you can (and probably should) rewrite the same helper methods with generators.
public static IEnumerable<KeyValuePair<string,string>> NameValueSplit( this string s )
{
foreach (var x in s.Split(','))
{
var y = x.Split(new char[] { '=' }, 2, StringSplitOptions.None);
yield return new KeyValuePair<string, string>(y[0].TrimStart(), y[1].TrimEnd());
}
}
With that helper function you can write code like this
var result = collection.Where(x => x.Address
.NameValueSplit().Any(x => x.Key == "Street1" && x.Value == "54"));
foreach ( var item in result )
{
Console.WriteLine(item.Name);
}
Now this code will not run on your SQL Server if you were thinking of that, but you could write a WHERE clause where you would search the Address field for a sub string %Street1=54%. I like lazy evaluation for string operations and think that's a lacking feature in the BCL. That is why I suggested that kind of solution.

Resources