Remove 3 oldest elements from a List<> in C# - c#

Let's say I have an object:
public class CustomObj
{
DateTime Date { get; set; }
String Name { get; set; }
}
Then let's say I have a List with 20 various elements.
var stuff = new List<CustomObj>
{
{ Date = DateTime.Now, Name = "Joe" },
{ Date = DateTime.Now.AddDays(1), Name = "Joe2" },
{ Date = DateTime.Now.AddDays(2), Name = "Joe3" },
{ Date = DateTime.Now.AddDays(3), Name = "Joe4" },
{ Date = DateTime.Now.AddDays(4), Name = "Joe5" },
{ Date = DateTime.Now.AddDays(5), Name = "Joe6" },
{ Date = DateTime.Now.AddDays(6), Name = "Joe7" },
{ Date = DateTime.Now.AddDays(7), Name = "Joe8" },
{ Date = DateTime.Now.AddDays(8), Name = "Joe9" },
{ Date = DateTime.Now.AddDays(9), Name = "Joe10" },
{ Date = DateTime.Now.AddDays(10), Name = "Joe11" }
}
How can I remove the 3 oldest elements?
stuff.RemoveAll(item => ???)

If you only need to enumerate the items, this will work:
stuff.OrderBy(item => item.Date).Skip(3);
If you actually want it in list form you will have to call .ToList() afterwards:
stuff = stuff.OrderBy(item => item.Date).Skip(3).ToList();

If you're willing to replace the list with a new one, you could try this:
stuff = stuff.OrderBy( c => c.Date).Skip(3).ToList();
On the other hand, if you need stuff to remain the same exact List<T> instance, you could sort it and then remove a range by index:
stuff.Sort(...);
stuff.RemoveRange(0, 3);

If your list is ordered you could simply use the RemoveRange method:
int n = 3;
stuff.RemoveRange(stuff.Count - n, n);

const int cToRemove = 3;
var top3 = (from c in stuff
orderby c.Date ascending
select c).Take(cToRemove);

All the other answers so far have relied on sorting the list, which is an O(n log n) operation if you don't already have it sorted.
Here's a solution which is O(n) albeit it with a horrible constant factor. It uses MinBy from MoreLINQ - you could easily rewrite that in your own code if you need to, and even make it return the index directly instead of the value (and useRemoveAt instead of Remove).
// The list.Count part is in case the list starts off with
// fewer than 3 elements
for (int i = 0; i < 3 && list.Count > 0; i++)
{
var oldest = list.MinBy(x => x.Date);
list.Remove(oldest);
}
You could certainly write this more efficiently to find the oldest three elements in a single pass of the list - but the code would be significantly more complicated, leading to more chances for errors. The above should work fine in O(n), even if it's lacking in elegance when you think of it going through the list 6 times :)

Related

Adding a new line in "string.join()" when formatting arrays?

Hello I am a newbie programmer, I am trying to format arrays in a way where there is a line break at the end of a specific set of arrays. I currently have 4 separate arrays in which I want to arrange each item in the array to a specific pattern. I have accomplished this task but now I am stumped because they are all in one line. Let me give you an example: (I am doing this on a datagridview by the way)
(This is what I want to happen)
Binder Clips Small pc 12 1260
Selleys All Clear pc 12 2400
(This is what I am getting)
Binder Clips Small pc 12 1260 Selleys All Clear pc 12 2400
This is my code:
//I get these from a datagridview
var items = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[1].Value.ToString().Trim())
.ToArray();
var units = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[2].Value.ToString().Trim())
.ToArray();
var quantity = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[6].Value.ToString().Trim())
.ToArray();
var prices = carto.Rows
.Cast<DataGridViewRow>()
.Select(x => x.Cells[8].Value.ToString().Trim())
.ToArray();
//this is what I use to sort out the pattern that I want the arrays to be in
string[] concat = new string[items.Length * 4];
int index = 0;
for (int i = 0; i < items.Length; i++)
{
concat[index++] = items[i];
concat[index++] = units[i];
concat[index++] = quantity[i];
concat[index++] = prices[i];
}
// and this is where I am stuck because I can just put \n, it would ruin the format even more
cartitems.Text = string.Join(" ", concat);
I also tried doing something like this:
int j = 0;
string str = "";
foreach (var item in concat)
{
str += item;
if (j <= concat.Length - 1)
{
if (j % 3 == 0)
str += " ";
else
str += "\n";
}
j++;
}
It kinda gets the job done but the line breaks are all over the place.
This is what my projects look like so you can get a better gist on where am I getting the data from the 4 arrays:
basically the product name, unit, quantity and line total
and lastly I am storing in on a label so I can see how it the formatting looks like:
that about sums up my problem, I really hope you can help a newbie like me out, I have a feeling the answer is quite simple and I am just un-experienced.
As a general rule, you should keep your data structures (how the data is stored, implemented here as an array of values) separate from the data representation (in this case, written to a list box).
C# is an object-oriented language, so we might as well take advantage of that, right?
Create a class for your items.
class Item
{
public string Name { get; set; }
public string Unit { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
override public string ToString() {
return $"{Name} {Unit} {Quantity} {Price}";
}
}
This is how you load your array.
Item[] concat = new Item[items.Length];
int index = 0;
for (int i = 0; i < items.Length; i++) {
concat[index++] = new Item {
Name = items[i],
Unit = units[i],
Quantity = quantity[i],
Price = prices[i]
};
}
and this is how you can add the list of items to a listbox.
foreach(Item item in concat) {
listBox.Items.Add(item);
}

c# Match between two lists, then add up time in between the two lists

I have two lists and a class
public class CommonLog
{
public string Break { get; set; }
public string Cart { get; set; }
public string Length { get; set; }
}
This is list one
commonlog.Add(new CommonLog { Break = breakTimeVar, Cart = cartVar,
Length = lengthHours });
and one like this list 2
commonlog2.Add(new CommonLog { Break = breakTimeVar2, Cart = cartVar2,
Length = lengthHours2 });
The two pieces of information I need to match are as follows
List 1 contains this
0016 009130 00:01:30
List 2 Contains this
0016 0066486 00:00:30
0016 0050093 00:00:30
0016 0063791 00:00:30
I need to match up the first number 0016 between the two lists, and then add up the last numbers 00:00:30 (3 x 30 seconds) from list 2 and compare that total time against list 1 total time, and then make a decision based on if the total of the last numbers (time) from list 2 equal list 1
How would I achieve that?
Here it is a LINQ solution which aggregates your List 2 entries in a similar (but more compact) way of Romoku answer:
var groupedLogs = commonlog2
.GroupBy(c => c.Break, c => TimeSpan.Parse(c.Length))
// group logs by Break, and get the TimeSpan representation of Length
// for each entry of the group
.ToDictionary(g => g.Key, g => g.Aggregate(TimeSpan.Zero, (s, c) => s + c));
// create a dictionary and aggregate each log group into sums of TimeSpans
Then you may iterate through each item of commonlog and compare the results:
foreach(var log in commonlog)
{
TimeSpan sum;
groupedLogs.TryGetValue(log.Break, out sum);
if(sum == TimeSpan.Parse(log.Length))
{
// do something
}
}
Or a one liner way to get only matching entries from commonlog (using C# 7 features):
var matching = commonlog.Where(
l => groupedLogs.TryGetValue(l.Break, out TimeSpan v)
&& TimeSpan.Parse(l.Length) == v);
You can group the individual breaks using GroupBy then loop through the aggregate breaks to find matches.
To sum the individual breaks there is Aggregate.
I recommend using TimeSpan instead of string for the Length.
Data
var totalBreaks = new List<CommonLog>
{
new CommonLog
{
Break = "0016",
Cart = "009130",
Length = "00:01:30"
}
};
var individualBreaks = new List<CommonLog>
{
new CommonLog
{
Break = "0016",
Cart = "0066486",
Length = "00:00:30"
},
new CommonLog
{
Break = "0016",
Cart = "0050093",
Length = "00:00:30"
},
new CommonLog
{
Break = "0016",
Cart = "0063791",
Length = "00:00:30"
}
};
Logic
//Group the individual breaks by their Break
var breakGroups = individualBreaks.GroupBy(x => x.Break);
// Loop through the aggregates
foreach (var totalBreak in totalBreaks)
{
// Match the aggregate to the individual
// The Key is the Break for all individual breaks in the group
var breaks = breakGroups.FirstOrDefault(x => x.Key == totalBreak.Break);
// Do we have a match?
if (breaks == null)
{
continue;
}
var breakLength = TimeSpan.Parse(totalBreak.Length);
// Add up the individual breaks with Aggregate
var breakTotal =
breaks
.Aggregate(
TimeSpan.Zero, // Initial break is 00:00:00
(time, b) => // Add each break to the initial
time.Add(TimeSpan.Parse(b.Length)));
// Does the break length match the total number of breaks?
if (breakLength == breakTotal)
{
}
}

What is the easiest way to split columns from a txt file

I've been looking around a bit but haven't really found a good example with what I'm struggling right now.
I have a .txt file with a couple of columns as follows:
# ID,YYYYMMDD, COLD,WATER, OD, OP,
52,20120406, 112, 91, 20, 130,
53,20130601, 332, 11, 33, 120,
And I'm reading these from the file into a string[] array.
I'd like to split them into a list
for example
List results, and [0] index will be the first index of the columns
results[0].ID
results[0].COLD
etc..
Now I've been looking around, and came up with the "\\\s+" split
but I'm not sure how to go about it since each entry is under another one.
string[] lines = File.ReadAllLines(path);
List<Bus> results = new List<Bus>();
//Bus = class with all the vars in it
//such as Bus.ID, Bus.COLD, Bus.YYYYMMDD
foreach (line in lines) {
var val = line.Split("\\s+");
//not sure where to go from here
}
Would greatly appreciate any help!
Kind regards, Venomous.
I suggest using Linq, something like this:
List<Bus> results = File
.ReadLines(#"C:\MyFile.txt") // we have no need to read All lines in one go
.Skip(1) // skip file's title
.Select(line => line.Split(','))
.Select(items => new Bus( //TODO: check constructor's syntax
int.Parse(items[1]),
int.Parse(items[3]),
DateTime.ParseExact(items[2], "yyyyMMdd", CultureInfo.InvariantCulture)))
.ToList();
I would do
public class Foo
{
public int Id {get; set;}
public string Date {get; set;}
public double Cold {get; set;}
//...more
}
Then read the file
var l = new List<Foo>();
foreach (line in lines)
{
var sp = line.Split(',');
var foo = new Foo
{
Id = int.Parse(sp[0].Trim()),
Date = sp[1].Trim(),//or pharse the date to a date time struct
Cold = double.Parse(sp[2].Trim())
}
l.Add(foo);
}
//now l contains a list filled with Foo objects
I would probably keep a List of properties and use reflection to populate the object, something like this :
var columnMap = new[]{"ID","YYYYMMDD","COLD","WATER","OD","OP"};
var properties = columnMap.Select(typeof(Bus).GetProperty).ToList();
var resultList = new List<Bus>();
foreach(var line in lines)
{
var val = line.Split(',');
var adding = new Bus();
for(int i=0;i<val.Length;i++)
{
properties.ForEach(p=>p.SetValue(adding,val[i]));
}
resultList.Add(adding);
}
This is assuming that all of your properties are strings however
Something like this perhaps...
results.Add(new Bus
{
ID = val[0],
YYYYMMDD = val[1],
COLD = val[2],
WATER = val[3],
OD = val[4],
OP = val[5]
});
Keep in mind that all of the values in the val array are still strings at this point. If the properties of Bus are typed, you will need to parse them into the correct types e.g. assume ID is typed as an int...
ID = string.IsNullOrEmpty(val[0]) ? default(int) : int.Parse(val[0]),
Also, if the column headers are actually present in the file in the first line, you'll need to skip/disregard that line and process the rest.
Given that we have the Bus class with all the variables from your textfile:
class Bus
{
public int id;
public DateTime date;
public int cold;
public int water;
public int od;
public int op;
public Bus(int _id, DateTime _date, int _cold, int _water, int _od, int _op)
{
id = _id;
date = _date;
cold = _cold;
water = _water;
od = _od;
op = _op;
}
}
Then we can list them all in the results list like this:
List<Bus> results = new List<Bus>();
foreach (string line in File.ReadAllLines(path))
{
if (line.StartsWith("#"))
continue;
string[] parts = line.Replace(" ", "").Split(','); // Remove all spaces and split at commas
results.Add(new Bus(
int.Parse(parts[0]),
DateTime.ParseExact(parts[1], "yyyyMMdd", CultureInfo.InvariantCulture),
int.Parse(parts[2]),
int.Parse(parts[3]),
int.Parse(parts[4]),
int.Parse(parts[5])
));
}
And access the values as you wish:
results[0].id;
results[0].cold;
//etc.
I hope this helps.

create an array of anonymous type

I am trying to get data from database for Google charts in my program. I would like to create an array of anonymous type (var) instead of repeating my code over and over again:
public JsonResult GetChartData(int sID, int regionID)
{
var testPathOne = from p in _rep.GetMetricsData().GetLHDb().page_loads
where p.t_3id == sID && p.test_path_id == 1
select new { time = p.time, created_at = p.created_at };
var testPathTwo = from p in _rep.GetMetricsData().GetLHDb().page_loads
where p.t_3id == sID && p.test_path_id == 2
select new { time = p.time, created_at = p.created_at };
var tOne = testPathOne.ToArray();
var tTwo = testPathTwo.ToArray();
var name = new { test1 = tOne, test2 = tTwo };
return Json(name);
}
i know that i will need a for loop so i can go through all the test path id's instead of hard coding them like this p.test_path_id == 1, but my question is how would i make this part dynamic var name = new { test1 = tOne, test2 = tTwo };
Edit:
I apologize, I would like to do something like this:
name is an array
for loop:
testPath = query
name.Add(testPath)
I hope that makes sense
The easiest solution in this particular case would be to just give a name to the class that is currently anonymous. While there are workarounds that you can use, when you need to start working really hard to use an anonymous type you probably shouldn't be using it. It's there to make certain tasks quicker and easier; if that isn't happening then you are likely better off with a real class.
That solution would look something like this:
//Please give me a real name
public class ClassToBeRenamed
{
public DateTime Time { get; set; }
public DateTime CreatedAt { get; set; }
}
List<ClassToBeRenamed[]> myList = new List<ClassToBeRenamed[]>();
for (int i = 0; i < 10; i++)
{
myList.Add((from p in _rep.GetMetricsData().GetLHDb().page_loads
where p.t_3id == sID && p.test_path_id == i
select new ClassToBeRenamed { Time = p.time, CreatedAt = p.created_at })
.ToArray());
}
Having said all of that, it's still possible.
var myList = new[]{
from p in _rep.GetMetricsData().GetLHDb().page_loads
where p.t_3id == sID && p.test_path_id == 1
select new { time = p.time, created_at = p.created_at }.ToArray()
}.ToList();
for (int i = 2; i < 10; i++)
{
myList.Add(from p in _rep.GetMetricsData().GetLHDb().page_loads
where p.t_3id == sID && p.test_path_id == i
select new { time = p.time, created_at = p.created_at }.ToArray()
);
}
var myArray = myList.ToArray();
If it's really, really important that you have an array, and not a list, then you could call ToArray on myList at the very end. It's important that you start out with a list, and only convert it to an array at the end because Arrays have a fixed size once they are created. You can mutate their contents, but you can't make them bigger or smaller. A List on the other hand, is designed to mutate it's size over time, so it can start out with 0 or 1 items and then add items over time, which is important for us in this particular context. (It's actually useful quite often, which is why it is frequently useful to use List over arrays.)
Instead of using LINQ use foreach loops. You are more limited with LINQ.
Also define your ArrayLists at the beginning and add to them as you go.
var test1 = new ArrayList();
var test2 = new ArrayList();
foreach(PageLoad p in _rep.GetMetricsData().GetLHDb().page_loads)
{
if(p.t_3id == sID)
{
var tr = new { time = p.time, created_at = p.created_at };
switch(p.test_path_id)
{
case 1: test1.Add(tr); break;
case 2: test2.Add(tr); break;
}
}
}
return Json(new { test1, test2, });
You do not need to define the names of properties anonymous types because they default to the variable names.

List<T> - distinction by T.field

I have a List<X> where X has a couple of fields:
public string word;
public int count;
how do I get a List<X> with distinct X.word elements?
You can use grouping
var n = from n in items
group n by n.word into g
select g.First();
MoreLinq has a DistinctBy method:
var distinctByWord = list.DistinctBy(x => x.Word).ToList();
From your data structure, I'd suggest you probably want a Dictionary instead of a list.
If you are doing something like counting the number of times a word is seen, or even combining (word,count) pairs from some other input by adding the counts, it will be more efficient to do this with a Dictionary because you won't have to scan the list to find the entry to update.
You'll need to use the overload of the Distinct method that takes an instance of IEqualityComparer<X>:
new List<X>().Distinct(new XComparer());
public class XComparer : IEqualityComparer<X> {
public bool Equals(X x, X y) {
return x.word.Equals(y.word);
}
public int GetHashCode(X obj) {
return obj.word.GetHashCode();
}
}
public class X {
public string Word { get; set; }
public int Count { get; set; }
}
And then:
var myList = new List<X>() {
new X(){ Count = 1, Word = "A" },
new X(){ Count = 2, Word = "A"},
new X(){ Count = 1, Word = "B"}
};
foreach(var x in myList.Distinct(new XComparer()))
Console.WriteLine(x.Count + " " + x.Word);
Prints:
1 A
1 B
I think the idea is to count the words and not to lose counts for words with the same name, right? If so, it reminds me map-reduce algorithm. You have already done map, so you need to do reduce somehow. I recommend you to create new Dictionary<string,int> and loop your list. If Dictionary does not have word - add it (key - word, count - value), if has - add count to value.

Categories