i have a list of writers.
public class Writers{
long WriterID { get;set; }
}
Also I have two lists of type Article.
public class Article{
long ArticleID { get; set; }
long WriterID { get; set; }
//and others
}
so the code i have is:
List<Article> ArticleList = GetList(1);
List<Article> AnotherArticleList = AnotherList(2);
List<Writers> listWriters = GetAllForbiddenWriters();
I want to remove those records from ArticleList, AnotherArticleList where WriterID matches from listWriters WriterID. How to do this in LINQ?
If you've actually got a List<T>, I suggest you use List<T>.RemoveAll, after constructing a set of writer IDs:
HashSet<long> writerIds = new HashSet<long>(listWriters.Select(x => x.WriterID));
articleList.RemoveAll(x => writerIds.Contains(x.WriterId));
anotherArticleList.RemoveAll(x => writerIds.Contains(x.WriterId));
If you do want to use LINQ, you could use:
articleList = articleList.Where(x => !writerIds.Contains(x.WriterId))
.ToList();
anotherArticleList = anotherArticleList
.Where(x => !writerIds.Contains(x.WriterId))
.ToList();
Note that this changes the variable but doesn't modify the existing list - so if there are any other references to the same list, they won't see any changes. (Whereas RemoveAll modifies the existing list.)
articlesList.RemoveAll(a => listWriters.Exists(w => w.WriterID == a.WriterID));
anotherArticlesList.RemoveAll(a => listWriters.Exists(w => w.WriterID == a.WriterID));
You can use Except:
List<car> list1 = GetTheList();
List<car> list2 = GetSomeOtherList();
List<car> result = list2.Except(list1).ToList();
I do not really see what is the difficulty you are facing...
Why don't you just filter/remove data from you lists using a simple for loop ?
(Note that a foreach Loop will definitely NOT work if you iterate while editing/changing the iterated object)
for (int i = ArticleList.Count -1; i >= 0; i--)
{
for (int j = 0; j < listWriters.Count; j++)
{
if (ArticleList[i].WriterId == listWriters[j].WriterID )
ArticleList.RemoveAt(i);
}
}
The Backward iteration trick solves the "delete items while iterating" paradigm.
Just a design tip, your class should be called Writer (singular form), not Writers (plural). Each item in your list represents a single writer, correct?
Related
I have two lists with Classes
public class Product
{
int id;
string url;
ect.
}
I need compare in the old list (10k+ elements) a new list(10 elements) by ID
and if an id is same just replace data from new List to old list
I think it will be good using LINQ.
Can you help me how can I use LINQ or there are batter library?
Do you need to modify the collection in place or return a new collection?
If you are returning a new collection you could
var query = from x in oldItems
join y in newItems on y.Id equals x.Id into g
from z in g.DefaultIfEmpty()
select z ?? x;
var new List = query.ToList();
This method will ignore entries in newItems that do not exist in old items.
If you are going to be modifying the collection in place you would be better off working with a dictionary and referencing that everywhere.
You can create a dictionary from the list by doing
var collection = items.ToDictionary(x => x.Id, x => x);
Note modifying the dictionary doesn't alter the source collection, the idea is to replace your collection with the dictionary object.
If you are using the dictionary you can then iterate over new collection and check the key.
foreach (var item in newItems.Where(x => collection.ContainsKey(x.Id))) {
collection[item.Id] = item;
}
Dictionaries are iterable so you can loop over the Values collection if you need to. Adds and removes are fast because you can reference by key. The only problem I can think you may run into is if you rely on the ordering of the collection.
If you are stuck needing to use the original collection type then you could use the ToDictionary message on your newItems collection. This makes your update code look like this.
var converted = newItems.ToDictionary(x => x.Id, x => x);
for (var i = 0; i < oldItems.Count(); i++) {
if (converted.ContainsKey(oldItems[i].Id)) {
oldItems[i] = converted[oldItems[i].Id];
}
}
This has the advantage the you only need to loop the newitems collection once, from then on it's key lookups, so it's less cpu intensive. The downside is you've created an new collection of keys for newitems so it consumes more memory.
Send you a sample function that joins the two list by id property of both lists and then update original Product.url with the newer one
void ChangeItems(IList<Product> original, IList<Product> newer){
original.Join(newer, o => o.id, n => n.id, (o, n) => new { original = o, newer = n })
.ToList()
.ForEach(j => j.original.Url = j.newer.Url);
}
Solution :- : The LINQ solution you're look for will be something like this
oldList = oldList.Select(ele => { return (newList.Any(i => i.id == ele.id) ? newList.FirstOrDefault(newObj => newObj.id == ele.id) : ele); }).ToList();
Note :- Here we are creating the OldList based on NewList & OldList i.e we are replacing OldList object with NewList object.If you only want some of the new List properties you can create a copy Method in your class
EG for copy constructor
oldList = oldList.Select(ele => { return (newList.Any(i => i.id == ele.id) ? ele.Copy(newList.FirstOrDefault(newObj => newObj.id == ele.id)) : ele); }).ToList();
//Changes in your class
public void Copy(Product prod)
{
//use req. property of prod. to be replaced the old class
this.id = prod.id;
}
Read
It is not a good idea to iterate over 10k+ elements even using linq as such it will still affect your CPU performance*
Online sample for 1st solution
As you have class
public class Product
{
public int id;
public string url;
public string otherData;
public Product(int id, string url, string otherData)
{
this.id = id;
this.url = url;
this.otherData = otherData;
}
public Product ChangeProp(Product newProd)
{
this.url = newProd.url;
this.otherData = newProd.otherData;
return this;
}
}
Note that, now we have ChangeProp method in data class, this method will accept new class and modify old class with properties of new class and return modified new class (as you want your old class be replaced with new classes property (data). So at the end Linq will be readable and clean.
and you already have oldList with lots of entries, and have to replace data of oldList by data of newList if id is same, you can do it like below.
suppose they are having data like below,
List<Product> oldList = new List<Product>();
for (int i = 0; i < 10000; i++)
{
oldList.Add(new Product(i, "OldData" + i.ToString(), "OldData" + i.ToString() + "-other"));
}
List<Product> newList = new List<Product>();
for (int i = 0; i < 5; i++)
{
newList.Add(new Product(i, "NewData" + i.ToString(), "NewData" + i.ToString() + "-other"));
}
this Linq will do your work.
oldList.Where(x => newList.Any(y => y.id == x.id))
.Select(z => oldList[oldList.IndexOf(z)].ChangeProp(newList.Where(a => a.id == z.id).FirstOrDefault())).ToList();
foreach(var product in newList)
{
int index = oldList.FindIndex(x => x.id == product.id);
if (index != -1)
{
oldList[index].url = product.url;
}
}
This will work and i think it's a better solution too.
All the above solution are creating new object in memory and creating new list with 10k+
records is definitely a bad idea.
Please make fields in product as it won't be accessible.
I want to create a loop to check a list of titles for duplicates.
I currently have this:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach (var x in productTitles)
{
var title = x.Text;
productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach (var y in productTitles.Skip(productTitles.IndexOf(x) + 1))
{
if (title == y.Text)
{
Assert.Fail("Found duplicate product in the table");
}
}
}
But this is taken the item I skip out of the array for the next loop so item 2 never checks it's the same as item 1, it moves straight to item 3.
I was under the impression that skip just passed over the index you pass in rather than removing it from the list.
You can use GroupBy:
var anyDuplicates = SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.GroupBy(p => p.Text, p => p)
.Any(g => g.Count() > 1);
Assert.That(anyDuplicates, Is.False);
or Distinct:
var productTitles = SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.Select(p => p.Text)
.ToArray();
var distinctProductTitles = productTitles.Distinct().ToArray();
Assert.AreEqual(productTitles.Length, distinctProductTitles.Length);
Or, if it is enough to find a first duplicate without counting all of them it's better to use a HashSet<T>:
var titles = new HashSet<string>();
foreach (var title in SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.Select(p => p.Text))
{
if (!titles.Add(title))
{
Assert.Fail("Found duplicate product in the table");
}
}
All approaches are better in terms of computational complexity (O(n)) than what you propose (O(n2)).
You don't need a loop. Simply use the Where() function to find all same titles, and if there is more than one, then they're duplicates:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach(var x in productTitles) {
if (productTitles.Where(y => x.Text == y.Text).Count() > 1) {
Assert.Fail("Found duplicate product in the table");
}
}
I would try a slightly different way since you only need to check for duplicates in a one-dimensional array.
You only have to check the previous element with the next element within the array/collection so using Linq to iterate through all of the items seems a bit unnecessary.
Here's a piece of code to better understand:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
for ( int i = 0; i < productionTitles.Length; i++ )
{
var currentObject = productionTitles[i];
for ( int j = i + 1; j < productionTitles.Length; j++ )
{
if ( currentObject.Title == productionTitles[j].Title )
{
// here's your duplicate
}
}
}
Since you've checked that item at index 0 is not the same as item placed at index 3 there's no need to check that again when you're at index 3. The items will remain the same.
The Skip(IEnumerable, n) method returns an IEnumerable that doesn't "contain" the n first element of the IEnumerable it's called on.
Also I don't know what sort of behaviour could arise from this, but I wouldn't assign a new IEnumerable to the variable over which the foreach is being executed.
Here's another possible solution with LINQ:
int i = 0;
foreach (var x in productTitles)
{
var possibleDuplicate = productTitles.Skip(i++).Find((y) => y.title == x.title);
//if possibleDuplicate is not default value of type
//do stuff here
}
This goes without saying, but the best solution for you will depend on what you are trying to do. Also, I think the Skip method call is more trouble than it's worth, as I'm pretty sure it will most certainly make the search less eficient.
The basic question
I have:
IEnumerable<string> listA
var listB (this is an anonymous list generated by a LINQ query)
I want to query a list of objects that contain listA to see if they match to listB:
someObjectList.Where(x => x.listA == listB)
The comparison doesn't work - so how do I ensure that both lists are the same type for comparison?
The detailed question
I am grouping a larger list into a subset that contains a name and related date(s).
var listGroup = from n in list group n by new
{ n.NAME } into d
select new
{
NAME = d.Key.NAME, listOfDates = from x in d select new
{ Date = x.DATE } };
I have a object to hold the values for further processing:
class SomeObject
{
public SomeObject()
{
_listOfDates = new List<DateTime>();
}
private IEnumerable<DateTime> _listOfDates;
public IEnumerable<DateTime> ListOfDates
{
get { return _listOfDates; }
set { _listOfDates = value; }
}
}
I am then iterating over the listGroup and adding into a generic List<> of SomeObject:
foreach(var item in listGroup)
{
SomeObject so = new SomeObject();
// ...do some stuff
if (some match occurs then add into List<SomeObject>)
}
As I iterate through then I want to check the existing List<SomeOjbect> for matches:
var record = someObjectList.Where(x => x.NAME == item.NAME &&
x.ListOfDates == item.listOfDates)
.SingleOrDefault();
The problem is that comparing x.ListOfDates against item.listOfDates doesn't work.
There is no compiler error but I suspect that the returned value lists are different. How to I get the lists to commonize so they can be compared?
Update #1
This seems to work to get the listOfDates into a similar format:
IEnumerable<DateTime> tempList = item.listOfDates.Select(x => x.DATE).ToList()
Then I followed the 'SequenceEqual' suggestion from #Matt Burland
You can just compare one IEnumerable<DateTime> to another IEnumerable<DateTime>, you need to compare the sequence. Luckily, there's Enumerable.SequenceEquals (in both static and extension method flavors) which should work here.
So something like:
var record = someObjectList
.Where(x => x.NAME == item.NAME && x.ListOfDates.SequenceEquals(item.listOfDates))
.SingleOrDefault();
I have a cart.Lines List and want to remove all items where quantity == 0
This is a list that holds collection of CartLine objects:
public class Cart
{
private IList<CartLine> lines = new List<CartLine>();
public IList<CartLine> Lines { get { return lines; } set { lines = value; } }
}
public class CartLine
{
Product Product {get; set;}
int Quantity {get; set;}
}
So something like:
cart.Lines.RemoveAll(x => x.Quantity == 0)
I only get Remove and RemoveAt, not RemoveAll !
Also can't remove in a foreach loop, get error:
Collection was modified; enumeration operation may not execute.
I have now managed to do it with this code, surely there must be something more efficient ?
var myList = cart.Lines.ToList();
myList.RemoveAll(x => x.Quantity == 0);
cart.Lines = myList;
Okay Problem solved!Thanks guys, this here does it:
cart.Lines = cart.Lines.Where(x => x.Quantity != 0);
If Lines is a List<T>, then the simplest way is to just write:
cart.Lines.RemoveAll(x => x.Quantity == 0);
If Lines is an IEnumerable<T>, though, you could select the negative (as Vlad suggested) - you could also change to a list using ToList() and then do RemoveAll() but that would be overkill.
cart.Lines = cart.Lines.Where(x => x.Quantity != 0);
UPDATE:
Since you said Lines is an IList<T>, then you will want to select the negative and convert to a list like:
cart.Lines = cart.Lines.Where(x => x.Quantity != 0).ToList();
Or you can conver to a List<T> using ToList() then call RemoveAll() and then save back:
var temp = cart.Lines.ToList();
temp.RemoveAll(x => x.Quantity != 0);
cart.Lines = temp;
Incidentally, as an FYI I timed both building a remove list and then using Remove() vs selecting the negative using Where() and calling ToList() and the Where/ToList combo was much faster which makes sense because both allocate memory, but the Where/ToList does a lot less memory shuffling.
Here's the timing for removing all the even numbers out of a list of 100,000 ints:
Removing all evens building a remove list and calling Remove() on each took: 3921 ms
Removing all evens using Where() on negative and then ToList() took: 2 ms
Removing all evens using ToList() on original then RemoveAll() took: 1 ms
Assuming that cart.Lines is List<>: cart.Lines.RemoveAll(x => x.Quantity == 0);
These queries essentially foreach over a list, and as you know, you shouldn't use them to directly modify the list. Rather you should make a list of the items to be removed using the query, and then remove them in a separate operation.
EDIT:
yeh, I forgot you can use RemoveAll to do this in one line :D
var myResult = cart.Lines.Where(x => x.Quantity > 0)
Alternatively you can use RemoveAll
cart.Lines.RemoveAll(x => x.Quantity == 0)
Check this post that answers your question C# using LINQ to remove objects within a List
I will go ahead and post my suggestion to this problem.
private IList<CartLine> lines = new List<CartLine>();
should be:
private List<CartLine> lines = new List<CartLine>();
This will allow you to use the suggested method of:
cart.Lines.RemoveAll(x => x.Quantity == 0);
You do exactly that by doing it this way:
var myList = cart.Lines.ToList();
myList.RemoveAll(x => x.Quantity == 0);
cart.Lines = myList;
You can do it as follows:
Cart cart = new Cart();
List<CartLine> cartLines = cart.Lines.ToList<CartLine>();
cartLines.RemoveAll(x => x.Quantity == 0);
cart.Lines = cartLines;
Also, you should set the CartLine Quantity and Product properties as public.
I'd like to calculate the TCC metric:
The Tight Class Cohesion (TCC)
measures the ratio of the number of
method pairs of directly connected
visible methods in a class NDC(C) and
the number of maximal possible method
pairs of connections between the
visible methods of a class NP(C). Two
visible methods are directly
connected, if they are accessing the
same instance variables of the class.
n is the number of visible methods
leading to:
NP(C) = (n(n-1))/2
and
TCC(C) = NDC(C) / NP(C)
So i wrote a method that parse through all methods in the class i want to check. This method stores all methods in that class and there fields they are using in a dictionary that looks like this:
Dictionary<MethodDefinition, IList<FieldReference>> references = new Dictionary<MethodDefinition, IList<FieldReference>>();
So now, how do I iterate through this dictionnary to check the condition mentioned above? If I understand it correctly I have to find these two pairs of methods that are using the same set of fields? Then how can I do this the best way? I think I have to iterate over the dictionary and see if the IList contains the same set? (even not in the same order)?
Any oder ideas`?
My code is the following, but it does not work correctly:
class TCC
{
public static int calculate(TypeDefinition type)
{
int count = 0;
Dictionary<MethodDefinition, HashSet<FieldReference>> references = new Dictionary<MethodDefinition, HashSet<FieldReference>>();
foreach (MethodDefinition method in type.Methods)
{
if (method.IsPublic)
{
references.Add(method, calculateReferences(method));
}
}
for (int i = 0; i < references.Keys.Count; i++)
{
HashSet<FieldReference> list = new HashSet<FieldReference>();
references.TryGetValue(references.Keys.ElementAt(i), out list);
if (isPair(references, list)) {
count++;
}
}
if (count > 0)
{
count = count / 2;
}
return count;
}
private static bool isPair(Dictionary<MethodDefinition, HashSet<FieldReference>> references, HashSet<FieldReference> compare)
{
for (int j = 0; j < references.Keys.Count; j++)
{
HashSet<FieldReference> compareList = new HashSet<FieldReference>();
references.TryGetValue(references.Keys.ElementAt(j), out compareList);
for (int i = 0; i < compare.Count; i++)
{
if (containsAllElements(compareList, compare)) {
return true;
}
}
}
return false;
}
private static bool containsAllElements(HashSet<FieldReference> compareList, HashSet<FieldReference> compare)
{
for (int i = 0; i < compare.Count; i++)
{
if (!compareList.Contains(compare.ElementAt(i)))
{
return false;
}
}
return true;
}
private static HashSet<FieldReference> calculateReferences(MethodDefinition method)
{
HashSet<FieldReference> references = new HashSet<FieldReference>();
foreach (Instruction instruction in method.Body.Instructions)
{
if (instruction.OpCode == OpCodes.Ldfld)
{
FieldReference field = instruction.Operand as FieldReference;
if (field != null)
{
references.Add(field);
}
}
}
return references;
}
}
Well, if you don't mind keeping another dictionary, we can hit this thing with a big-durn-hammer.
Simply put, if we imagine a dictionary where ordered_set(field-references) is the key instead, and we keep a list of the values for each key.... Needless to say this isn't the most clever approach, but it is quick, easy, and uses data structures you are already familiar with.
EG:
hashset< hashset < FieldReference >, Ilist< methods >> Favorite_delicatessen
Build ReferenceSet for method
Look up ReferenceSet in Favorite_Delicatessen
If there:
Add method to method list
Else:
Add Referenceset,method pair
And your methods list is thus the list of methods that share the same state-signature, if you'll let me coin a term.
Since you didn't tell us how can we tell two FieldReferences are duplicated, I will use the default.
LINQ version:
int duplicated = references.SelectMany( p => p.Value )
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Count();
Can you use ContainsValue to check for duplicates? From what you described it appears you only have duplicates if the values are the same.
How about getting a dictionary where the key is the duplicate item, and the value is a list of keys from the original dictionary that contain the duplicate:
var dupes = references
.SelectMany(k => k.Value)
.GroupBy(v => v)
.Where(g => g.Count() > 1)
.ToDictionary(i => i.Key, i => references
.Where(f => f.Value.Contains(i.Key))
.Select(o => o.Key));