How to sort stock months using linq C# - c#

I have some stock data that I'm trying to sort using linq, however I'm very unfamiliar with linq and cannot understand the documentation.
Right now I have a list of bars (a class I've created that holds the stock data) and it's all of the stock data for each day since 1990. Now I'm trying to group this stock data by year and month so I can turn daily stock data into monthly stock data (the resolution of the stock).
public class Stock
{
private string stockSymboll;
private string period;
private List<bar> aBar = new List<bar>();
private DateTime startingDate;
private DateTime endingDate;
enum period { DAILY, WEEKLY, MONTHLY };
private period PeriodType;
}
public class bar
{
private double open;
private double high;
private double low;
private double close;
private double volume;
private DateTime stockDate;
}
within the stock class I have a function that I'm trying to use to convert from a list of daily data to a list of monthly data, in order from most recent to least recent.
Here is what I've attempted:
stock convertPeriod(Period pt)
{
stock newStock = new Stock(stockName, startingDate, endingDate, period);
if (pt == Periode.MONTHLY)
{
List<bar> monthlyGroup = new List<bar>();
var group1 = (from b in bar group c by b.getDate().Month);
var group2 = from g in group1 group g by g.getDate().Year)
return...;
}
}
However I've discovered that you cannot sort a var. So I was thinking the best way would be to attempt a nested query in linq, however I can hardly seem to even get basic queries to work. Any help would be greatly appreciated.

I'm not 100% clear on what contract you're supposed to be setting up, but as I understand it, you want to:
Receive an IEnumerable<Bar>, and
Return an IEnumerable<Bar> such that StockDate is the first day of the year and month for the summarized stocks, all sorted descending by date.
As I understand it, Stock is more or less irrelevant to your true question here. If I'm incorrect in that, let me know and I can help you take this that one step further.
You have a good start on this in your LINQ. I'm a little confused about your use of private fields in the types, but I'm assuming those to be typos, and that your actual code uses public, probably properties.
I'll do this as a couple separate methods just to make what I'm doing more clear, but you may want to bring them together in the interest of performance. Particularly since I order the collection twice, effectively.
This method groups and sorts the data, based on the first day of their year-month set. Note that you can actually perform a grouping on an anonymous type. Sorting doesn't work on the anonymous object itself, as you noted, but it does work on its properties.
public IEnumerable<IEnumerable<Bar>> GroupIntoMonths(IEnumerable<Bar> bars)
{
return bars.GroupBy(c => new { c.StockDate.Year, c.StockDate.Month })
.OrderByDescending(c => c.Key.Year)
.ThenByDescending(c => c.Key.Month);
}
The choice is yours whether you prefer to group on an instantiated DateTime object with a date set of one, or what I've done here. I don't touch the Key property again, so I was fine effectively losing track of it after I left the method. Other implementations might drive you to make a different decision on that.
Once you've got that, it's a matter of converting an IEnumerable<Bar> into a single Bar that summarizes the whole period.
public IEnumerable<Bar> GroupIntoBars(IEnumerable<IGrouping<DateTime, Bar>> groups)
{
return groups.Select(GetBar);
}
public Bar GetBar(IEnumerable<Bar> bars)
{
Bar ret = new Bar();
Bar last = null;
int index = -1;
foreach(var v in bars.OrderBy(c => c.StartingDate))
{
index++;
if(index == 0)
{
ret.Open = v.Open;
ret.StockDate = v.StockDate;
ret.High = v.High;
ret.Low = v.Low;
}
else
{
ret.High = Math.Max(ret.High, v.High);
ret.Low= Math.Max(ret.Low, v.Low);
}
last = v;
}
if(last == null) throw new ArgumentException("Collection cannot be empty!");
ret.Close = last.Close;
return ret;
}
I think that method is pretty straight-forward, but let me know if I can clear anything up.

You can group by multiple properties at once by specifying them in an anonymous object that will be the group key:
var monthlyGroup = aBar.OrderBy(bar => bar.stockDate)
.GroupBy(bar => new { Year = bar.stockDate.Year, Month = bar.stockDate.Month })
//create bars from groups
.Select(g => new bar()
{
open = g.First().open,
high = g.Max(b => b.high),
low = g.Min(b => b.low),
close = g.Last().close,
volume = g.Average(b => b.volume),
stockDate = new DateTime(g.Key.Year, g.Key.Month, 1)
})
.ToList();
Sorry but I prefer the functions syntax of linq.
I noticed that fields in bar class are private so they will be inaccessible. However, I assume that you have properties for each field.
In that case you will have to replace field names with property names in the code above.

Related

Remove DataGridView cells containing duplicate values

My DataGridView looks like this:
How to clear the text of duplicate cells in the DataGridView Rows?
I tried below but it's clearing all values of Cells[0].
string duplicateValue = dataGridView1.Rows[0].Cells[0].Value.ToString();
for (int i = 1; i < dataGridView1.Rows.Count; i++)
{
if (dataGridView1.Rows[i].Cells[0].Value.ToString() == duplicateValue)
{
dataGridView1.Rows[i].Cells[0].Value = string.Empty;
}
else
{
dataGridView1.Rows[i].Cells[0].Value = duplicateValue;
}
}
One way to achieve this would be to use a HashSet as follows:
var valuesFound = new HashSet<string>();
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
string cellText = dataGridView1.Rows[i].Cells[0].Value.ToString();
// Attempt to add the value to the HashSet. If it fails, then it's a duplicate.
if (!valuesFound.Add(cellText))
{
dataGridView1.Rows[i].Cells[0].Value = string.Empty;
}
}
Or if you prefer LINQ, you could do something like this:
var duplicateCells = dataGridView1.Rows.OfType<DataGridViewRow>()
.Select(r => r.Cells[0])
.GroupBy(c => c.Value.ToString())
.SelectMany(g => g.Skip(1))
.ToList();
duplicateCells.ForEach(c => c.Value = string.Empty);
Short answer:
How to clear the text of duplicate cells in the DataGridView Rows?
Apparent you consider some products to be the same. Alas you forgot to say when two products are equal. Is Product [Apple, UK, 1] equal to [Apple, UK, 2]? And if so, which one do you want to show?
Or do you want to show the sum: [Apple, UK, 3]?
And what about: [Apple, Ireland, 1]? Is that the same as [Apple, UK, 1]?
Clearly you need a method that says: this product equals that product, but that one is a different product.
For this we'll have to create an equality comparer.
class Product
{
public Name {get; set;}
public string Country {get; set;}
public int Quantity {get; set;}
...
}
IEqualityComparer<Product> productComparer = ... // TODO: implement
Once you've got this, you can get rid of duplicates:
IEnumerable<Product> productsWithDuplicates = ...
IEnumerable<Product> noDuplicates = productsWithDuplicates.Distinct(productComparer);
Or if you want to combine [Apple, UK, 1] and [Apple, UK, 2] to show the sum [Apple, UK, 3], use groupBy to make groups:
IEnumerable<Product> productsToDisplay = productsWithDuplicates
.GroupBy(product => new {product.Name, product.Country}
(key, productsWithThisKey) => new Product
{
Name = key.Name,
Country = key.Country,
Quantity = productWithThisKey.Select(product => product.Quantity).Sum(),
},
productComparer);
So the solution depends on when two products are equal, and what you want to show if you've found equal produts.
Equality Comparer for Products
class ProductComparer : EqualityComparer<Product>()
{
public static IEqualityComparer<Product> NameCountry {get;} = new ProductComparer();
public override bool Equals(Product x, Product y)
{
if (x == null) return y == null; // true if both null, false if x null, but y not
if (y == null) return false; // because x not null
if (object.ReferenceEquals(x, y) return true;
// define equality, for instance:
return x.Name == y.Name && x.Country == y.Country;
}
If you want case insensitive, add a property:
private static IEqualityComparer<string> NameComparer {get; } = StringComparer.InvariantIgnoreCase;
private static IEqualityComparer<string> CountryComparer {get;} = ...
And in Equals:
return NameComparer.Equals(x.Name, y.Name)
&& CountryComparer.Equals(x.Country, y.Country);
Now if you later decide that you want to be case sensitive when comparing Countries, or maybe want to use the current culture, you'll only have to change this on one location.
The use of the comparers, makes changing easier, but also your code: you don't have to check for null names and countries, that is handled by the comparers.
GetHashCode: only requirement: if x equals y, return same GetHashCode. if not equal, you are free to return whatever you want, but it is more efficient if you return different hashcode.
public override int GetHashCode(Product product)
{
if (product == null) return 47855249; // just a number
return NameComparer.GetHashCode(product.Name)
^ CountryComparer.GetHashCode(product.Country);
}
There's room for improvement
It is usually not a good idea to intertwine your model with the view of your model. If in future you want to change how your data is displayed, for instance you want to show it in a ListBox, or in a Graph, you'll have to change a lot.
Besides, if you have separated your model from the way that it is displayed, it will be a lot easier to unit test your model. To test your view, you won't need your original data, you can test with edge conditions, like an empty datagridview,
First of all, you need a method to fetch the products from your model:
private IEnumerable<Productm> FetchProducts(...) {...}
So now you have a unit testable method that fetches the products. The nice thing is that you even hid where you get this information from: it can be from a database, or an XML file, or even from the internet: your Form doesn't know, and doesn't have to know. Totally designed for change.
Using visual studio designer you have defined columns. Every column shows exactly the value of one property. Which property the column shows is defined in property DataGridViewColumn.DataPropertyName
columnName.DataPropertyName = nameof(Product.Name);
columnCountry.DatapropertyName = nameof(Product.Country);
...
To show the fetched Products, use property DataGridView.DataSource. If you assign a List<Product>, then changes that the operator makes (add / remove rows, change cells) are not reflected in the List. If you want to automatically update the changes that the operator made, use a BindingList
public BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.datagridView1.DataSource;
set => this.datagridView1.DataSource = value;
}
private IEqualityComparer<Product> ProductComparer {get;} = ProductComparer.NameCountry;
public void InitProductDisplay()
{
IEnumerable<Product> productsToDisplay = this.FetchProducts()
.Distinct(productComparer);
// or if you want to show the totals: use the GroupBy described above
this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}
Nice! If you don't want to compare on NameCountry, but differently, or if you want to Compare using current culture, if you want to show the totals of the quantity, or even if you want to show it in a graph instead of a table: there is only one place you need to change.
Now every change that the operator makes: add / remove / change is reflected in your BindingList, even if the rows are sorted.
For instance, if the operator indicates that he finished editing by clicking a button:
private void OnButtonOk_Clicked(object sender, ...)
{
var displayedProducts = this.DisplayedProducts;
// find out which products are added / removed / changed
this.ProcessEditedProducts(displayedProducts);
}
If you need to do something with selected rows, consider to add the follwing:
private Product CurrentProduct => (Product)(this.datagridView1.CurrentRow?.DataBoundItem);
private IEnumerable<Product> SelectedProducts = this.datagridView1.SelectedRows
.Cast<DataGridViewrow>()
.Select(row => row.DataBoundItem)
.Cast<Product>();

Sorting List Array based on an index of array

I want to sort a List Array on the basis of an array item.
I have a List Array of Strings as below:
List<String>[] MyProjects = new List<String>[20];
Through a loop, I have added five strings
(Id, Name, StartDate, EndDate, Status)
to each of the 20 projects from another detailed List source.
for(int i = 0; i<20; i++){
MyProjects[i].Add(DetailedProjectList.Id.ToString());
MyProjects[i].Add(DetailedProjectList.Name);
MyProjects[i].Add(DetailedProjectList.StartDate);
MyProjects[i].Add(DetailedProjectList.EndDate);
MyProjects[i].Add(DetailedProjectList.Status)}
The Status values are
"Slow", "Normal", "Fast", "Suspended" and "" for unknown status.
Based on Status, I want to sort MyProject List Array.
What I have done is that I have created another List as below
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
I tried as below to sort, however unsuccessful.
MyProjects = MyProjects.OrderBy(x => sortProjectsBy.IndexOf(4));
Can anyone hint in the right direction. Thanks.
I suggest you to create class Project and then add all the fields inside it you need. It's much nicer and scalable in the future. Then create a List or an Array of projects and use the OrderBy() function to sort based on the field you want.
List<Project> projects = new List<>();
// Fill the list...
projects.OrderBy(project => project.Status);
The field Status has to be a primitive type or needs to implement the interface IComparable in order for the sorting to work. I suggest you add an enum for Status with int values.
First consider maybe to use Enum for status and put it in a different file lite (utils or something) - better to work like that.
enum Status {"Slow"=1, "Normal", "Fast", "", "Suspend"}
Now about the filtering you want to achieve do it like this (you need to tell which attribute of x you are referring to. In this case is status)
MyProjects = MyProjects.OrderBy(x => x.status == enum.Suspend);
Read about enums :
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
Read about lambda expressions :
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions
First of all, storing project details as List is not adivisable. You need to create a Custom Class to represent them.
For example,
public class DetailedProjectList
{
public string Name {get;set;}
public eStatus Status {get;set;}
// rest of properties
}
Then You can use
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
For example
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status="Fast"},
new DetailedProjectList{Name="abc2", Status="Normal"},
new DetailedProjectList{Name="abc3", Status="Slow"},
};
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
Output
abc3 Slow
abc2 Normal
abc1 Fast
A better approach thought would be to use Enum to represent Status.
public enum eStatus
{
Slow,
Normal,
Fast,
Unknown,
Suspended
}
Then your code can be simplified as
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status=eStatus.Fast},
new DetailedProjectList{Name="abc2", Status=eStatus.Normal},
new DetailedProjectList{Name="abc3", Status=eStatus.Slow},
};
var result = MyProjects.OrderBy(x=> x.Status);
Ok so you have a collection of 20 items. Based on them you need to create a list of strings(20 DetailedProjectList items).
What you can do to solve your problem is to SORT YOUR COLLECTION before you create your list of strings. In this way your list of strings will be sorted.
But your code is not optimal at all. So you should concider optimization on many levels.
Lets say you have ProjectDetail class as follow:
private class ProjectDetail
{
public int Id {get;set;}
public string Name {get;set;}
DateTime StartDate {get;set;} = DateTime.Now;
DateTime EndDate {get;set;} = DateTime.Now;
public string Status {get;set;}
public string toString => $"{Id} - {Name} - {StartDate} - {EndDate} - {Status}";
}
Notice that I have added a toString attribute to make things easier, and I also have added default values.
Then your program could be like:
static void Main(string[] args)
{
var projectDetails = MockProjectItems();
Console.WriteLine("Before sortig:");
foreach (var item in projectDetails)
{
Console.WriteLine(item.toString);
}
var myProjects = projectDetails.OrderBy(p => p.Status).Select(p => p.toString);
Console.WriteLine("\n\nAfter sorting:");
foreach (var item in myProjects)
{
Console.WriteLine(item);
}
}
where the helper method is
private static List<ProjectDetail> MockProjectItems()
{
var items = new List<ProjectDetail>(20);
for(int i = 0; i < 20 ; i += 4){
items.Add(new ProjectDetail{Id = i, Name = "RandomName "+i, Status = "Slow"});
items.Add(new ProjectDetail{Id = i+1, Name = "RandomName "+(i+1), Status = "Normal"});
items.Add(new ProjectDetail{Id = i+2, Name = "RandomName "+(i+2), Status = "Fast"});
items.Add(new ProjectDetail{Id = i+3, Name = "RandomName "+(i+3), Status = "Suspended"});
}
return items;
}
Then your program should print the following:

Update collection from DbSet object via Linq

i know it is not complicated but i struggle with it.
I have IList<Material> collection
public class Material
{
public string Number { get; set; }
public decimal? Value { get; set; }
}
materials = new List<Material>();
materials.Add(new Material { Number = 111 });
materials.Add(new Material { Number = 222 });
And i have DbSet<Material> collection
with columns Number and ValueColumn
I need to update IList<Material> Value property based on DbSet<Material> collection but with following conditions
Only one query request into database
The returned data from database has to be limited by Number identifier (do not load whole database table into memory)
I tried following (based on my previous question)
Working solution 1, but download whole table into memory (monitored in sql server profiler).
var result = (
from db_m in db.Material
join m in model.Materials
on db_m.Number.ToString() equals m.Number
select new
{
db_m.Number,
db_m.Value
}
).ToList();
model.Materials.ToList().ForEach(m => m.Value= result.SingleOrDefault(db_m => db_m.Number.ToString() == m.Number).Value);
Working solution 2, but it execute query for each item in the collection.
model.Materials.ToList().ForEach(m => m.Value= db.Material.FirstOrDefault(db_m => db_m.Number.ToString() == m.Number).Value);
Incompletely solution, where i tried to use contains method
// I am trying to get new filtered collection from database, which i will iterate after.
var result = db.Material
.Where(x=>
// here is the reasonable error: cannot convert int into Material class, but i do not know how to solve this.
model.Materials.Contains(x.Number)
)
.Select(material => new Material { Number = material.Number.ToString(), Value = material.Value});
Any idea ? For me it is much easier to execute stored procedure with comma separated id values as a parameter and get the data directly, but i want to master linq too.
I'd do something like this without trying to get too cute :
var numbersToFilterby = model.Materials.Select(m => m.Number).ToArray();
...
var result = from db_m in db.Material where numbersToFilterBy.Contains(db_m.Number) select new { ... }

C# Remove duplicate times within range and mark them

So ive come across a slight issue with Lists in C#
I have a List that has some very similar dates. Now what i need to do is remove duplicates but where a duplicate is if its within one minute of another time, so not exactly the same.
As well as this, if there were duplicates of a specific time the object should have a flag saying so in the final list.
Now i can see how this could be done with lots of for loop, but i was hoping theres a nice way with LINQ to make this simple. So far my attempts havent been going very well though.
As an example a list of the following dates:
21/12/12 12:13:00
21/12/12 12:13:20
13/12/12 10:13:00
21/10/15 07:13:00
should turn into the list of DateDuplicate objects:
{date = 21/12/12 12:13:00, isDuplcicate=true}
{date = 13/12/12 10:13:00, isDuplcicate=false}
{date = 21/10/15 07:13:00, isDuplcicate=false}
public class DateDuplicate{
public DateTime date;
public bool isDuplicate = false;
}
List<DateDuplicate> RemoveAndMark(List<DateTime> dates){
///something nice here hopefully
}
thanks for any help!
Try this
List<DateDuplicate> RemoveAndMark(List<DateTime> dates)
{
var dateValues = dates.GroupBy(y => new { AddHours = y.Date.Date.AddHours(y.Hour), y.Minute}).Select(x =>
new DateDuplicate {isDuplicate = x.Count() > 1, date = x.Key.AddHours.AddMinutes(x.Key.Minute)}
);
return dateValues;
}

LINQ Aggregate with Sub-Aggregates

Using a semi-complex structure, I am trying to 'combine' several objects into one using the Linq Aggregate method (though if there is a better way, I am open to ideas).
Here is my basic class design.
class Aspect {
string Name { get; set; }
}
class Arrangement {
Aspect Aspect { get; set; }
IList<Aperture> Apertures { get; set; }
IList<Step> Steps { get; set; }
}
class Step {
int Rank { get; set; }
int Required { get; set; }
}
class Aperture {
string Name { get; set; }
int Size { get; set; }
}
Basically, I am trying to aggregate the entire hierarchy of an IEnumerable<Arrangement> and keep everything on the base level, but where things can appropriately overwrite, I want to overwrite them.
Update
I want to get all Arrangements that share the same Aspect.Name, and get a complete list of Steps, overwriting lower level Steps where higher level Arrangements have the same Rank with a different Required value.
So take for instance...
var list = new List<Arrangement>{
new Arrangement{
Aspect = Aspects.Named("One"),
Steps = new List<Step>{
new Step {
Rank = 1,
Required = 2
},
new Step {
Rank = 2,
Required = 4
}
}
},
new Arrangement{
Aspect = Aspects.Named("One"),
Steps = new List<Step>{
new Step {
Rank = 1,
Required = 3
}
}
}
}
When aggregated properly, it should come out to look like ...
Arrangement
- Aspect
- Name : One
- Steps
- Rank : 1
- Required : 3
- Rank : 2
- Required : 4
I have attempted to use Distinct and Aggregate and it just isn't getting me anywhere. I keep ending up not getting one list or the other. Can anyone help with this?
Update
Here is an example of my current aggregation.
public static Layouts.Template Aggregate(this IList<Layouts.Template> source) {
return source.Aggregate(
source.First(),
(current, next) => new Layouts.Template {
Apertures = (current.Apertures.Concat(next.Apertures).Distinct().ToList()),
Arrangements = (current.Arrangements.Concat(next.Arrangements).Distinct().ToList()),
Pages = (current.Pages.Concat(next.Pages).Distinct().ToList())
});
}
My problem is that I'm having a lot of trouble wrapping my head around how to do this at all, much less in one expression. I'm not unwilling to use multiple methods, but if I could encapsulate it all, it would be really useful. I am fascinated by LINQ in general and I really want to get my head around this.
Update 2
The other collection, Apertures, will work in a similar manner, but it is unrelated to the Steps. Simply two different arrays I must do the same thing to, but they have nothing in common with one another. Learning how to do this with one will give me the knowledge to do it with the other.
If there's no correlation between steps and apertures you can do this:
var result = new Arrangement{
Steps = list.SelectMany(arrangement => arrangment.Steps)
.GroupBy(step => step.Rank)
.Select(l => l.Last())
.OrderBy(step => step.Rank)
.ToList()),
}
If there is you'll need to combine the two somehow. If steps index into apertures then you can use something similar.
After your updates:
var query = arrangements
.GroupBy(a => a.Aspect.Name)
.Select(g =>
new Arrangement
{
Steps = ga.SelectMany(a => a.Steps)
.GroupBy(s => s.Rank)
.Select(gs => gs.Last()),
Aspect = ga.First().Aspect
});
This will create output as in your example.
Now, how to merge it with your current aggregation method? As to my understanging, you want to create one big layout from all current layout contents (including arrangements, pages, etc)?
You don't need aggregate at all, just split it into 3 LINQ queries:
// get all arrangements object from all layouts [flattening with SelectMany]
var arrangements = source.SelectMany(s => s.Arrangements);
// and filter them
var filteredArrangements = // enter larger query from above here
// repeat the same for Apertures and Pages
...
// and return single object
return new Layouts.Template
{
Apertures = filteredApertures,
Arrangements = filteredArrangements,
Pages = filteredPages
};
I assume this isn't the complete solution that you want:
private static Arrangement Accumulate(IEnumerable<Arrangement> arrangements)
{
var stepsByRank = new Dictionary<int, Step>();
foreach (var arrn in arrangements)
foreach (var step in arrn.Steps)
stepsByRank[step.Rank] = step;
return new Arrangement { Steps = stepsByRank.Values.ToArray() };
}
What's missing from this? How else do you want to "aggregate" the arrangements and steps? (edit: after reading your comment, maybe this actually is what you want.)

Categories

Resources