Finding duplicate items then selecting one with closest date to currentDate - c#

Note: Using Windows Mobile 6.5 Compact Framework.
I have a collection of the following object.
public class RFileModel
{
public List<string> RequiredFilesForR = new List<string>();
public string Date { get; set; }
public string RouteId { get; set; }
}
var ListOfRFileModels = new List<RFileModel>();
There is the chance that the same RouteId will be in multiple instances of RFileModel but with a different Date.
I'm trying to identify the duplicates and select only one, the one closest to the current date.
I have the following LINQ so far:
var query = ListOfRFileModels.GroupBy(route => route.RouteId)
.OrderBy(newGroup => newGroup.Key)
.Select(newGroup => newGroup).ToList();
But I don't think this is what I need, since it still returns all elements. I was expecting a list of non unique RouteId, that way I can iterate each non-unique id and compare dates to see which one to keep.
How can I accomplish this with LINQ or just plain ole foreach?

Your expression sorts groups, not group elements. Here is how to fix it:
DateTime currentDate = ...
var query = ListOfRFileModels
.GroupBy(route => route.RouteId)
.Select(g => g.OrderBy(fm => currentDate-fm.Date).First())
.ToList();
currentDate-fm.Date expression produces the difference between the current date and the date of the RFileModel object. The object with the smallest difference would end up in the first position of the ordered sequence. The call First() picks it up from the group to produce the final result.

Assuming you want ONLY the members with duplicates, take #dasblinkenlight's answer and add a Where clause: .Where(grp => grp.Count()>1):
DateTime currentDate = DateTime.Now;
var query = ListOfRFileModels
.GroupBy(route => route.RouteId)
.Where(grp => grp.Count()>1)
.Select(g => g.OrderBy(fm => currentDate-fm.Date).First())
.ToList();

Related

Get Elements from String List in order of Occurrence in provided string

Hi I have List of strings as below.
List<string> MyList = new List<string> { "[FirstName]", "[LastName]", "[VoicePhoneNumber]", "[SMSPhoneNumber]" };
I need to get all the elements from the List if exist in string in order. For example my string is
string MessageContent = Hello [LastName] [FirstName]There, this message is for [SMSPhoneNumber]
Right now I am doing
var Exists = MyList.Where(MessageContent.Contains);
This new list have all the items from MyList which occured in MessageContent string but not in order.
How i can get occurrence in order in string?
Desired List as per example is = { "[LastName]","[FirstName]","[SMSPhoneNumber]" }
I would suggest using IndexOf to determine position (and thereby order) as well as existence to avoid searching MessageContent twice at the expense of sorting the answer:
var ans = MyList.Select(w => new { w, pos = MessageContent.IndexOf(w) })
.Where(wp => wp.pos >= 0)
.OrderBy(wp => wp.pos)
.Select(wp => wp.w)
.ToList();
However, if a field may appear more than once, or if you think avoiding the repeated scanning of MessageContent is faster than multiple IndexOf (once per MyList member) (probably not) and avoiding the sort, then you can invert the search (using Span to avoid generating lots of Strings):
var ans2 = Enumerable.Range(0, MessageContent.Length-MyList.Select(w => w.Length).Min())
.Select(p => MyList.FirstOrDefault(w => MessageContent.AsSpan().Slice(p).StartsWith(w)))
.Where(w => w != null)
.ToList();
I did it Using
var Exists = MyList.Where(MessageContent.Contains).OrderBy(s => MessageContent.IndexOf(s));

LINQ how to return group by as List?

I have a datatable that is populated with 2 columns, DateTime and Data. I have been able to group the list using:
var dates = table.AsEnumerable().GroupBy(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString());
The conversion is to drop the "time" portion of the datetime as I only want 1 date per day so I can iterate over it.
I can see during debugging that the LINQ works out as I intended, but I have almost no experience in LINQ and I do not know how to return these results in a way that is iterable.
Below that LINQ statement I have:
foreach (string date in dates) {
string dummy2 = "";
}
with an error on foreach that says
Cannot convert type 'System.Linq.IGrouping<string, System.Data.DataRow>' to 'string'
The goal here is to return a list of just the unique dates which I can iterate over to perform additional processing/LINQ queries on the datatable
If you just want the dates, then you don't need to use GroupBy, you can use Distinct. Also, you can get them as a DateTime object by just grabbing the Date property, which has a zeroed-out time component:
IEnumerable<DateTime> distinctDates = table.AsEnumerable()
.Select(x => Convert.ToDateTime(x["DateTime"]).Date)
.Distinct();
If you want the groups but are just trying to select the date strings from them, then you want to use the Key property of the groups, which is the property that was used to group the items:
IEnumerable<IGrouping<string, System.Data.DataRow>> dates = table.AsEnumerable()
.GroupBy(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString());
List<string> distinctDates = dates.Select(date => date.Key).ToList();
As a side note, unless you know the data type being returned by something, you might avoid using var, since that was hiding the fact that dates wasn't a collection of strings.
If you want a list of unique dates, you can do something like this.
var dates = table.Select(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString()).Distinct();
that's happening since as the error says you're trying to loop in a dictionary:
Cannot convert type 'System.Linq.IGrouping<string, System.Data.DataRow>' to 'string'
what you can do is add to your GroupBy statement a select portion:
var dates = table.AsEnumerable().GroupBy(x => Convert.ToDateTime(x["DateTime"]).ToShortDateString()).Select(x=> x.date);

Grouping using multiple columns, then summing a specific column using method syntax

Currently I am looking for a way to get the sum of all piece counts from an enumerable while ignoring duplicates, all this while using method syntax. As things are right now my code will work, but I realize this is only temporary. More on this later.
Lets use the following class as an example
internal class Piece
{
public int Count { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
}
This class is then used to create a list with the following information
List<Piece> pieces = new List<Piece>
{
new Piece(41,DateTime.Parse("2019-07-12"),"BB"),
new Piece(41,DateTime.Parse("2019-07-12"),"BB"),
new Piece(21,DateTime.Parse("2019-07-12"),"JP"),
new Piece(23,DateTime.Parse("2019-07-14"),"AA")
};
To do the sum, I came up with the following
int total = pieces.Where(x => x.Count > 0)
.GroupBy(x => x.Count, x => x.Date,
(piece, date) => new { Count = piece,Date = date})
.Sum(x => x.Count);
This is where things get tricky. If another piece were to be added with as follows
new Piece(23,DateTime.Parse("2019-07-14"),"AB")
that piece would be ignored due to how I am grouping. This is far from ideal.
I have found the following way to group by several columns
GroupBy( x => new {x.Count,x.Date,x.Description})
But I have found no way make it so I can use Sum on this Grouping. This grouping using the AnonymousType does not let me declare local variables (piece,date) as I am able to do in the prior GroupBy (as far as I know).
For now the code that I have will do the trick but it is only a matter of time before that is no longer the case.
Some extra details.
I am manipulating a query result using Razor, and I have no control on the data that I get from the server. Manipulating the data using linq is basically the only way I have at the moment.
Any help is greatly appreciated
For the count you just need this query:
int total = pieces
.Where(x => x.Count > 0)
.GroupBy(x => new { x.Count, x.Date, x.Description })
.Sum(g => g.Key.Count);
So you can access all key properties of the grouping.
This returns 85 for your initial sample and 108 if you add the new piece.

How to write LINQ query to sql IN

I want to query my cosmosDB to get List of Documents of type ZonesDO && whose id are in UserPreferance.Zones My UserPreferance class is :
public class UserPreference
{
[JsonProperty("zones")]
public List<Zone> Zones { get; set; }
}
and Zone Class is:
public class Zone
{
[JsonProperty("id")]
public override Guid Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("category")]
public string Category { get; set; }
}
I am trying this query but not able to complete it.
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
*z.Id in user.UserPreference.Zones.ids*)// here I need the solution
.AsEnumerable().ToList();
Perform select on Zones.Id and then check with Contains to get desired result
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Select(x => x.Id).Contains(z.Id))
.AsEnumerable().ToList();
You can try to use Contain method.
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Select(x => x.Id).Contain(z.Id)).ToList();
Or you can use inline Where and Count
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(
z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Where(a=> a.Id == z.Id).Count() > 0
).ToList();
Another option may be to use the Any() operator:
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Where(x => x.Id == z.Id).Any())
.AsEnumerable().ToList();
Linq providers usually understands basic methods on arrays/collections. Therefore you can use Contains method.
var zones = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri)
.Where(z => z.Type == typeof(ZoneDO).ToString() &&
user.UserPreference.Zones.Contains(z.Id))
.AsEnumerable().ToList();
So apparently you have a user. This user has a UserPreference and this UserPreference has zero or more Zones. It seems that this user, and the Zones of this user's UserPreference is in local memory (not in a database. The Zones are IEnumerable and not IQueryable)
Although you didn't specify, it seems that DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri) returns an IQueryable<ZoneDO>
You want all ZoneDo that have an Id that is an Id of one of the Zones in the one and only UserPreference of your user.
In baby steps:
IEnumerable<Guid> zoneIds = user.Userpreference.Zones.Select(zone => zone.Id);
IQueryable<ZoneDO> allZoneDOs = DbUtil.Client.CreateDocumentQuery<ZoneDO>(CollectionUri);
IEnumerable<ZoneDO> requestedZoneDOs = allZoneDOs
.Where(zoneDo => zoneIds.Contains(zoneDo.Id);
Until now no query is performed, nor any enumeration. To perform the query and return the fetched data as a List<ZoneDO> use .ToList();
List<ZoneDO> documents = requestedZoneDOs.ToList();
TODO: if desired put all statements into one big LINQ statement. I doubt whether this will improve performance. It surely will deteriorate readability and thus maintainability.
Did you notice that I did not do your type checking part. This was not needed, because function CreateDocumentQuery<ZoneDO> already returns a sequence f ZoneDo.
If not, and there are other types in the returned sequence, use OfType<ZoneDo> instead of checking on string representation of the returned objects:
IQueryable<ZoneDO> allZoneDOs = DbUtil.Client
.CreateDocumentQuery<ZoneDO>(CollectionUri)
.OfType<ZoneDo>();
AsEnumerable is not needed. IQueryable.ToList() will perform the query and convert the data to a list.
AsEnumerable is only needed if you need the data in local memory to continue with LINQ statements that cannot be performed as IQueryable, like LINQ statements where you call local functions, or LINQ statements that cannot be translated into SQL.
AsEnumerable will fetch the requested data per 'page'. So if you only need the first (few) elements, you don't fetch the complete table, but only the first (few) pages.

Sort list by string field of class

I have a list of objects, each with time data, id numbers, and a string descriptor in the type field. I wish to pull all the values to the front of the list with a certain string type, while keeping the order of those list elements the same, and the order of the rest of the list elements the same, just attached to the back of those with my desired string.
I've tried, after looking for similar SE questions,
list.OrderBy(x => x.type.Equals("Auto"));
which has no effect, though all other examples I could find sorted by number rather than by a string.
List Objects class definition:
public class WorkLoad
{
public long id;
public DateTime timestamp;
...
public String type;
}
...create various workload objects...
schedule.Add(taskX)
schedule.OrderBy(x => x.type.Equals("Manual"));
//has no effect currently
If you already have a sorted list I think the fastest way to resort it by "having a type of auto or not" without losing the original order (and without having to resort all over again) could be this:
var result = list.Where(x => x.type.Equals("Auto"))
.Concat(list.Where(x => !x.type.Equals("Auto")))
.ToList();
Update:
You commented that "everyting else should be sorted by time", so you can simply do this:
var result = list.OrderByDescending(x => x.type.Equals("Auto"))
.ThenBy(x => x.Time).ToList();
You can use multiple orderings in a sequence:
list.OrderBy(x => x.type == "Auto" ? 0 : 1).ThenBy(x => x.type);

Categories

Resources