Combine properties values from a custom List with LINQ - linq

Lets assume I have a list of Persons, in the List I have the following objects:
Person = { ID= 1, State="CA"}
Person = { ID= 2, State="PA"}
Person = { ID= 1, State="NY"}
Person = { ID= 1, State="OH"}
Person = { ID= 3, State="FL"}
Person = { ID= 2, State="KC"}
How do I get a new List Where I can have new Persons by ID only once and if repeated get the State value and put it in the new Person object separated by comma. For example the new List will be
Person = { ID= 1, State="CA,NY,OH"}
Person = { ID= 2, State="PA,KC"}
Person = { ID= 3, State="FL"}
Is there a way to achieve this?

Use Enumerable.GroupBy and String.Join:
var result = persons.GroupBy(p => p.ID)
.Select(g => new Person{
ID = g.Key,
State = string.Join(",", g.Select(p => p.State))
}).ToList();

here is the extension method version
var persons = Person.GroupBy(p => p.ID).Select(p => new Person() { ID = p.Key, State = String.Join(",", p.Select(p2 => p2.State).ToArray()) }).ToList();

Related

LINQ - Group By child property

I have a list of users, each user has string array property called Tags. I am trying to get a unique list of tags and a total count, any idea what I a missing here? I am using LinqPad to write my test query, please see the example code:
void Main()
{
List<User> users = new List<User>(){
new User {Id = 1, Tags = new string[]{"tag1", "tag2"}},
new User {Id = 2, Tags = new string[]{"tag3", "tag7"}},
new User {Id = 3, Tags = new string[]{"tag7", "tag8"}},
new User {Id = 4, Tags = new string[]{"tag1", "tag4"}},
new User {Id = 5 },
};
var uniqueTags = users.Where(m=>m.Tags != null).GroupBy(m=>m.Tags).Select(m=> new{TagName = m.Key, Count = m.Count()});
uniqueTags.Dump();
// RESULT should BE:
// tag1 - Count(2)
// tag2 - Count(1)
// tag3 - Count(1)
// tag4 - Count(1)
// tag7 - Count(2)
// tag8 - Count(1)
}
public class User{
public int Id {get;set;}
public string[] Tags {get;set;}
}
You can flatten to IEnumerable<string> before grouping:
var uniqueTags = users.SelectMany(u => u.Tags ?? new string[0])
.GroupBy(t => t)
.Select(g => new { TagName = g.Key, Count = g.Count() } );
LINQPad C# Expression version:
new[] {
new { Id = 1, Tags = new[] { "tag1", "tag2" } },
new { Id = 2, Tags = new[] { "tag3", "tag7" } },
new { Id = 3, Tags = new[] { "tag7", "tag8" } },
new { Id = 4, Tags = new[] { "tag1", "tag4" } },
new { Id = 5, Tags = (string[])null }
}
.SelectMany(u => u.Tags ?? Enumerable.Empty<string>())
.GroupBy(t => t)
.Select(g => new { TagName = g.Key, Count = g.Count() } )

Using MinimumShouldMatch with terms query in elasticsearch

I am writing a query in nest for elasticsearch that matches to a list of countries - it cutrrently matches whenever any of the countries in the list is present in ESCountryDescription (a list of countries). I only want to match when all of the countries in CountryList match ESCountryDescription. I believe that I need to use MinimumShouldMatch as in this example http://www.elastic.co/guide/en/elasticsearch/reference/0.90/query-dsl-terms-query.html
a.Terms(t => t.ESCountryDescription, CountryList)
But I cannot find a way of adding MinimumShouldMatch into my query above.
You can apply MinimumShouldMatch patameter in TermsDescriptor. Here is an example:
var lookingFor = new List<string> { "netherlands", "poland" };
var searchResponse = client.Search<IndexElement>(s => s
.Query(q => q
.TermsDescriptor(t => t.OnField(f => f.Countries).MinimumShouldMatch("100%").Terms(lookingFor))));
or
var lookingFor = new List<string> { "netherlands", "poland" };
var searchResponse = client.Search<IndexElement>(s => s
.Query(q => q
.TermsDescriptor(t => t.OnField(f => f.Countries).MinimumShouldMatch(lookingFor.Count).Terms(lookingFor))));
And this is the whole example
class Program
{
public class IndexElement
{
public int Id { get; set; }
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed)]
public List<string> Countries { get; set; }
}
static void Main(string[] args)
{
var indexName = "sampleindex";
var uri = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(uri).SetDefaultIndex(indexName).EnableTrace(true);
var client = new ElasticClient(settings);
client.DeleteIndex(indexName);
client.CreateIndex(
descriptor =>
descriptor.Index(indexName)
.AddMapping<IndexElement>(
m => m.MapFromAttributes()));
client.Index(new IndexElement {Id = 1, Countries = new List<string> {"poland", "germany", "france"}});
client.Index(new IndexElement {Id = 2, Countries = new List<string> {"poland", "france"}});
client.Index(new IndexElement {Id = 3, Countries = new List<string> {"netherlands"}});
client.Refresh();
var lookingFor = new List<string> { "germany" };
var searchResponse = client.Search<IndexElement>(s => s
.Query(q => q
.TermsDescriptor(t => t.OnField(f => f.Countries).MinimumShouldMatch("100%").Terms(lookingFor))));
}
}
Regarding your problem
For terms: "netherlands" you will get document with Id 3
For terms: "poland" and "france" you will get documents with Id 1 and 2
For terms: "germany" you will get document with Id 1
For terms: "poland", "france" and "germany" you will get document
with Id 1
I hope this is your point.
Instead of doing
.Query(q => q
.Terms(t => t.ESCountryDescription, CountryList))
You can use the command below
.Query(q => q
.TermsDescriptor(td => td
.OnField(t => t.ESCountryDescription)
.MinimumShouldMatch(x)
.Terms(CountryList)))
See this for unit tests in elasticsearch-net Github repository.

LINQ RowNumber, Aggregate (Sum) and GroupBy

I have an SQL code like;
Select GroupName, sum(LineAmount) as Total, WeekNumber,
ROW_NUMBER() over (partition by WeekNumber order by sum(LineAmount) desc) as RowNum
from
Invoices
where
month(InvoiceDate)=month(getdate())
group by
GroupName,WeekNumber
I would like to convert this to LINQ, but no luck. I am using LINQ to Object. Any help would be appreciated.
TIA
EDIT : Here is some sample data, and the expected result.
public class Invoice
{
public string GroupName { get; set; }
public int LineAmount { get; set; }
public int WeekNum { get; set; }
}
List<Invoice> theData = new List<Invoice>();
theData.Add(new Invoice { GroupName = "A", LineAmount = 1, WeekNum = 1});
theData.Add(new Invoice { GroupName = "A", LineAmount = 2, WeekNum = 1 });
theData.Add(new Invoice { GroupName = "A", LineAmount = 3, WeekNum = 1 });
theData.Add(new Invoice { GroupName = "A", LineAmount = 2, WeekNum = 2 });
theData.Add(new Invoice { GroupName = "A", LineAmount = 3, WeekNum = 2 });
theData.Add(new Invoice { GroupName = "A", LineAmount = 4, WeekNum = 2 });
theData.Add(new Invoice { GroupName = "B", LineAmount = 4, WeekNum = 1 });
theData.Add(new Invoice { GroupName = "B", LineAmount = 3, WeekNum = 1 });
theData.Add(new Invoice { GroupName = "B", LineAmount = 7, WeekNum = 2 });
theData.Add(new Invoice { GroupName = "B", LineAmount = 6, WeekNum = 2 });
theData.Add(new Invoice { GroupName = "B", LineAmount = 5, WeekNum = 2 });
I have removed "where" from my first query as its not a problem at the moment.
theData
.GroupBy(g => new {g.GroupName, g.WeekNum}, (key, gg) => new {key.GroupName, key.WeekNum, Total = gg.Sum(g => g.LineAmount)})
.GroupBy(g => g.WeekNum, (weekNum, gg) => gg.OrderByDescending(g => g.Total).Select((g,i) => new {g.GroupName, g.Total, g.WeekNum, RowNum = i}))
.SelectMany(g => g)
You have not specified the language you need it in. Here is the code in C#
int index = 0;
var filteredInvoices = (from i in invoices
where i.InvoiceDate.Month == DateTime.Now().Month
group i by new { i.GroupName, i.WeekNumber }
into ig
select new {i.GroupName, Total = ig.Sum(i => i.LineAmount), i.WeekNumber, RowNum = ++index}).OrderByDescending(n => n.Total);
filteredInvoices should have the result that you want. Also I am assuming that the i.InvoiceDate is of type DateTime.
Serg Rogovtsev answer gives me expected result. And the below code is what I have done. Don't know which performs better, but results are same.
(theData.GroupBy(f => new { f.GroupName, f.WeekNum})
.Select(r => new {r.Key.WeekNum, r.Key.GroupName, Total = r.Sum(f => f.LineAmount)}))
.GroupBy(r => new {r.WeekNum}).SelectMany(
g =>
g.OrderByDescending(f => f.Total).Select(
(f, index) => new { f.GroupName, f.Total, f.WeekNum, Ix = index + 1 }))

MVC moq with repository that uses lambda expression

I have a controller method that acceses a repository method which has lambda expression as parameter:
// GET: /Product/
public ViewResult List(string category, int page = 1) {
ProductsListViewModel productsListViewModel = new ProductsListViewModel {
Products = _repository.GetByPage(q => q.Category == category || category == null, page, PageSize),
PagingInfo = new PagingInfo {
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = _repository.Get(q=>q.Category==category || category==null).Count()
},
CurrentCategory = category
};
return View(productsListViewModel);
}
In my unit test, when controller invokes the repository method, returned object("result" variable) is always null, do you have any idea about this situation?
public void Can_Paginate() {
//Arrange
//Create mock repository
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(q => q.GetByPage(c=>c.Category=="C1",1,3)).Returns(new List<Product>
{
new Product {Id = 1, Name = "P1", Category = "C1"},
new Product {Id = 2, Name = "P2", Category = "C1"},
new Product {Id = 3, Name = "P3", Category = "C1"}
});
mock.Setup(q => q.Get(c => c.Category == "C1")).Returns(new List<Product>
{
new Product {Id = 1, Name = "P1", Category = "C1"},
new Product {Id = 2, Name = "P2", Category = "C1"},
new Product {Id = 3, Name = "P3", Category = "C1"}
});
//Create a controller and make page size 3 items
ProductController controller = new ProductController(mock.Object);
controller.PageSize = 3;
//Action
ProductsListViewModel result = (ProductsListViewModel) controller.List("C1", 1).Model;
//Assert
Assert.IsTrue(result.Products.Count()==3);
}
Thanks
Just had quick look at the moq wiki and it had an example of matching Func<int>
as a parameter. So to match an argument of Func<string> you would write something like this:
mock.Setup(q => q.GetByPage(It.Is<string>(c=>c.Category=="C1"),1,3)).Returns...

LINQ Group By Subtotal & Total

I have a Batch with BatchItems entered by multiple users. I'm trying to not only get the subtotal per user for a single batch, but also grand total for that same batch regardless of the user grouping. Its this last part that I can't figure out. How might I get that total in order to return it as a list?
from b in context.BatchItem
where b.BatchId == batchId
group b by b.CreatedByUser into g
select new
{
BatchName = g.FirstOrDefault<BatchItem>().Batch.Name,
User = g.Key,
UserBatchCount = g.Count<BatchItem>(),
// something like this is what I can't figure out
TotalBatchCount = b.Count<BatchItem>()
}
Not sure, but try this:
from b in context.BatchItem
let cnt = context.BatchItem.Count()
b.BatchId == batchId
group b by b.CreatedByUser into g
select new
{
BatchName = g.FirstOrDefault<BatchItem>().Batch.Name,
User = g.Key,
UserBatchCount = g.Count<BatchItem>(),
// something like this is what I can't figure out
TotalBatchCount = cnt
}
var batch1 = new { Name = "Batch A", BatchId = 1, CreatedByUser = "David" };
var batch2 = new { Name = "Batch A", BatchId = 1, CreatedByUser = "Mike" };
var batch3 = new { Name = "Batch B", BatchId = 2, CreatedByUser = "Cathy" };
var batch4 = new { Name = "Batch B", BatchId = 2, CreatedByUser = "Cathy" };
var batch5 = new { Name = "Batch B", BatchId = 2, CreatedByUser = "David" };
var batch6 = new { Name = "Batch C", BatchId = 3, CreatedByUser = "Henry" };
var batchItem = new[] { batch1, batch2, batch3, batch4, batch5, batch6 }.ToList();
var result =
batchItem.Where(b => b.BatchId == batchId)
.GroupBy(b => b.BatchId, b => b)
.SelectMany(g =>
g.GroupBy(c => c.CreatedByUser, c => c)
.SelectMany(sg =>
sg.Select(c => new
{
BatchName = g.First().Name,
UserName = c.CreatedByUser,
UserBatchCount = sg.Count(),
TotalBatchCount = g.Count()
})
)
);
Audit Log: Removed previous two code blocks.

Resources