Linq items in a list exist in another list - c#

I have 2 lists
List 1
var hashTags = new List<HashTag>();
hashTags.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags.Add(new HashTag
{
Name = "#HashTag2",
Index = 2
});
hashTags.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
List 2
var hashTags2 = new List<HashTag>();
hashTags2.Add(new HashTag
{
Name = "#HashTag1",
Index = 1
});
hashTags2.Add(new HashTag
{
Name = "#HashTag3",
Index = 3
});
hashTags2.Add(new HashTag
{
Name = "#HashTag4",
Index = 4
});
How do I check if all the elements in hashTags2 exist in hashTags? The index can be ignored and only the name matching is crucial. I can write a for loop to check element but I am looking for a LINQ solution.

Simple linq approach.
hashTags2.All(h=> hashTags.Any(h1 => h1.Name == h.Name))
Working Demo

As only equality of the names is to be taken into account, the problem can be solved by first mapping to the names and then checking containment as follows.
var hashTags2Names = hashTags2.Select( iItem => iItem.Name );
var hashTagsNames = hashTags.Select( iItem => iItem.Name );
var Result = hashTags2Names.Except( hashTagsNames ).Any();

So you want a boolean linq expression that returns true if the name of every element in hashTags2 exists in hashTags?
For this you want the function Enumerable.All, you want that every Hashtag in hashTags2 ...
bool result = hashTags2.All(hashTag => ...)
what do you want to check for every hashTag in hashTags2? That the name is a name in hashTags. So we need the names of hashTags:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
and to check if an item is in a sequence: Enumerable.Contains.
Put it all together:
IEnumerable<string> names = hashTags.Select(hashTag => hashTag.Name);
bool result = hashTags2.All(hashTag => names.Contains(hashTag.Name));
Of if you want one fairly unreadable expression:
bool result = hashTags2.All(hashTagX =>
hashTags.Select(hashTagY => hashTagY.Name)
.Contains(hashtagX)))
Because of delayed execution there is no difference between the first and the second method. The first one will be more readable.

With Linq to objects you will need at least one IEqualityComparar, to
tell linq how to compare objects and to determine when they are equal.
A simple comparer would be the following that uses the Name property to determine equality of your HashTag.
public class NameEquality : IEqualityComparer<HashTag>
{
public bool Equals(HashTag tag, HashTag tag2)
{
return tag.Name == tag2.Name;
}
public int GetHashCode(HashTag tag)
{
return tag.Name.GetHashCode();
}
}
With this Equality Comparer you can use the linq method Except(), to get all Elements from your list hashTag that are not part of hashTag2.
hashTags.Except(hashTags2, new NameEquality())

I prefer the join operator, however it is just a matter of taste, I guess:
var hashMatched = hashTags.Join(hashTags2,_o => _o.Name, _i => _i.Name, (_o,_i) => _o);

Related

How to filter navigation property of a list by LINQ and Lambda expression?

I have a list of FirstLevelItems. Each item has a list of SecondLevelItems.
public class FirstLevelItem {
public List<SecondLevelItem> SecondLevelItems { get; set; }
}
public class SecondLevelItem{
public bool Deleted {get; set;}
}
I want to return all FirstLevelItems and filter SecondLevelItems which are Deleted.
I understand you have a local collection and you want to filter the items that are not deleted from each element. In such case you can use this:
items.ForEach( item =>
item.SecondLevelItems = item.SecondLevelItems.Where(s => !s.Deleted).ToList() );
Alternatively, if you are talking about a database, you could do:
var results = DataContext.FirstLevelItems.Select( fi => new {
FirstLevelItem = fi,
SecondLevelItems = fi.SecondLevelItems.Where( si => !si.Deleted )
} );
This will return tuples where FirstLevelItem property is the first level item itself, and SecondLevelItems property is the filtered list of second level items. However, make sure you access the SecondLevelItems property directly, not the FirstLevelItem.SecondLevelItems property, as that would lazily evaluate and query the database for all second level items.
It is interesting how everyone understood the problem differently :-) .
Return all FirstLevelItems that contain at least one SecondLevelItem that is deleted:
var result = DbContext.FirstLevelItems
.Where(fl => fl.SecondLevelItems.Any(sl => sl.Deleted));
The Any clause checks if a boolean condition is true for at least one item in a collection.
this code maybe help you
var fli = //get your FirstLevelItems
var result= fli.SecondLevelItems.Where(x => x.Deleted);
Suppose you have variable list of type List<FirstLevelItem>.
list = list.Select(listItem =>
{
listItem.SecondLevelItems = listItem
.SecondLevelItems
.Where(secondLevelItem => secondLevelItem.Deleted)
.ToList();
return listItem;
}).ToList();

C# List.OrderBy with multiple lists

I got 5 lists. One is containing the date of release and the others are the attributes of that list but seperated in multiple lists.
List<string> sortedDateList = x1.OrderBy(x => x).ToList();
This code is sorting the list with the oldest date first, like it should. But I also want to sort (sync) the other attributes list, because they need the same index as the date.
How can I realize that? I'm new to Linq-methods.
You could use the .Zip() method to combine the lists as described here. You could combine them into a class or an anonymous type and then sort them.
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => new { Num = first, Word = second });
var sorted = numbersAndWords.OrderBy(x => x.Num).ToList();
Alternately, if you can guarantee that all the lists are of the same length (or just grab the shortest list) you could use the following instead of the .Zip() extension.
var numbersAndWords = numbers.Select((number, i) => new { Num = number, Word = words[i], Foo = myFoos[i] }); // Where myFoos is another collection.
And in the lambda combine all the items from the separate lists into an object at the same time by accessing the collection by index. (Avoids multiple use of .Zip()) Of course, if you try to access an index that is larger than the list size you will get an IndexOutOfRangeException.
As far as I understand your question, you have different lists containing properties of certain objects. You should definitely look into storing all data into one list of a class of your making, where you consolidate all separate information into one object:
var list = new List<YourClass>
{
new YourClass
{
Date = ...,
OtherProperty = ...,
},
new YourClass
{
Date = ...,
OtherProperty = ...,
},
};
var ordered = list.OrderBy(o => o.Date);
But if you insist in storing different properties each in their own list, then you could to select the dates with their index, then sort that, as explained in C# Sorting list by another list:
var orderedDates = list.Select((n, index) => new { Date = n, Index = index })
.OrderBy(x => x.Date)
.ToList();
Then you can use the indexes of the sorted objects to look up the properties in the other lists, by index, or sort them on index as explained in C# Sort list while also returning the original index positions?, Sorting a list and figuring out the index, and so on.
It almost sounds like you want 1 list of a class.
public class MyClass{
public string Date{get; set;} //DateTime is a better type to use for dates by the way
public string Value2{get; set;}
public string Value3{get; set;}
public string Value4{get; set;}
public string Value5{get; set;}
}
...
var sortedDateList = x1.OrderBy(x => x.Date).ToList()
Create an Object containing the date and attributes:
public class DateWithAttributes
{
public string Date {get;set;}
public Attribute Attribute1 {get;set;}
public Attribute Attribute2 {get;set;}
...
}
List<DateWithAttributes> DateWithAttributesList = new List<DateWithAttributes>()
{
DateWithAttribute1,
DateWithAttribute2
}
List<DateWithAttributes> sortedDateList = DateWithAttributesList.OrderBy(x => x.date).ToList();
If you want to keep the lists separate, and/or create the ordered versions as separate lists, then you can concatenate the index to the dates and sort by dates, then use the sorted indexes:
var orderedIndexedDateOfReleases = dateOfReleases.Select((d, i) => new { d, i }).OrderBy(di => di.d);
var orderedDateOfReleases = orderedIndexedDateOfReleases.Select(di => di.d).ToList();
var orderedMovieNames = orderedIndexedDateOfReleases.Select(di => movieNames[di.i]).ToList();
If you don't mind the result being combined, you can create a class or use an anonymous class, and again sort by the dates:
var orderedTogether = dateOfReleases.Select((d, i) => new { dateOfRelease = d, movieName = movieNames[i] }).OrderBy(g => g.dateOfRelease).ToList();

Sort a C# list by word

I want to sort a C# list by word. Assume I have a C# list (of objects) which contains following words:
[{id:1, name: "ABC"},
{id:2, name: "XXX"},
{id:3, name: "Mille"},
{id:4, name: "YYY"},
{id:5, name: "Mill",
{id:6, name: "Millen"},
{id:7, name: "OOO"},
{id:8, name: "GGGG"},
{id:9, name: null},
{id:10, name: "XXX"},
{id:11, name: "mil"}]
If user pass Mil as a search key, I want to return all the words starting with the search key & then all the words which does not match criteria & have them sort alphabetically.
Easiest way I can think of is to run a for loop over the result set, put all the words starting with search key into one list and put the renaming words into another list. Sort the second list and them combine both the list to return the result.
I wonder if there is a smarter or inbuilt way to get the desired result.
Sure! You will sort by the presence of a match, then by the name, like this:
var results = objects.OrderByDescending(o => o.Name.StartsWith(searchKey))
.ThenBy(o => o.Name);
Note that false comes before true in a sort, so you'll need to use OrderByDescending.
As AlexD points out, the name can be null. You'll have to decide how you want to treat this. The easiest way would be to use o.Name?.StartsWith(searchKey) ?? false, but you'll have to decide based on your needs. Also, not all Linq scenarios support null propagation (Linq To Entities comes to mind).
This should do it, but there's probably a faster way, maybe using GroupBy somehow.
var sorted = collection
.Where(x => x.Name.StartsWith(criteria))
.OrderBy(x => x.Name)
.Concat(collection
.Where(x => !x.Name.StartsWith(criteria))
.OrderBy(x => x.Name))
You can try GroupBy like this:
var sorted = collection
.GroupBy(item => item.Name.StartsWith(criteria))
.OrderByDescending(chunk => chunk.Key)
.SelectMany(chunk => chunk
.OrderBy(item => item.Name));
Separate items into two groups (meets and doesn't meet the criteria)
Order the groups as whole (1st that meets)
Order items within each group
Finally combine the items
There's nothing C#-specific to solve this, but it sounds like you're really looking for algorithm design guidance.
You should sort the list first. If this is a static list you should just keep it sorted all the time. If the list is large, you may consider using a different data structure (Binary Search Tree, Skip List, etc.) which is more optimized for this scenario.
Once it's sorted, finding matching elements becomes a simple binary search. Move the matching elements to the beginning of the result set, then return.
Add an indicator of a match into the select, and then sort on that:
void Main()
{
word[] Words = new word[11]
{new word {id=1, name= "ABC"},
new word {id=2, name= "XXX"},
new word {id=3, name= "Mille"},
new word {id=4, name= "YYY"},
new word {id=5, name= "Mill"},
new word {id=6, name= "Millen"},
new word {id=7, name= "OOO"},
new word {id=8, name= "GGGG"},
new word {id=9, name= null},
new word {id=10, name= "XXX"},
new word {id=11, name= "mil"}};
var target = "mil";
var comparison = StringComparison.InvariantCultureIgnoreCase;
var q = (from w in Words
where w.name != null
select new {
Match = w.name.StartsWith(target, comparison)?1:2,
name = w.name})
.OrderBy(w=>w.Match).ThenBy(w=>w.name);
q.Dump();
}
public struct word
{
public int id;
public string name;
}
It is probably not easier but you could create a class that implements IComparable Interface and have a property Mil that is used by CompareTo.
Then you could just call List.Sort(). And you can pass an IComparer to List.Sort.
It would probably be the most efficient and you can sort in place rather than producing a new List.
On average, this method is an O(n log n) operation, where n is Count;
in the worst case it is an O(n ^ 2) operation.
public int CompareTo(object obj)
{
if (obj == null) return 1;
Temperature otherTemperature = obj as Temperature;
if (otherTemperature != null)
{
if(string.IsNullOrEmpty(Mil)
return this.Name.CompareTo(otherTemperature.Name);
else if(this.Name.StartsWith(Mill) && otherTemperature.Name.StartsWith(Mill)
return this.Name.CompareTo(otherTemperature.Name);
else if(!this.Name.StartsWith(Mill) && !otherTemperature.Name.StartsWith(Mill)
return this.Name.CompareTo(otherTemperature.Name);
else if(this.Name.StartsWith(Mill))
return 1;
else
return 0;
}
else
throw new ArgumentException("Object is not a Temperature");
}
You will need to add how you want null Name to sort
First create a list of the words that match, sorted.
Then add to that list all of the words that weren't added to the first list, also sorted.
public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
{
var result = new List<Word>(words.Where(word => word.Name.StartsWith(keyword))
.OrderBy(word => word.Name));
result.AddRange(words.Except(result).OrderBy(word => word.Name));
return result;
}
Some of the comments suggest that it should be case-insensitive. That would be
public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
{
var result = new List<Word>(
words.Where(word => word.Name.StartsWith(keyword, true)) //<-- ignoreCase
.OrderBy(word => word.Name));
result.AddRange(words.Except(result).OrderBy(word => word.Name));
return result;
}

Iterate over a collection of strings using LINQ

I put the following code segment in .NET Fiddle but it printed out System.Linq.Enumerable+WhereArrayIterator1[System.String] I'd like to print out each content in result, in order to understand how Select works. Can someone please help to point out what the problem is? Many thanks!
string[] sequ1 = { "abcde", "fghi", "jkl", "mnop", "qrs" };
string[] sequ2 = { "abc", "defgh", "ijklm", "nop" };
var result =sequ1.Select( n1 => sequ2.Where(n2 => n1.Length < n2.Length) );
foreach( var y in result)
{
Console.WriteLine(y);
}
You are actually returning a collection of collections.
sequ1.Select( n1 => sequ2.Where(n2 => n1.Length < n2.Length) );
For each element in sequ1, this statement filters sequ2 to find all of the elements from the second sequence where the current value in the first sequence is shorter than it and then maps to a new collection containing each of those results.
To describe what Select is actually doing:
You start with a collection of things. In your case: sequ1 which has type IEnumerable<string>
You supply it with a function, this function takes an argument of the type of thing you supplied it with a collection of and has a return type of some other thing, in your case:
fun n1 => sequ2.Where(n2 => n1.Length < n2.Length)
Your function takes a string and returns an IEnumerable<string>
Finally, it returns a result containing a collection of each element in the original collection transformed to some new element by the function you supplied it with.
So you started with IEnumerable<string> and ended up with IEnumerable<IEnumerable<string>>.
That means you have a collection for each value that appears in sequ1.
As such, you would expect the result to be:
{{}, {"defgh", "ijklm"}, {"defgh", "ijklm"}, {"defgh", "ijklm"}, {"defgh", "ijklm"}}
You can inspect the results by adding another loop.
foreach(var y in result)
{
foreach(var z in result)
{
Console.WriteLine(z);
}
}
Change your Select to SelectMany:
var result = sequ1.SelectMany(n1 => sequ2.Where(n2 => n1.Length < n2.Length));
I may be wrong, but I think the OP wants to compare both arrays, and for each element, print the longest one.
If that's the case, I would do it as follows:
var result = sequ1.Take(sequ2.Length)
.Select((n1, i) =>
(n1.Length > sequ2.ElementAt(i).Length)
? n1
: sequ2.ElementAt(i));
Explanation:
Use Take to only go as long as the length of the second array, and avoid nullreference exceptions later on.
Use Select, with two arguments, the first is the string, the second is the index.
Use ElementAt to find the corresponding element in sequ2
I don't know about this example is about to help you to understand how select work. A more simple exmaple what i think is this.
public class Person {
public string Name { get; set; }
public string LastName { get; set; }
}
public class Test {
public Test() {
List<Person> persons = new List<Person>();
persons.Add(new Person() { Name = "Person1",LastName = "LastName1" });
persons.Add(new Person() { Name = "Person2",LastName = "LastName2" });
var getNamesFromPersons = persons.Select(p => p.Name);
}
}
If you are beginning c#, you need to sideline the keyword "var" from your code.
Force yourself to write out what the variables really are:
If you forego the use of var, you would have seen why your code was Console.Writing what it did.
string[] sequ1 = { "abcde", "fghi", "jkl", "mnop", "qrs", };
string[] sequ2 = { "abc", "defgh", "ijklm", "nop", };
IEnumerable<IEnumerable<string>> result = sequ1.Select(n1 => sequ2.Where(n2 => n1.Length < n2.Length));
foreach (IEnumerable<string> y in result)
{
foreach (string z in y)
{
Console.WriteLine(z);
}
}

How to Select the Token and Index from a Comma Separated String in Linq

Given a comma delimited string "a,b,c" I would like to split the string and select the token and its respective index into a list.
In other words, I want "a,b,c".Split(',') to return a list of:
a, 1
b, 2
c, 3
I attempted the solution myself, but this is as close as I get. Of course, I only use a.Index() in the final line to indicate what I am trying to do.
public class var
{
public string Token;
public int Index;
}
List<var> varList = "a,b,c"
.Split(',')
.Select(a => new var { Token = a, Index = a.Index() };
You can use the other overload of Select, .Select(Func<string, int, TResult>) which gives us the index of the value.
List<Var> varList = "a,b,c".Split(',')
.Select((a, i) => new Var { Token = a, Index = i + 1 })
.ToList();
You can use the overload of Select which provides indexing:
var list = "a,b,c".Split(',').Select((a,i) => new { Token = a, Index = i+1 }).ToList();
On a side note - I would recommend not using var as a class name, as it will conflict with the C# var keyword.

Categories

Resources