remove list-items with Linq when List.property = myValue - c#

I have the following code:
List<ProductGroupProductData> productGroupProductDataList = FillMyList();
string[] excludeProductIDs = { "871236", "283462", "897264" };
int count = productGroupProductDataList.Count;
for (int removeItemIndex = 0; removeItemIndex < count; removeItemIndex++)
{
if (excludeProductIDs.Contains(productGroupProductDataList[removeItemIndex].ProductId))
{
productGroupProductDataList.RemoveAt(removeItemIndex);
count--;
}
}
Now i want to do the same with linq. Is there any way for this?
The second thing would be, to edit each List-Item property with linq.

you could use RemoveAll.
Example:
//create a list of 5 products with ids from 1 to 5
List<Product> products = Enumerable.Range(1,5)
.Select(c => new Product(c, c.ToString()))
.ToList();
//remove products 1, 2, 3
products.RemoveAll(p => p.id <=3);
where
// our product class
public sealed class Product {
public int id {get;private set;}
public string name {get; private set;}
public Product(int id, string name)
{
this.id=id;
this.name=name;
}
}

Firstly corrected version of your current code that won't skip entries
List<ProductGroupProductData> productGroupProductDataList = FillMyList();
string[] excludeProductIDs = { "871236", "283462", "897264" };
int count = productGroupProductDataList.Count;
for (int removeItemIndex = 0; removeItemIndex < count; removeItemIndex++)
{
while (removeItemIndex < count && excludeProductIDs.Contains(productGroupProductDataList[removeItemIndex].ProductId)) {
productGroupProductDataList.RemoveAt(removeItemIndex);
count--;
}
}
}
This linq code would do the job.
List<ProductGroupProductData> productGroupProductDataList = FillMyList();
string[] excludeProductIDs = { "871236", "283462", "897264" };
productGroupProductDataList=productGroupProductDataList.Where(x=>!excludedProductIDs.Contains(x.ProductId)).ToList();
Alternatively using paolo's answer of remove all the last line would be would be
productGroupProductDataList.RemoveAll(p=>excludedProductIDs.Contains(p=>p.ProductId));
What you mean by "The second thing would be, to edit each List-Item property with linq."?

As per your comment here's a version that creates a set that excludes the elements rather than removing them from the original list.
var newSet = from p in productGroupProductDataList
where !excludeProductIDs.Contains(p.ProductId))
select p;
The type of newSet is IEnumerable if you need (I)List you can easily get that:
var newList = newSet.ToList();

Related

Extract overlapping substrings from a list of strings using LINQ

I have the following list of strings {"a","b","c","d","e"}. How can I obtain sublists of length 3 using LINQ like this:
{"a","b","c"}
{"b","c","d"}
{"c","d","e"}
I am not looking for every combination
var list = students.OrderBy(student => student.LastName)
.Select(student => student);
List<Student> sortedStudents = list.ToList();
var triplets = from x in sortedStudents
from y in sortedStudents
from z in sortedStudents
select new { x, y, z};
StudentListBox.ItemsSource = triplets;
I am not looking for something like
{"a","b","c"}
{"a","b","d"}
{"a","b","e"}
.............
{"d","a","b"}
{"d","a","c"}
{"d","a","e"} and so on
Student class
class Student
{
public Student()
{
}
public String FirstName
{
get;
set;
}
public String LastName
{
get;
set;
}
public DateTime Birthday
{
get;
set;
}
public override string ToString()
{
return FirstName + " " + LastName;
}
}
You can us an overload of Select which gets the index of current element as an extra parameter to the selector and use it like this:
var triplets = sortedStudents.Take(list.Count - 2)
.Select((x, i) => new { S1 = x, S2 = list[i+1], S3 = list[i+2] });
Here is one approach with Linq - .Take(3)defines length of 3
string[] input = { "a", "b", "c", "d", "e" };
var result = Enumerable.Range(0, input.Length - 2).Select(x => input.Skip(x).Take(3));
Just loop the strings in your array:
public IEnumerable<IEnumerable<string>> GetTriples(string[] myArray)
{
for (int i = 0; i < myArray.Length - 2; i++)
{
yield return myArray.Skip(i).Take(3);
}
}
This codes loops every string in your array and gets the next two strings.
Assuming (because you haven't got a complete code sample) that you want to take triples of items from your collection in the order in which they appear, you can use a combination of Skip and Take to give you subsets representing your triples.
var triplets = new List<IEnumerable<Student>>();
for(int i = 0; i < (sortedStudents.Count - 2); i++)
{
triplets.Add(sortedStudents.Skip(i).Take(3));
}

Skip and take method in List

I have a class Players. And I want to create Hyperlink with Skip and Take methods. But gives me System.Linq.Enumerable error. My goal is make a pyramid user list. here is my codes
public class Players
{
public string Name{ get; set; }
public int Order{ get; set; }
public int ID { get; set; }
}
List<Players> playerlist= new List<Players>();
playerlist= (from DataRow dr in dt.Rows
select new Players()
{
Name= (dr["name"].ToString()),
Order= int.Parse(dr["order"].ToString()),
ID = int.Parse(dr["Id"].ToString())
}).ToList();
playerlist= playerlist.OrderBy(x => x.Order).ToList();
int skip = 0;
int take = 1;
int addedCount = 0;
do
{
HyperLink links= new HyperLink();
links.Text = "" + playerlist.Skip(skip ).Take(take).Select(x => x.Name);
links.NavigateUrl = "playerdetails.aspx?id=" + oyunculistesi.Skip(skip).Take(take).Select(x => x.ID);
Page.Controls.Add(links);
addedCount += take ;
skip+= take ;
take += +1;
}
while (addedCount < playerlist.Count);
It is working with StringBuilder but with HyperLink not.
sb.AppendLine(string.Join(" ", players.Skip(skip).Take(take).Select(x => $"{x.Order}) {x.Name}")));
Your Select is returning an IEnumerable of char and you need to build a string from them by using string.Join like what you did in the StringBuilder:
linkuret.Text = string.Join("" , playerlist.Skip(skip).Take(take).Select(x => x.Name));
I would rewrite your loop in this way
int skip = 0;
while (skip < playerlist.Count)
{
HyperLink links= new HyperLink();
Players p = playerlist.Skip(skip).FirstOrDefault();
links.Text = $"{p.Name}"
links.NavigateUrl = $"playerdetails.aspx?id={p.Id}"
Page.Controls.Add(links);
skip++;
}
First I have removed the Take part from your code and used FirstOrDefault to get always the first element after the skip. Finally the Players elements is loaded just one time and then I used the properties of the class with more readable code.

Merging lists with a for loop

I'm working on an algorithm which can generate 2 types of recommendations, restaurants and dishes. All of this works fine, but I wanted to merge these 2 types of recommendations in a single list, which is where I encountered some issues. From my previous question I concluded that I needed a wrapper class, which I have set up like this:
public class RecommenderItem
{
public Guid Id { get; set; }
public object Entity { get; set; }
}
Now I want to alternate the 2 types of recommendations so the list would look like this:
[Restaurant][Dish][Restaurant][Dish][Restaurant][Dish] //Etc...
Note that these recommendations are completely separate. They are generated purely based on the user's preference, and they have no correlation in between them. My product owner wants to show these recommendations on the home page of our app like this.
These lists are different in length, so if I have added all items from a list, I wanted to just add the remaining objects from the other list. A possible scenario of this could look like this:
/*Other objects before this...*/[Dish][Restaurant][Dish][Dish][Dish] //Etc...
Here did the list of restaurant objects run out and I just wanted to add the remaining dish recommendations at the end of the list.
I have gotten this far, but I'm unsure how I would catch an IndexOutOfBounds exception and add the rest of the remaining objects at the end.
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations,
List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = 0;
//Check which list is longer and use that count
if (restaurantRecommendations.Count > dishRecommendations.Count)
count = dishRecommendations.Count;
else
count = restaurantRecommendations.Count;
for (int i = 0; i < count; i++)
{
//I'm fully aware this isn't the most optimal way of doing this,
//but I'm only looking at functionality here, optimizing performance comes later.
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
return output;
}
Does anyone have an idea how I could do this? Could I just catch an IndexOutOfBounds exception and use .AddRange() for the remaining objects? I'm not sure how I could check which list was out of bounds.
Let me know if I should elaborate more and thanks in advance!
Edit: -removed because it wasn't fair.-
This is a fairly succinct way of doing this.
While not Linq, it works in the spirit of the way Linq works by deferring doing any work until the resulting sequence is enumerated:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
using (var r = restaurants.GetEnumerator())
using (var d = dishes.GetEnumerator())
{
while (true)
{
bool rAvailable = r.MoveNext();
bool dAvailable = d.MoveNext();
if (rAvailable)
yield return new RecommenderItem { Id = r.Current.Id, Entity = r.Current };
if (dAvailable)
yield return new RecommenderItem { Id = d.Current.Id, Entity = d.Current };
if (!rAvailable && !dAvailable)
break;
}
}
}
If you happen to be using the MoreLinq NuGet package that includes the ZipLongest extension method, you can use the following simplified implementation instead:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
foreach (var item in restaurants.ZipLongest(dishes, (r, d) => new { r, d }))
{
if (item.r != null)
yield return new RecommenderItem { Id = item.r.Id, Entity = item.r };
if (item.d != null)
yield return new RecommenderItem { Id = item.d.Id, Entity = item.d };
}
}
Addendum
As #InBetween posted in his answer, you can put the interleaving logic into an extension method. Here's my version; it's substantially the same, except I've added a small optimisation to avoid calling .MoveNext() when its not necessary:
public static class EnumerableExt
{
public static IEnumerable<T> Interleave<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
using (var ae = a.GetEnumerator())
using (var be = b.GetEnumerator())
{
bool aAvailable = true;
bool bAvailable = true;
while (aAvailable || bAvailable)
{
aAvailable = aAvailable && ae.MoveNext();
bAvailable = bAvailable && be.MoveNext();
if (aAvailable)
yield return ae.Current;
if (bAvailable)
yield return be.Current;
}
}
}
}
Once you have that, I realised that you don't need to write an implict operator. Instead, you can just convert the two sequences to the resultant type before calling Interleave() like so:
var restaurantsAsRecommenderItems =
restaurantRecommendations
.Select(r => new RecommenderItem {Id = r.Id, Entity = r});
var dishesAsRecommenderItems =
dishRecommendations
.Select(d => new RecommenderItem {Id = d.Id, Entity = d});
var result =
restaurantsAsRecommenderItems
.Interleave(dishesAsRecommenderItems)
.ToList();
My recommendation would be to just make simple implicit operator :
public static implicit operator RecommenderItem(Restaurant restaurant) {
return new RecommenderItem { Id = restaurant.Id, Entity = restaurant };
}
Then you have possibility to convert these types easily like :
Restaurant rest = //...
RecommenderItem rItem = rest; // here the implicit operator is called
After doing this you can just use one for loop :
int count = Math.Max(restaurantRecommendations.Count, dishRecommendations.Count);
for ( int i = 0; i < count; i++ ) {
if ( i < restRecommendations.Count )
output.Add(restRecommendations[i]);
if ( i < dishRecommendations.Count )
output.Add(dishRecommendations[i]);
}
This will make your work much more easier.
Well, there are probably more elegant LINQ solutions but you have already most, it's also a very efficient approach:
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations, List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = Math.Min(restaurantRecommendations.Count, dishRecommendations.Count);
for (int i = 0; i < count; i++)
{
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
int remainingRestaurant = restaurantRecommendations.Count - count;
int remainingDishes = dishRecommendations.Count - count;
if (remainingRestaurant > 0)
{
for (int i = count; i < restaurantRecommendations.Count; i++)
{
var restRecommendation = restaurantRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
}
}
else if (remainingDishes > 0)
{
for (int i = count; i < dishRecommendations.Count; i++)
{
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
}
return output;
}
A simple way of doing it would be:
public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
{
while (firstEnumerator.MoveNext())
{
yield return firstEnumerator.Current;
if (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
while (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
}
After having created two arrays of restaurants and dishes of the same type RecommenderItem, you can use the Zip method like :
var restaurants = restaurantRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var dishes = dishRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var output = restaurants.Zip(dishes, (r, d) => new[] { r, d })
.SelectMany(r => r).Concat(dishes.Skip(restaurants.Length))
.Concat(restaurants.Skip(dishes.Length));
Restaraunt and Dish would have to share a base type:
restaurantRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
dishRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
Once that's the case you could use something like this slightly modified version of Zip (from System.Linq):
private static IEnumerable<T> ZipThrough<T>(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
using (var e1 = first.GetEnumerator())
{
using (var e2 = second.GetEnumerator())
{
while (true)
if (e1.MoveNext())
{
yield return e1.Current;
if (e2.MoveNext()) yield return e2.Current;
}
else if (e2.MoveNext())
{
yield return e2.Current;
}
else
{
break;
}
}
}
}

How add only new city?

I have CreateDiscountViewByUser discountViewByUser it contains a list of cities that are chosen by the user, but they may already be those cities that have been added.
List<DiscountCity> discountCities = (from city in db.DiscountCities
where city.DiscountId == discountViewByUser.Id
select city).ToList();
for (int y = 0; y < discountCities.Count(); y++)
{
var dc = discountCities[y];
bool flag = false;
for (int i = 0; i < discountViewByUser.DiscountCitys.Length; i++)
{
if (dc.CityId == discountViewByUser.DiscountCitys[i])
{
flag = true;
discountCities.Remove(dc);
y--;
}
}
if (!flag)
{
db.DiscountCities.DeleteObject(dc);
}
}
foreach (var dc in discountCities)
{
DiscountCity discountCity = new DiscountCity
{Id = Guid.NewGuid(),
CityId = dc.CityId,
DiscountId = main.Id};
db.DiscountCities.AddObject(discountCity);
}
how to add only the new city?
My code does not work = (
UPDATE:
discountViewByUser.DiscountCitys type int[].
db.DiscountCities table: Id DiscountId CityId.
example:
in database: Odessa, Kiev
user set: Odessa, Moscow.
I need delete Kiev and add moscow how do this?
What I recommend is adding all the items and then removing duplicates.
// Where uniqueList is a List<T> of unique items:
uniqueList.AddRange(valuesToAdd);
uniqueList = uniqueList.Distinct(new CityEqualityComparer()).ToList();
// Sorry, I don't know how this would fit into your code
Since you are comparing cities by their CityId's, you will probably need to use a custom IEqualityComparer to determine which cities are duplicates.
Here is an example of such a class:
class CityEqualityComparer : IEqualityComparer<City>
{
public bool Equals(City arg1, City arg2)
{
return arg1.CityId == arg2.CityId;
}
public int GetHashCode(City arg)
{
return arg.CityId;
}
}
This question may also be of some help.
I suggest you do this in 2 steps.
1) Find the cities to be deleted
var deleteCities = db.DiscountCities.Where(c => c.DiscountId == discountViewByUser.Id
&& !discountViewByUser.DiscountCitys.Contains(c.CityId));
foreach(deleteCity in deleteCities)
{
db.DiscountCities.DeleteObject(deleteCity);
}
2) Find cityId's to be inserted
var insertCities = discountViewByUser.DiscountCitys.Except(
db.DiscountCities.Where(c => c.DiscountId == discountViewByUser.Id)
.Select(c => c.CityId));
foreach(var insertCity in insertCities)
{
DiscountCity discountCity = new DiscountCity
{Id = Guid.NewGuid(), CityId = insertCity, DiscountId = discountViewByUser.Id};
db.DiscountCities.AddObject(discountCity);
}

WPF list filtering

I am new to WPF so this is probably an easy question. I have an app that reads some words from a csv file and stores them in a list of strings. What I am trying to do is parametise this list to show the most popular words in my list. So in my UI I want to have a text box which when I enter a number e.g. 5 would filter the original list leaving only the 5 most popular (frequent) words in the new list. Can anyone assist with this final step? Thanks -
public class VM
{
public VM()
{
Words = LoadWords(fileList);
}
public IEnumerable<string> Words { get; private set; }
string[] fileList = Directory.GetFiles(#"Z:\My Documents\", "*.csv");
private static IEnumerable<string> LoadWords(String[] fileList)
{
List<String> words = new List<String>();
//
if (fileList.Length == 1)
{
try
{
foreach (String line in File.ReadAllLines(fileList[0]))
{
string[] rows = line.Split(',');
words.AddRange(rows);
}
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message, "Problem!");
}
}
else
{
System.Windows.MessageBox.Show("Please ensure that you have ONE read file in the source folder.", "Problem!");
}
return words;
}
}
A LINQ query that groups by the word and orders by the count of that word descending should do it. Try this
private static IEnumerable<string> GetTopWords(int Count)
{
var popularWords = (from w in words
group w by w
into grp
orderby grp.Count() descending
select grp.Key).Take(Count).ToList();
return popularWords;
}
You could use CollectionViewSource.GetDefaultView(viewModel.Words), which returns ICollectionView.
ICollectionView exposes Filter property of type Predicate<object>, that you could involve for filtering.
So the common scenario looks like:
ViewModel exposes property PopularCount, that is binded to some textbox in View.
ViewModel listens for PopularCount property's changing.
When notification occured, model obtains ICollectionView for viewModel.Words collection and set up Filter property.
You could find working sample of Filter property usage here. If you get stuck with code, let me know.
Instead of reading all the words into the list and then sorting it based on the frequency, a cleaner approach would be to create a custom class MyWord that stores the word and the frequency. While reading the file, the frequency of the word can be incremented. The class can implement IComparable<T> to compare the words based on the frequency.
public class MyWord : IComparable<MyWord>
{
public MyWord(string word)
{
this.Word = word;
this.Frequency = 0;
}
public MyWord(string word, int frequency)
{
this.Word = word;
this.Frequency = frequency;
}
public string Word { get; private set;}
public int Frequency { get; private set;}
public void IncrementFrequency()
{
this.Frequency++;
}
public void DecrementFrequency()
{
this.Frequency--;
}
public int CompareTo(MyWord secondWord)
{
return this.Frequency.CompareTo(secondWord.Frequency);
}
}
The main class VM would have these members,
public IEnumerable<MyWord> Words { get; private set; }
private void ShowMostPopularWords(int numberOfWords)
{
SortMyWordsDescending();
listBox1.Items.Clear();
for (int i = 0; i < numberOfWords; i++ )
{
listBox1.Items.Add(this.Words.ElementAt(i).Word + "|" + this.Words.ElementAt(i).Frequency);
}
}
And the call to ShowMostPopularWords()
private void Button_Click(object sender, RoutedEventArgs e)
{
int numberOfWords;
if(Int32.TryParse(textBox1.Text, NumberStyles.Integer, CultureInfo.CurrentUICulture, out numberOfWords))
{
ShowMostPopularWords(numberOfWords);
}
}
I'm not sure if grouping and ordering of the 'words' list is what you want but if yes this could be a way of doing it:
int topN = 3;
List<string> topNWords = new List<string>();
string[] words = new string[] {
"word5",
"word1",
"word1",
"word1",
"word2",
"word2",
"word2",
"word3",
"word3",
"word4",
"word5",
"word6",
};
// [linq query][1]
var wordGroups = from s in words
group s by s into g
select new { Count = g.Count(), Word = g.Key };
for (int i = 0; i < Math.Min(topN, wordGroups.Count()); i++)
{
// (g) => g.Count is a [lambda expression][2]
// .OrderBy and Reverse are IEnumerable extension methods
var element = wordGroups.OrderBy((g) => g.Count).Reverse().ElementAt(i);
topNWords.Add(element.Count + " - " + element.Word);
}
Thsi could be made much shorter by using ordering in the linq select clause but I wished to introduce you to inline lambdas and ienumerable extensions too.
The short version could be:
topNWords = (from s in words
group s by s
into g
orderby g.Count() descending
select g.Key).Take(Math.Min(topN, g.Count()).ToList();

Categories