I have a collection where I am trying to sort the records first by Quarter and then inside the quarter by highest amounts. My code so far is:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Test> lstTest = new List<Test>();
lstTest.Add(new Test { dt = new DateTime(2017, 1, 2), amount = 2500 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 2), amount = 10000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 5), amount = 4000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 10), amount = 40000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 15), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 1, 25), amount = 12000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 5), amount = 38000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 10), amount = 38000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 15), amount = 4000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 20), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 2, 20), amount = 20000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 15), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 20), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 20), amount = 4000 });
lstTest.Add(new Test { dt = new DateTime(2017, 3, 31), amount = 1000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 9), amount = 50000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 11), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 21), amount = 1000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 21), amount = 10000 });
lstTest.Add(new Test { dt = new DateTime(2017, 4, 28), amount = 5000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 5), amount = 45000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 7), amount = 98000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 9), amount = 7000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 25), amount = 2000 });
lstTest.Add(new Test { dt = new DateTime(2017, 5, 31), amount = 1000 });
var result = lstTest.Select(x => new
{
Amount = x.amount,
Date = x.dt,
MonthDiff = GetMonthsDiff(DateTime.Now, x.dt),
Quater = GetQuarter(DateTime.Now, x.dt)
}).OrderBy(o=>o.Quater).ToList();
foreach (var res in result)
{
Console.WriteLine("Amount = {0} Date= {1} MonthDiff= {2} Quater= {3}", res.Amount, res.Date, res.MonthDiff, res.Quater);
}
Console.ReadKey();
}
public static string GetQuarter(DateTime start, DateTime end)// int month)
{
int month = GetMonthsDiff(start, end);
string quarter = month <= 3 ? "Q1" : (month >= 4 && month <= 6) ? "Q2" : (month >= 7 && month <= 9) ? "Q3" : "Q4";
return quarter;
}
public static int GetMonthsDiff(DateTime start, DateTime end)
{
if (start > end)
return GetMonthsDiff(end, start);
int months = 0;
do
{
start = start.AddMonths(1);
if (start > end)
return months;
months++;
}
while (true);
}
}
public class Test
{
public DateTime dt { get; set; }
public int amount { get; set; }
}
}
The output is
If I do OrderBy(o=>o.Quater).OrderByDescending(o=>o.Amount) the output changes to
That is it is first sorting by Quarter and then by Amount.
But I am looking for first sort by Quarter and within the Quarter sort by Amount descending.
The desired output is
What needs to be modified in the program so as achieve the target?
replace
OrderBy(o=>o.Quater).OrderByDescending(o=>o.Amount)
with
OrderBy(o=>o.Quater).ThenByDescending(o=>o.Amount)
ThenByDescending performs a subsequent ordering of the elements in a
sequence in descending order by using a specified comparer.
Here you are sorting the list two times, you are getting the output of the final sort only, if you want to perform the second sort over the sorted result of the first then you have to make use of ThenByDescending(if you want the second sort in descending order or else use ThenBy()) followed by OrderBy as like this:
var sortedItems = lstTest.OrderBy(o=>o.Quater).ThenByDescending(o=>o.Amount);
I have modified the code here in this example, you can check the output in the fiddle
You don't need to sort by a single property only. You can return the sort fields as an anonymous object tuple.
In C# 7 you can write :
OrderBy(o => (o.Quarter,o.Amount));
In previous versions :
OrderBy(o => Tuple.Create(o.Quarter,o.Amount));
If you want to use different sort orders you have to specify the fields one at a time, eg:
OrderBy(o=>o.Quarter).ThenByDescending(o=>o.Amount);
Or you can use query syntax to make the code cleaner:
var result = ( from x in lstTest
let o = new {
Amount = x.amount,
Date = x.dt,
MonthDiff = GetMonthsDiff(DateTime.Now, x.dt),
Quarter = GetQuarter(DateTime.Now, x.dt)
}
orderby o.Quarter, o.Amount descending
select o
).ToList()
Related
I am keeping records of data usage daily and want to display out the total data usage between a date range.
The data resets at 10th every month so an example data usage would be like:
Number: 001xxxxxxxx
Date - Data
8th - 100mb
9th - 120mb
10th - 10mb
11th - 40mb
So to get the total data usage between 8th and 11th, it's not possible to take the data at 11th deducted by 8th (40mb-100mb) as it resets at 10th.
I would need something like, 40mb+(120mb-100mb) = 60mb total usage
here is my method that gives the data, date and number into Dictionarys
private void getNumberDatas(List<int> phoneNo, DateTime dateStart, DateTime dateEnd)
{
Dictionary<int, Dictionary<DateTime, float>> d_PhoneNo_DateDataList = new Dictionary<int, Dictionary<DateTime, float>>();
Dictionary<DateTime, float> d_DateTime_Data = new Dictionary<DateTime, float>();
string strConnectionString = ConfigurationManager.ConnectionStrings["xxx"].ConnectionString;
SqlConnection conn = new SqlConnection(strConnectionString);
string sqlcommand = "SELECT xxx FROM xxx WHERE PhoneNo=#PhoneNo AND date BETWEEN #Date1 AND #Date2";
for (int i = 0; i < phoneNo.Count; i++)
{
d_DateTime_Data.Clear();
using (SqlCommand cmd = new SqlCommand(sqlcommand, conn))
{
conn.Open();
cmd.Parameters.AddWithValue("#PhoneNo", phoneNo[i]);
cmd.Parameters.AddWithValue("#Date1", dateStart);
cmd.Parameters.AddWithValue("#Date2", dateEnd);
cmd.ExecuteNonQuery();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
d_DateTime_Data.Add(DateTime.Parse(reader["Date"].ToString()), float.Parse(reader["Data"].ToString()));
}
conn.Close();
}
d_PhoneNo_DateDataList.Add(phoneNo[i], d_DateTime_Data);
}
}
So to get a data for today would be:
Dictionary<DateTime, float> temp_Datetime_data = new Dictionary<DateTime, float>();
if (d_PhoneNo_DateDataList.TryGetValue("001xxxxxxxx", out temp_Datetime_data))
{
float dataToday;
if (temp_Datetime_data.TryGetValue(DateTime.Today, out dataToday))
{
Console.WriteLine(dataToday.ToString());
}
}
Now my problem is that I do not know how to implement the calculations if let's say the user chooses 20th May 2016 to 30th June 2016
Let me know if it's too confusing, I don't know the best way to explain this
It's not perfect but it will be a good base for your needs:
private static int getData(Dictionary<DateTime, int> values, DateTime start, DateTime stop)
{
// Reduce the scope
var val = values.Where(x => x.Key >= start && x.Key <= stop);
// Create a list of ranges
List<Dictionary<DateTime, int>> listOfMonth = new List<Dictionary<DateTime, int>>
{
new Dictionary<DateTime, int>()
};
var currentMonth = listOfMonth.Last();
int previousValue = int.MinValue;
foreach (KeyValuePair<DateTime, int> keyValuePair in val)
{
// Reset the month
if (keyValuePair.Value < previousValue)
{
currentMonth = new Dictionary<DateTime, int>()
{
{DateTime.Now, 0} // Add placeholder
};
listOfMonth.Add(currentMonth);
}
previousValue = keyValuePair.Value;
currentMonth.Add(keyValuePair.Key, keyValuePair.Value);
}
return listOfMonth.Sum(dictionary => dictionary.Values.Max() - dictionary.Values.Min());
}
With this data:
Dictionary<DateTime, int> dataDictionary = new Dictionary<DateTime, int>()
{
{new DateTime(2016, 8, 4), 20},
{new DateTime(2016, 8, 5), 50},
{new DateTime(2016, 8, 6), 70},
{new DateTime(2016, 8, 7), 90},
{new DateTime(2016, 8, 8), 100},
{new DateTime(2016, 8, 9), 120},
{new DateTime(2016, 8, 10), 10},
{new DateTime(2016, 8, 11), 40},
};
DateTime start = new DateTime(2016, 8, 7);
DateTime stop = new DateTime(2016, 8, 11);
I get 70 (120-90) + (40 - 0)
As I understand it, the the problem can be restated as
Calculate the sum of the differences between the usages, except
On the reset date, use the amount rather than the difference
For example purposes we can use a small usage class
class Usage
{
public Usage(int day, int totalUsage)
{
Day = day;
TotalUsage = totalUsage;
}
public int Day { get; }
public int TotalUsage { get; }
}
and a list of usages to represent the log
private readonly static List<Usage> Log = new List<Usage>
{
new Usage(8,100),
new Usage(9,120),
new Usage(10,10),
new Usage(11,40),
};
We can calculate the total usage for the periods as follows
private void CalcTotalUsage(int startDate, int endDate, int expectedTotal)
{
// I just used ints for the days for demo purposes, dates will not change the calculation below
var records = Log.Where(u => u.Day >= startDate && u.Day <= endDate);
var total = records.Skip(1).Zip(records, (curr, prev) => (curr.TotalUsage > prev.TotalUsage) ? curr.TotalUsage - prev.TotalUsage : curr.TotalUsage).Sum();
Assert.AreEqual(expectedTotal, total);
}
and the tests to check that it works correctly
[TestMethod]
public void CheckTotals()
{
CalcTotalUsage(8, 9, 20);
CalcTotalUsage(8, 10, 30);
CalcTotalUsage(8, 11, 60);
}
I have a list of objects in which every object is containing a list itself. how do I get the the JellyFishID field or the Amount field for using an IF argument
(I'm currently using Foreach):`
public static List<Report> DataSorted = new List<Report> {
new Report() { IsGoldUser=true, Date=new DateTime(2016, 3, 12,11, 59, 33), IsBurningWater=true, Type=Type.Shore, ZoneID = 1 ,
ReportDetails =new List<ReportDetail> { new ReportDetail() { Amount = Amount.Few, Jellyfish = new Jellyfish { JellyfishID = 1, Venom = Venom.Strong } } } },
new Report() { IsGoldUser=true, Date=new DateTime(2016, 3, 12, 11, 59, 33), IsBurningWater=true, Type=Type.Shore, ZoneID = 1 ,
ReportDetails =new List<ReportDetail> { new ReportDetail() { Amount = Amount.Few, Jellyfish = new Jellyfish { JellyfishID = 1, Venom = Venom.Strong } } } },
new Report() { IsGoldUser=true, Date=new DateTime(2016, 3, 12, 11, 59, 33), IsBurningWater=true, Type=Type.Shore, ZoneID = 1 ,
ReportDetails =new List<ReportDetail> { new ReportDetail() { Amount = Amount.Few, Jellyfish = new Jellyfish { JellyfishID = 1, Venom = Venom.Strong } } } },
new Report() { IsGoldUser=true, Date=new DateTime(2016, 3, 12, 11, 59, 33), IsBurningWater=true, Type=Type.Shore, ZoneID = 1 ,
ReportDetails =new List<ReportDetail> { new ReportDetail() { Amount = Amount.Few, Jellyfish = new Jellyfish { JellyfishID = 1, Venom = Venom.Strong } } } },
foreach (var item in DataSorted)
{
if (item.ReportDetails....) //???I want here to Make an Argument about The Amount field or the JellyFishID field in the list above....
}
You don't describe exactly what you want to check, but with LINQ to Objects you have a lot of possiblities. At first, you need to reference the correct namespace with
using System.Linq;
at the top of your source code file.
Now, if you want to check if any items of your list contains a jellyfish with a given ID, you can use:
if (item.ReportDetails.Any(t => t.Jellyfish.JellyfishID == 1)) //...
Additionally you can have conditions inside a Where-function to filter your list and search only for jellyfish with a few amount:
if (item.ReportDetails.Where(t => t.Amount == Amount.Few).
Any(t => t.Jellyfish.JellyfishID == 1)) //...
There is a lot of information avaliable about LINQ, a lot of examples are in the MSDN (for example this intro page), but there are alternatives like this one: 101 Linq examples. It even has a tag on StackOverflow.
I have LINQ sql (see below, thanks to Cameron ). I am trying to get a property (ItemCode) from class First without using that in Group by clause.
How do I do that?
Don't use First.ItemCode in group by but still want it in output by First.Begin, First.End order by decending.
public class First
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
public string ItemCode;
}
public class Second
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
}
List<First> firstAccount = new List<First>();
List<Second> secondAccount = new List<Second>();
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 7),
Amount = 1000,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 0,
ItemCode = ""
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 7, 7),
End = new DateTime(2014, 7, 14),
Amount = 1000,
ItemCode = "BBB"
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 2000
});
var result = from account in (from first in firstAccount
join second in secondAccount
on first.Account equals second.Account
where
((first.Begin >= second.Begin && first.Begin <= second.Begin) &&
(first.End >= second.Begin && first.End <= second.End))
select new
{
first.Account,
second.Begin,
second.End,
first.Amount,
first.ItemCode
})
group account by new {account.Account, account.Begin, account.End }
into groupedAccounts
select new
{
groupedAccounts.Key.Account,
groupedAccounts.Key.Begin,
groupedAccounts.Key.End,
Sum = groupedAccounts.Sum(a => a.Amount)
};
One way to get the itemcode is to change the last select.
Add this line
Itemcode = String.Join(" ",groupedAccounts.Select(q=> q.ItemCode))
after Sum = groupedAccounts.Sum(a => a.Amount),
It should produce itemcode
foreach (var data in result)
{
Console.WriteLine(data.Account + " " + data.Itemcode);
}
Output
1234 AAA
1234 AAA
I have 2 lists in C#:
public class AvailableSlot
{
public DateTime DateTime;
public string Name
}
List<AvailableSlot> list1 = GetList();
List<AvailableSlot> list2 = GetAnotherList();
I want to call intersect on these lists to find out where there are items in both lists for the same date. I know i can use .Intersect to get this info but I have a slightly more complicated requirement. I want to return a intersected list but i want to this list to contain a list of objects with all of the name in them. so something like this:
List<AvailableSlot2> intersectedList . ..
where AvailableSlot2 is this below:
public class AvailableSlot2
{
public DateTime DateTime;
public string[] Names;
}
Is there any ability to do this transformation after trying to intersect between two lists?
I would just union the two lists, group by DateTime and then pull out the names from the group:
var list1 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Alpha" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bravo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Charlie" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Delta" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Echo" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 3), Name = "Foxtrot" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 4), Name = "Golf" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hotel" }
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Apple" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Bannana" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 1), Name = "Dog" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 2), Name = "Egg" },
new AvailableSlot { DateTime = new DateTime(2013, 2, 5), Name = "Hi" }
};
var list3 = list1.Where (l => list2.Where (li => l.DateTime == li.DateTime).Any ())
.Union(list2.Where (l => list1.Where (li => l.DateTime == li.DateTime).Any ()));
var groupedItems = from slot in list3
group slot by slot.DateTime into grp
select new AvailableSlot2 {
DateTime = grp.Key,
Names = grp.Select (g => g.Name).ToArray()
};
foreach(var g in groupedItems)
{
Console.WriteLine(g.DateTime);
foreach(var name in g.Names)
Console.WriteLine(name);
Console.WriteLine("---------------------");
}
Output:
2/1/2013 12:00:00 AM
Alpha
Delta
Apple
Dog
---------------------
2/2/2013 12:00:00 AM
Bravo
Echo
Bannana
Egg
---------------------
2/5/2013 12:00:00 AM
Hotel
Hi
---------------------
You can use a LINQ to Objects Join() to line up items with the same DateTime property and then collect all the names into an array
var joinedItems = from slot1 in list1
join slot2 in list2
on slot1.DateTime equals slot2.DateTime into g
where g.Any()
select new AvailableSlot2
{
DateTime = slot1.DateTime,
Names = Enumerable.Range(slot1.Name,1).Union(g.Select(s => s.Name)).ToArray()
}
You can make use of ToLookup:
DateTime dt1 = new DateTime(2013, 2, 1);
DateTime dt2 = new DateTime(2013, 3, 1);
DateTime dt3 = new DateTime(2013, 4, 1);
var list1 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt1, Name = "n3",},
};
var list2 = new List<AvailableSlot>
{
new AvailableSlot{DateTime = dt1, Name = "n1",},
new AvailableSlot{DateTime = dt2, Name = "n2",},
new AvailableSlot{DateTime = dt3, Name = "n3",},
};
var intersected = list1.Select (l => l.DateTime).
Intersect(list2.Select (l => l.DateTime));
var lookup = list1.Union(list2).ToLookup (
slot => slot.DateTime, slot => slot);
lookup.Where (l => intersected.Contains(l.Key)).Select (
slot => new
{
DateTime=slot.Key,
Names=slot.Select (s => s.Name)
});
Which in this case gives the result:
DateTime Names
01/02/2013 00:00 n1
n3
n1
01/03/2013 00:00 n2
n2
You could of course use Names=slot.Select(s => s.Name).Distinct() to get a distinct list of names.
Have a list like this:
01/01/2009, 120
04/01/2009, 121
30/12/2009, 520
01/01/2010, 100
04/01/2010, 101
31/12/2010, 540
I need to find the last value for each year, e.g. the result would be 520, 540?
var lastValues = records.OrderByDescending(r => r.Date)
.GroupBy(r => r.Date.Year)
.Select(g => g.First().Value);
class Program
{
static void Main()
{
var list = new[]
{
new { Date = new DateTime(2009, 1, 1), Value = 120 },
new { Date = new DateTime(2009, 4, 1), Value = 121 },
new { Date = new DateTime(2009, 12, 30), Value = 520 },
new { Date = new DateTime(2010, 1, 1), Value = 100 },
new { Date = new DateTime(2009, 4, 1), Value = 101 },
new { Date = new DateTime(2010, 12, 31), Value = 540 },
};
var result = list
.GroupBy(x => x.Date.Year)
.Select(g => new { Date = g.Key, MaxValue = g.Max(x => x.Value) });
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}