I write a simple program using c# 3.5 and linq .
i have class
public class Product
{
public Product()
{
}
public int RoleId { get; set; }
public int ObjectId { get; set; }
public bool Read { get; set; }
public override bool Equals(object obj)
{
return Equals((Product) obj);
}
public bool Equals(Product other)
{
return ObjectId == other.ObjectId && Read == other.Read;
}
}
I am trying to compair list.
List<Product> products = new List<Product>()
{
new Product { RoleId = 1, ObjectId = 2, Read = false },
new Product { RoleId = 2, ObjectId = 1, Read = false },
new Product { RoleId = 1, ObjectId = 1, Read = true }
};
var groupedCustomerList = products.GroupBy(u => u.RoleId)
.Select(grp => grp.ToList()).ToList();
var firstGroup = groupedCustomerList.ElementAt(0);
List<Product> productsListSearch = new List<Product>()
{
new Product {ObjectId = 1, Read = true },
new Product {ObjectId = 2, Read = false }
};
var result= productsListSearch.SequenceEqual(firstGroup);
Why the result is incorrect ?
I need to sort the items?
They are not sequence-equal because the objects come in different order. If you change productsListSearch like this
List<Product> productsListSearch = new List<Product>()
{
new Product {ObjectId = 2, Read = false }
, new Product {ObjectId = 1, Read = true }
};
SequenceEqual would return True.
Demo on ideone.
Generally, you should not rely on the order of items in LINQ-generated groups, unless you set the order yourself:
var groupedCustomerList = products.GroupBy(u => u.RoleId)
.Select(grp => grp.OrderBy(p => ...).ToList()).ToList();
Note: your Product class overrides Equals without overriding GetHashCode. This will be problematic if you decide to use Product in hash sets or as keys of hash-based dictionaries.
The lists are in different orders. Change second list to
List<Product> productsListSearch = new List<Product>()
{
new Product {ObjectId = 2, Read = false },
new Product {ObjectId = 1, Read = true }
};
and the result will be true
SequenceEquals means the sequence has to be equal. You have the same elements in both collections but in a different sequence.
I assume that your second data set is how you expect to see your main data once it is grouped and sorted. In order for that to happen you need to change this line so that your grouped items will be sorted:
var groupedCustomerList = products.GroupBy (u => u.RoleId)
.Select (u => u.OrderBy (x => x.ObjectId));
And this is a cleaner looking way to get the first element:
var firstGroup = groupedCustomerList.First ();
Related
I have an array or list of objects returned from the Database. Let's take this as an example:
this is the class:
public class products
{
public string name { get; set; }
public int quantity { get; set; }
public long id{ get; set; }
}
List<Product> prod = new List<Product>()
prod = (call to the DB to get data back...)
array is returned with object of products
What I need is to loop through this array and add up the quantities for the same ids. Meaning, add up 2+7 for id 3 and add that data to another object so that new object would have something like: total: 9, id: 3
then same again for id 5, total: 7, id: 5
and so on.
I am at a loss of the right way to do this. I was looking at Linq but the only way I used it is by providing specific values. Please help me with this
`
foreach (var p in prod){ Now what do i do?}
`
The easiest way is with GroupBy and Sum (Both use System.Linq):
List<Product> products = new List<Product>()
{
new Product(){Id = 1, Cost = 20.0M },
new Product(){Id = 1, Cost = 30.0M },
new Product(){Id = 2, Cost = 20.0M },
new Product(){Id = 3, Cost = 20.0M },
new Product(){Id = 3, Cost = 5.0M }
};
products.GroupBy(g => g.Id).ToList().ForEach(e =>
{
Console.WriteLine($"Total: {
e.Sum(s => s.Cost)} for {e.Key}");
});
Edit
With the new information provided: You can do this to a concrete class:
public class Product
{
public string name { get; set; }
public int quantity { get; set; }
public long id{ get; set; }
}
List<Product> products = new List<Product>();
var products = (call to the DB to get data back...).GroupBy(g => g.Id).ToList().ForEach(e =>
{
products.Add(new Product()
{
Id = e.Key,
Quantity = e.Sum(s => s.Quantity)
})
});
Per your code snippet, prod is single product of type products.
So, assuming your code which invokes database call returns prod list something like below:
List<products> prod = new List<products>();
prod = _productRepository.GetProductData(prodId);
You can use linq GroupBy (please include System.Linq to use GroupBy linq extension method) to find the total quantity of each product like below:
var anonymousList = prod.GroupBy(p => p.id).Select(g => new {id = g.Key, totalQuantity = g.Sum(p => p.quantity)}).ToList()
The above returns anonymous list of objects where each object contains id and totalQuantity.
If you are interested in dictionary of product id vs totalQuantity, then use something like below:
Dictionary<long, int> dictionary = prod.GroupBy(p => p.id).ToDictionary(k => k.Key, v => v.Sum(p => p.quantity));
UPDATE based on comments discussion:
You can invoke GroupBy on prod without verifying the count. No exception will be thrown.
I am wondering if/how I can do the following thing using LINQ: I have a list of objects with some properties and another list of different distinct values corresponding to a certain property.
Example:
A = [{id=1, property=1}, {id=2, property=1}, {id=3, property=2}]
B = [1, 2]
Is there a way of achieving the following thing (obtaining the counts list) using only LINQ?
var counts = new List<int>();
foreach (var b in B)
{
counts.Add(A.Where(a => a.property == b).Count();
}
Sample code:
public class MyObject
{
public MyObject(int id, int prop)
{
Id = id;
Property = prop;
}
public int Id { get; set; }
public int Property { get; set; }
public void test()
{
var A = new List<MyObject>
{
new MyObject(1, 1), new MyObject(2, 1), new MyObject(3, 2)
};
var B = new List<int>{1, 2};
// LINQ magic
// 2 objects with property 1
// 1 object with property 2
}
}
Yes, use select operators to only select the specific properties you want to compare, and then use intersect and count to get the count. Example:
var listOfObjects = new List<PocoClass>()
{
new PocoClass(){Id=1,Property=3},
new PocoClass(){Id=2,Property=2}
};
var intArray = new int[] { 1, 2, 3 };
var count = listOfObjects.Select(o => o.Property).Intersect(intArray).Count();
Sure, you can just loop through the values and, for each one, get the count of items that have Property == value.
In the sample below, I'm selecting an anonymous type that contains the Value and the Count of each item that has Property == value:
public class Data
{
public int Id { get; set; }
public int Property { get; set; }
}
public class Program
{
private static void Main()
{
var allData = new List<Data>
{
new Data {Id = 1, Property = 1},
new Data {Id = 2, Property = 1},
new Data {Id = 3, Property = 2},
};
var values = new[] {1, 2};
var results = values.Select(value =>
new {Value = value, Count = allData.Count(item => item.Property == value)});
foreach (var result in results)
{
Console.WriteLine($"{result.Count} objects with Property {result.Value}");
}
}
}
Output
You can use Count method with a predicate:
var A = new[] {new {id = 1, property = 1}, new {id = 2, property = 1}, new {id = 3, property = 2}};
var B = new[] {1, 2};
var count = B.Count(b => A.Any(a => a.property == b));
Code above will check every member in B and if at least one member in A have a property with that value it will be counted
convert B to List and run ForEach on it
B.OfType<int>().ToList().ForEach(m=>{
counts.Add(A.Where(a => a.property == m).Count();
})
This is the gist of what you need. I'm typing this without a c# compiler, so hopefully this doesn't have errors.
var results =
from a in A
join b in B on a.property equals b
group a by a.property into g
select new { Property = g.Key, Count = g.Count() }
In this case, I have two different LINQ expressions to get count from Products for two different conditions. I was just curious if there could be anyway of retrieving these two counts from one LINQ expression?
class Program
{
static void Main(string[] args)
{
List<Product> Products = new List<Product>()
{
new Product() { ID = 1 },
new Product() { ID = 2 },
new Product() { ID = 3 },
new Product() { ID = 4 },
new Product() { ID = 5 },
new Product() { ID = 6 }
};
int all = Products.Count();
int some = Products.Where(x => x.ID < 2).Count();
}
}
public class Product
{
public int ID { get; set; }
}
Using Aggregate you can avoid iterating through your collection twice:
var result = Products.Aggregate(new {a=0, s=0},(p,c) =>
{
return new { a = p.a + 1, s = c.ID < 2 ? p.s + 1 : p.s };
});
Now result.a == 6 and result.s == 2
You can, of course, create a class to hold your result if you want instead of using an anonymous type, and it works much the same way. That maybe easier to deal with if you have to return it from a function, for example.
So you could do something like:
public class CountResult
{
public int All { get; set; }
public int Some { get; set; }
}
public CountResult GetMyCount(IEnumerable<Product> products)
{
return products.Aggregate(new CountResult(), (p,c) =>
{
p.All++;
if (c.ID < 2) // or whatever you condition might be
{
p.Some++;
}
return p;
});
}
You can do this using a Tuple<int, int>:
var result = new Tuple<int, int>(Products.Count, Products.Where(x => x.ID < 2).Count());
And plese remark the use of Products.Count property which has O(1) complexity instead of O(N), so you don't have to worry at all about the performance of this implementation. For further reading you can check this post.
I have a list of transaction class :
class Transactions
{
public Transactions()
{
Products = new List<Product>();
}
public string Date { get; set; }
public string TransactionID { get; set; }
public List<Product> Products { get; set; }
}
And a Product Class:
class Product
{
public decimal ProductCode { get; set; }
}
I have a list of Products like this:
List<Product> unioned = product.Union(secondProduct).ToList();
And I want Intersect of unioned and transaction products,
This code does not work:
var intersection = transactions.Where(q => q.Products.Intersect(unioned).Any());
I think the reason is transaction products length is variant and union length is fixed.
How can I do it?
Intersect is using the default equality comparer, so will do a reference check - i.e. compare that the object references are the same.
You need to use the overload which allows you to specify an equality comparer:
Thus:
public class ProductComparer : IEqualityComparer<Product>
{
public bool Equals(Product x, Product y)
{
// TODO - Add null handling.
return x.ProductCode == y.ProductCode;
}
public int GetHashCode(Product obj)
{
return obj.ProductCode.GetHashCode();
}
}
And then:
var intersection = transactions
.Where(q => q.Products.Intersect(unioned, new ProductComparer()).Any());
This test will now pass:
[TestMethod]
public void T()
{
Product p = new Product { ProductCode = 10M };
List<Product> product = new List<Product> { p };
List<Product> secondProduct = new List<Product> { new Product { ProductCode = 20M } };
List<Product> unioned = product.Union(secondProduct).ToList();
var transaction = new Transactions();
// add a different object reference
transaction.Products.Add(new Product { ProductCode = 10M });
IList<Transactions> transactions = new List<Transactions> { transaction };
var intersection = transactions
.Where(q => q.Products.Intersect(unioned, new ProductComparer()).Any());
Assert.AreEqual(1, intersection.Count());
}
Try this to solution without using Intersect. I use ProductCode to check if the Product is the same:
transactions.Where(q => q.Products.Exists(x => unioned.Exists(z => z.ProductCode == x.ProductCode))).ToList();
You could do something like:
List<Product> allproductsTrans = new List<Product>();
transactions.ForEach(p => allproductsTrans.Concat(p.Products));
var result = allproductsTrans.Intersect(unioned);
but as Slava Utesinov said in the comments, the following code would do the same:
transactions.SelectMany(x => x.Products).Intersect(unioned)
Instead of using EqualityComparer, you can intersect ids, and then, if needed, find objects by ids.
Your example:
transactions.Where(q => q.Products.Select(x => x.ProductCode).Intersect(unioned.Select(x => x.ProductCode)).Any());
General case:
var ids = list1.Select(x => x.Id).Intersect(list2.Select(x => x.Id));
var result = list1.Where(x => ids.Contains(x.Id));
Example classes:
public class Pallet
{
public int Id { get; set; }
public List<Location> Locations { get; set; }
}
public class Location
{
public int Id { get; set; }
}
Given this input:
var input = new List<Pallet>
{
new Pallet
{
Id = 1,
Locations = new List<Location>
{
new Location { Id = 1 },
new Location { Id = 3 }
}
},
new Pallet
{
Id = 2,
Locations = new List<Location>
{
new Location { Id = 2 }
}
},
new Pallet
{
Id = 1,
Locations = new List<Location>
{
new Location { Id = 1 },
new Location { Id = 4 }
}
},
};
Is there a nice LINQ way to collapse duplicates (in both parent and children collections) to the equivalent of this?
var output = new List<Pallet>
{
new Pallet
{
Id = 1,
Locations = new List<Location>
{
new Location { Id = 1 },
new Location { Id = 3 },
new Location { Id = 4 }
}
},
new Pallet
{
Id = 2,
Locations = new List<Location>
{
new Location { Id = 2 }
}
}
};
Yes, I can iterate over the collection and manually merge items, but I'm curious if LINQ would/can offer something more expressive.
You can use the GroupBy to gather the matching IDs together, then a Select to create new collections:
var output = input.GroupBy(pallet => pallet.Id)
.Select(grp => new Pallet {
Id = grp.Key,
Locations = grp.SelectMany(pallet => pallet.Locations).Distinct().ToList()
}).ToList();
The one catch to the above, is that Distinct() only works correctly on class types, if you either provide it with an IEqualityComparer<Location>, or have the "Location" class implement IEquatable<Location> (and also override object.GetHashCode):
public class Location : IEquatable<Location>
{
public int Id { get; set; }
public bool Equals(Location other)
{
//Check whether the compared object is null.
if (object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (object.ReferenceEquals(this, other)) return true;
return Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
Or, instead of all that jazz, you could just use a second GroupBy at select the first location in each group:
var output = input.GroupBy(pallet => pallet.Id)
.Select(grp => new Pallet {
Id = grp.Key,
Locations = grp.SelectMany(pallet => pallet.Locations)
.GroupBy(location => location.Id)
.Select(location => location.First())
.ToList()
}).ToList();
Is this one nice enough?
var output = from p in input
group p by p.Id into g
select new Pallet
{
Id = g.Key,
Locations = (from l in g.SelectMany(x => x.Locations)
group l by l.Id into gl
select new Location { Id = gl.Key }).ToList()
};