How to get distinct values from 2 different columns in the same list - list

So as you can see from the code bellow i have a list object named Matches, from which i would like to get a single list of the distinct teams, both from HomeTeam and AwayTeam. I'm trying to use LINQ and i can get a list of distinct teams if i only use HomeTeam parameter or AwayTeam parameter but not both together.
Thank you.
public class Match
{
public int ID { get; set; }
public string Country { get; set; }
public string Championship { get; set; }
public string Seasson { get; set; }
public DateTime MatchDate { get; set; }
public string HomeTeam { get; set; }
public int HomeScore { get; set; }
public int AwayScore { get; set; }
public string AwayTeam { get; set; }
}
private List<Match> Matches;
Matches = dataAccess.GetAllMatches();
I'm Trying to do something like that:
result = Matches.Select(HomeTeam, AwayTeam).Distinct().ToList();

At the risk that this smells like homework, a hint rather than code. Get your Home teams, Union your Away teams and apply a Distinct to the result.

So i finally come up with this solution.
Notice that now i need also to get not only the team but the country which the team belongs to.
public class Team
{
public string Name { get; set; }
public string Country { get; set; }
}
So Union really do the job here but since now i need to get it as an anonymous type... here is the code:
List<Team> teams = new List<Team>();
var result = Matches.Select(x => new { Name = x.HomeTeam, Country = x.Country }).Union(Matches.Select(x => new { Name = x.AwayTeam, Country = x.Country })).ToList();
foreach (var record in result)
{
teams.Add(new Team { Name = record.Name, Country = record.Country });
}
return teams;
I would prefer this way:
List<Team> teamsResult = Matches.Select(x => new Team { Name = x.HomeTeam, Country = x.Country }).Union(Matches.Select(x => new Team { Name = x.AwayTeam, Country = x.Country })).ToList();
But this way get duplicates so i will stick with the first example for now.
Do you think it is the more elegant way to go?
Thank you.

You can take advantage of GroupBy, like this:
IEnumerable<Team> teams = Matches.GroupBy(m => new { m.AwayTeam, m.HomeTeam, m.Country })
.Select(
g =>
new[]
{
new Team {Country = g.Key.Country, Name = g.Key.AwayTeam},
new Team {Country = g.Key.Country, Name = g.Key.HomeTeam}
})
.SelectMany(x => x)
.GroupBy(t => new { t.Name, t.Country })
.Select(g => new Team { Name = g.Key.Name, Country = g.Key.Country });

Related

how to convert List<KeyValuePair<string,object>> to class, model in .Net Core

I have got response List<List<KeyValuePair<string, object>>> but I want to convert response into List<ClassName>.
KeyValuePair key and ClassName property both are same name and same type
What is the most programmatically way to convert response?
I have got response
My class structure
public class TestModel
{
public string TaxablePersonCode { get; set; }
public string LegalNameAsPerPan { get; set; }
public string TradeName { get; set; }
public string ConstitutionName { get; set; }
public string ResidentialStatusName { get; set; }
public string PrimaryMobileNo { get; set; }
public string FlatOrOfficeNo { get; set; }
public string TownOrCityOrDist { get; set; }
public string Pincode { get; set; }
public string StateName { get; set; }
public string CountryName { get; set; }
public string ContactName { get; set; }
public string ContactDesignationName { get; set; }
public string ContactMobile { get; set; }
public string ContactEmail { get; set; }
}
var listKeyValue = response.Select(x => x.Value).ToList();
var data = JsonConvert.DeserializeObject<List<TestModel>>(listKeyValue);
The only part of this im unsure about is when indexing the final list for the correct property, but you can choose what works best for you. Using a .Where() every time ensures you'll get the right result but it will search the list every single time and be a lot slower. If your %100 certain the order of the list will never change you could gain some performance by directly indexing the list for the element you want using [] or .ElementAt(). Anyway, heres what your looking for.
List<TestModel> myList = response.Select(x => new TestModel
{
// Using Where
TaxablePersonCode = x.Where(t => t.Key == "TaxablePersonCode").First().Value,
// Using direct index
LegalNameAsPerPan = x[1].Value,
// Using ElementAt
TradeName = x.ElementAt(2).Value,
...
});
Hope that helps!
Did you get some result with JsonConvert class? Did it worked for you? If not, you can try out something like this (if JSON and field are properly named):
var listKeyValue = response.Select(x => x.Value).ToList();
var result = new List<TestModel>();
foreach (var keyValueList in listKeyValue)
{
// convert the response list of KeyValuePair to dictionary
var dictionary = keyValueList.ToDictionary(kv => kv.Key, kv => kv.Value);
var tempModel = new TestModel();
// get actual value by name of the rpoperty
tempModel.TaxablePersonCode = dictionary[nameof(tempModel.TaxablePersonCode)].ToString();
// etc.
result.Add(tempModel);
}
Maybe this approach could be improved with reflection, but this will degrade the performance.
// get all properties to populate
var properties = typeof(TestModel).GetProperties(BindingFlags.Public | BindingFlags.Instance);
I see you're using JSON, so you should probably just deserialize the object properly, which I would expect to look something like this:
List<TestModel> models = JsonConvert.DeserializeObject<List<TestModel>>(response);
Otherwise, you could use reflection to bind the known KeyValuePair keys to the properties of the object; that being said, you will need to ensure that the return values are compatible with the values from the returned data, else this will fail.
outerList.ForEach(innerList => {
TestModel result = new TestModel();
innerList.ForEach(listItem => {
result
.GetType()
.GetProperty(listItem.Key)
?.SetValue(result, listItem.Value);
});
});

RavenDB: Why do I get null-values for fields in this multi-map/reduce index?

Inspired by Ayende's article https://ayende.com/blog/89089/ravendb-multi-maps-reduce-indexes, I have the following index, that works as such:
public class Posts_WithViewCountByUser : AbstractMultiMapIndexCreationTask<Posts_WithViewCountByUser.Result>
{
public Posts_WithViewCountByUser()
{
AddMap<Post>(posts => from p in posts
select new
{
ViewedByUserId = (string) null,
ViewCount = 0,
Id = p.Id,
PostTitle = p.PostTitle,
});
AddMap<PostView>(postViews => from postView in postViews
select new
{
ViewedByUserId = postView.ViewedByUserId,
ViewCount = 1,
Id = (string) postView.PostId,
PostTitle = (string) null,
});
Reduce = results => from result in results
group result by new
{
result.Id,
result.ViewedByUserId
}
into g
select new Result
{
ViewCount = g.Sum(x => x.ViewCount),
Id = g.Key.Id,
ViewedByUserId = g.Key.ViewedByUserId,
PostTitle = g.Select(x => x.PostTitle).Where(x => x != null).FirstOrDefault(),
};
Store(x => x.PostTitle, FieldStorage.Yes);
}
public class Result
{
public string Id { get; set; }
public string ViewedByUserId { get; set; }
public int ViewCount { get; set; }
public string PostTitle { get; set; }
}
}
I want to query this index like this:
Return all posts including - for a given user - the integer of how many times, the user has viewed the post. The "views" are stored in a separate document type, PostView. Note, that my real document types have been renamed here to match the example from the article (I certainly would not implement "most-viewed" this way).
The result from the query I get is correct - i.e. I always get all the Post documents with the correct view-count for the user. But my problem is, the PostTitle field always is null in the result set (all Post documents have a non-null value in the dataset).
I'm grouping by the combination of userId and (post)Id as my "uniqueness". The way I understand it (and please correct me if I'm wrong), is, that at this point in the reduce, I have a bunch of pseudo-documents with identical userId /postId combination, some of which come from the Post map, others from the PostView map. Now I simply find any single pseudo-document of the ones, that actually have a value for PostTitle - i.e. one that originates from the Post map. These should all obviously have the same value, as it's the same post, just "outer-joined". The .Select(....).Where(....).FirstOrDefault() chain is taken from the very example I used as a base. I then set this ViewCount value for my final document, which I project into the Result.
My question is: how do I get the non-null value for the PostTitle field in the results?
The problem is that you have:
ViewedByUserId = (string) null,
And:
group result by new
{
result.Id,
result.ViewedByUserId
}
into g
In other words, you are actually grouping by null, which I'm assuming that isn't your intent.
It would be much simpler to have a map/reduce index just on PostView and get the PostTitle from an include or via a transformer.
You understanding of what is going on is correct, in the sense that you are creating index results with userId / postId on them.
Buit what you are actually doing is creating results from PostView with userId /postId and from Post with null /postId.
And that is why you don't have the matches that you want.
The grouping in the index is incorrect. With the following sample data:
new Post { Id = "Post-1", PostTitle = "Post Title", AuthorId = "Author-1" }
new PostView { ViewedByUserId = "User-1", PostId = "Post-1" }
new PostView { ViewedByUserId = "User-1", PostId = "Post-1" }
new PostView { ViewedByUserId = "User-2", PostId = "Post-1" }
The index results are like this:
ViewCount | Id | ViewedByUserId | PostTitle
--------- | ------ | -------------- | ----------
0 | Post-1 | null | Post Title
2 | Post-1 | User-1 | null
1 | Post-1 | User-2 | null
The map operation in the index simply creates a common document for all source documents. Thus, the Post-1 document produces one row, the two documents for Post-1 and User-1 produce two rows (which are later reduced to the single row with ViewCount == 2) and the document for Post-1 and User-2 produces the last row.
The reduce operation the groups all the mapped rows and produces the resulting documents in the index. In this case, the Post-sourced document is stored separately from the PostView-sourced documents because the null value in the ViewedByUserId is not grouped with any document from the PostView collection.
If you can change your way of storing data, you can solve this issue by storing the number of views directly in the PostView. It would greatly reduce duplicate data in your database while having almost the same cost when updating the view count.
Complete test (needs xunit and RavenDB.Tests.Helpers nugets):
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using System.Linq;
using Xunit;
namespace SO41559770Answer
{
public class SO41559770 : RavenTestBase
{
[Fact]
public void SO41559770Test()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
new PostViewsIndex().Execute(store);
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Post { Id = "Post-1", PostTitle = "Post Title", AuthorId = "Author-1" });
session.Store(new PostView { Id = "Views-1-1", ViewedByUserId = "User-1", PostId = "Post-1", ViewCount = 2 });
session.Store(new PostView { Id = "Views-1-2", ViewedByUserId = "User-2", PostId = "Post-1", ViewCount = 1 });
session.SaveChanges();
}
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
using (IDocumentSession session = store.OpenSession())
{
var resultsForId1 = session
.Query<PostViewsIndex.Result, PostViewsIndex>()
.ProjectFromIndexFieldsInto<PostViewsIndex.Result>()
.Where(x => x.PostId == "Post-1" && x.UserId == "User-1");
Assert.Equal(2, resultsForId1.First().ViewCount);
Assert.Equal("Post Title", resultsForId1.First().PostTitle);
var resultsForId2 = session
.Query<PostViewsIndex.Result, PostViewsIndex>()
.ProjectFromIndexFieldsInto<PostViewsIndex.Result>()
.Where(x => x.PostId == "Post-1" && x.UserId == "User-2");
Assert.Equal(1, resultsForId2.First().ViewCount);
Assert.Equal("Post Title", resultsForId2.First().PostTitle);
}
}
}
}
public class PostViewsIndex : AbstractIndexCreationTask<PostView, PostViewsIndex.Result>
{
public PostViewsIndex()
{
Map = postViews => from postView in postViews
let post = LoadDocument<Post>(postView.PostId)
select new
{
Id = postView.Id,
PostId = post.Id,
PostTitle = post.PostTitle,
UserId = postView.ViewedByUserId,
ViewCount = postView.ViewCount,
};
StoreAllFields(FieldStorage.Yes);
}
public class Result
{
public string Id { get; set; }
public string PostId { get; set; }
public string PostTitle { get; set; }
public string UserId { get; set; }
public int ViewCount { get; set; }
}
}
public class Post
{
public string Id { get; set; }
public string PostTitle { get; set; }
public string AuthorId { get; set; }
}
public class PostView
{
public string Id { get; set; }
public string ViewedByUserId { get; set; }
public string PostId { get; set; }
public int ViewCount { get; set; }
}
}

How to convert a dynamic list into list<Class>?

I'm trying to convert a dynamic list into a list of class-model(Products). This is how my method looks like:
public List<Products> ConvertToProducts(List<dynamic> data)
{
var sendModel = new List<Products>();
//Mapping List<dynamic> to List<Products>
sendModel = data.Select(x =>
new Products
{
Name = data.GetType().GetProperty("Name").ToString(),
Price = data.GetType().GetProperty("Price").GetValue(data, null).ToString()
}).ToList();
}
I have tried these both ways to get the property values, but it gives me null errors saying these properties doesn't exist or they are null.
Name = data.GetType().GetProperty("Name").ToString(),
Price = data.GetType().GetProperty("Price").GetValue(data,
null).ToString()
This is how my Model-class looks like:
public class Products
{
public string ID { get; set; }
public string Name { get; set; }
public string Price { get; set; }
}
Can someone please let me know what I'm missing? thanks in advance.
You're currently trying to get properties from data, which is your list - and you're ignoring x, which is the item in the list. I suspect you want:
var sendModel = data
.Select(x => new Products { Name = x.Name, Price = x.Price })
.ToList();
You may want to call ToString() on the results of the properties, but it's not clear what's in the original data.

Ravendb: map-reduce on two relating documents

In my ravendb I have 2 documents: Country and City
City document looks like this
Id
Name
CountryId
Country document looks like this
Id
Name
At the moment I have an index where I retrieve all cities as a list and this works.
But I would rather want to retrieve all cities grouped by countries
This is what I have
public class City_ByCountry
{
public string CityId { get; set; }
public string CityName { get; set; }
public string CountryName { get; set; }
}
Map = (city => from cit in city
let cou = LoadDocument<Country>(cit.CountryId)
select new City_ByCountry
{
CityId = cit.Id,
CityName = cit.Name,
CountryName = cou.Name
});
This works but gives me a list of all cities (id, name, countryName)
But I want a list like this
CountryName [
List with cities]
CountryName [
List with cities]
etc
Can I do this with a reduce on the result? Or what is the correct way to do this?
I think with a reduce this is possible. See Ayende's post Awesome indexing with RavenDB on how to perform advanced indexing.
I tried to modify Ayende's example to match your needs (just on Notepad, so I don't know if it even compiles):
public class City_ByCountry : AbstractIndexCreationTask<City, City_ByCountry.ReduceResult>
{
public class ReduceResult
{
public string CountryId { get; set; }
public string CountryName { get; set; }
public City[] Cities { get; set; }
}
public City_ByCountry()
{
Map = cities =>
from city in cities
select new
{
CountryId= city.CountryId,
CountryName = LoadDocument<Country>(city.CountryId),
Cities = new [] { city }
};
Reduce = cities =>
from city in cities
group city by city.CountryId
into g
select new
{
CountryId = g.Key,
CountryName = g.First().CountryName,
Cities = g.SelectMany(x => x.Cities)
};
}
}

How to update the content inside a list in c#

I have a class as follow:
public class student
{
public string studentID { get; set; }
public string studentName { get; set; }
public string studentGender { get; set; }
public string studentCGP { get; set; }
}
List<student> students = new List<student>();
..... I had added some data into the students List mention above, except for the data to the studentCGP.
After my other calculation for the studentCGP data, how do I put the data back to respectively? I'll have the studentID and studentCGP in hand.
using Linq...
var student = students.Find( x => x.studentID == idValue );
student.studentCGP = cgpValue;
Seems pretty trivial... am I missing something in the question?
students.Single(o => o.studentID == idValue).studentCGP = cgpValue;
There is a number of functions that you can use, just choose the one that suits you the best. For example you can use also First, Last etc.