I have a custom object list which has month and another int value. I am getting this list from the database. So data coming from a web service as JSON. Sometimes month is not consecutive. Sometimes some month get missing. For example if this is month 7,
then months in list may contains something like this.
{1,2,3,6,7}
so I want to add the missing months which are 4,5 ----
{1,2,3,4,5,6,7}
other value should be 0 (NEW_REC_COUNT)
My Object class
public class NewData
{
public int MONTH { get; set; }
public int NEW_REC_COUNT { get; set; }
}
Sample Json
[
{
"MONTH": 1,
"NEW_REC_COUNT": 19
},
{
"MONTH": 2,
"NEW_REC_COUNT": 5
},
{
"MONTH": 3,
"NEW_REC_COUNT": 2
},
{
"MONTH": 6,
"NEW_REC_COUNT": 9
},
{
"MONTH": 7,
"NEW_REC_COUNT": 3
}
]
You can try below approach,
Select all months (int value) from list using Select
var months = NewDataList.Select(x => x.MONTH); // This will give you all integers i.e MONTHs.
Find Max() from months and create Range from 1... maxMonths
var maxMonths = months.Max();
var oneTomaxMonths = Enumerable.Range(1,maxMonths).ToList();
Now you have 2 lists i.e. months and oneToMaxMonths, use Except to get missing Months from list of New Data
var results = oneTomaxMonths.Except(months);
Foreach result create new instance with NEW_REC_COUNT = 0
POC : .net Fiddle
If you don't have a lot of data, you can try a smiple loop and Insert omitted items into the list:
List<NewData> list = ...
// If list is not guarantee to be sorted
list.Sort((a, b) => a.MONTH.CompareTo(b.MONTH));
for (int i = 0; i < list.Count - 1; ++i) {
NewData current = list[i];
NewData next = list[i + 1];
// Do we have a hole at position i + 1?
if (current.MONTH + 1 < next.MONTH) {
list.Insert(i + 1, new NewData() {
MONTH = current.MONTH + 1, // Omitted month
NEW_REC_COUNT = 0, // Default value
});
}
}
Edit: If we want months from 1 up and including the current month (DateTime.Today.Month), we can use Linq:
using System.Linq;
...
List<NewData> list = ...
// Let's generalize a bit if you want, say Q3 period
int fromMonth = 1;
int upToMonth = DateTime.Today.Month; // or 7 for testing
list = Enumerable
.Range(fromMonth, upToMonth - fromMonth + 1)
.Select(month =>
list.FirstOrDefault(item => item.MONTH == month)
?? new NewData() { MONTH = month, // Omitted month
NEW_REC_COUNT = 0 }) // Default value
.ToList();
If you want to modify existing list:
list.AddRange(Enumerable
.Range(fromMonth, upToMonth - fromMonth + 1)
.Where(month => !list.Any(item => item.MONTH == month))
.Select(month => new NewData() {
MONTH = month,
NEW_REC_COUNT = 0 })
.ToArray());
Related
I need to calculate the count of total assigned week and total benched week for an employee.
An employee is assigned in different projects in some time duration which will always start from Monday and always end on Friday. Monday to Friday will be considered as 1 week.There cant be more than 2 week duration for any project. Any overlapping weeks of assigned status should be adjusted. If employee staus is assigned and that time duration overlaps or falls with any of the benched week duration then that week duration for benched status should not be counted at all .
Below are the relevant scenario as below.
"AssignedHistory":[
{"Project":1,"status":"Assigned","Startdate":"01/03/2022", "Enddate":"01/07/2022" },
{"Project":2,"status":"Assigned","Startdate":"01/10/2022", "Enddate":"01/14/2022" },
{"Project":3,"status":"Assigned", "Startdate":"01/10/2022", "Enddate":"01/21/2022" },
{"Project":4,"status":"Assigned", "Startdate":"02/21/2022", "Enddate":"02/25/2022" },
{"Project":5,"status":"Bench","Startdate":"01/17/2022", "Enddate":"01/21/2022" },
{"Project":6,"status":"Bench","Startdate":"02/07/2022", "Enddate":"02/11/2022" }
{"Project":7,"status":"Bench","Startdate":"02/21/2022", "Enddate":"03/04/2022" }
]
Here I need to find the count of total assigned week and total benched week for an employee, and expected result should be :
Total assigned week:4
Total benched week :1
Since 1 week of assigned status is overlapping for 2 projects(no2 and no 3) from 10th Jan to 14 Jan so project 2 and 3 together will be counted as 2 .Also for assigned week of project 3 it is overlapping with benched week of project 5 from 17th Jan to 21st Jan so in that case bench week for project 5 will be counted as 0.Similarly for assigned week of project 4,duration from 21st feb to 25th feb is overlapping or falls under benched week of project 7 so in this case project 7 will be counted as 0.So only we have project 6 for benched week which doest not overlaps with any of the assined project duration.
Thats why total benched count will be 1.
This is how I am thinking.
var assignedweek = AssignedHistory.Where(x => new []{"Assigned"}.Contains(x.status)).ToList();
var benchedweek = AssignedHistory.Where(x => new []{"Bench"}.Contains(x.status)).ToList();
List<DateTime> AssignedWeeklist = assignedweek
.SelectMany(a => {
DateTime firstSunday = a.Startdate.AddDays(-(int)a.Startdate.DayOfWeek);
DateTime lastSunday = a.Enddate.AddDays(-(int)a.Enddate.DayOfWeek);
int weeks = (lastSunday - firstSunday).Days / 7 + 1;
// Enumerate one Sunday per week
return Enumerable.Range(0, weeks).Select(i => firstSunday.AddDays(7 * i));
})
.Distinct()
.ToList();
List<DateTime> BenchWeeklist = benchedweek
.SelectMany(a => {
DateTime firstSunday = a.Startdate.AddDays(-(int)a.Startdate.DayOfWeek);
DateTime lastSunday = a.Enddate.AddDays(-(int)a.Enddate.DayOfWeek);
int weeks = (lastSunday - firstSunday).Days / 7 + 1;
// Enumerate one Sunday per week
return Enumerable.Range(0, weeks).Select(i => firstSunday.AddDays(7 * i));
})
.Distinct()
.ToList();
int assignedweekcount = AssignedWeeklist.Count();
int benchedweekcount = BenchWeeklist.Except(AssignedWeeklist).Count();
It is giving correct assign week count But it is giving incorrect bench week count if there is overlapping bench week with assigned week.
Any way to do this?
you have to change the logic of your calcul on benchweek, you have to keep the sunday date by project:
( i have OS French, so the dates in sample are in format DD/MM/YYYY and not MM/DD/YYYY)
var AssignedHistory1 = new List<Projet>()
{
new Projet(1,"Assigned","03/01/2022", "07/01/2022" ),
new Projet(2,"Assigned","10/01/2022", "14/01/2022" ),
new Projet(3,"Assigned","10/01/2022", "21/01/2022" ),
new Projet(4,"Assigned","21/02/2022", "25/02/2022" ),
new Projet(5,"Bench", "17/01/2022", "21/01/2022" ),
new Projet(6,"Bench", "07/02/2022", "11/02/2022" ),
new Projet(7,"Bench", "21/02/2022", "04/03/2022" )
};
var AssignedHistory2 = new List<Projet>()
{
new Projet(1,"Assigned","17/01/2022", "28/01/2022" ),
new Projet(2,"Bench", "10/01/2022", "21/01/2022" )
};
var AssignedHistory3 = new List<Projet>()
{
new Projet(1,"Assigned","07/02/2022", "18/02/2022" ),
new Projet(2,"Bench", "14/02/2022", "25/02/2022" )
};
var listsamples = new List<List<Projet>> { AssignedHistory1, AssignedHistory2, AssignedHistory3 };
for(int i = 0; i < listsamples.Count;i++)
{
calculate(listsamples[i], $"Sample Assigned{i+1}");
}
void calculate(List<Projet> AssignedHistory, string txt)
{
var assignedweek = AssignedHistory.Where(x => new[] { "Assigned" }.Contains(x.Status)).ToList();
var benchedweek = AssignedHistory.Where(x => new[] { "Bench" }.Contains(x.Status)).ToList();
List<DateTime> AssignedWeeklist = assignedweek
.SelectMany(a =>
{
DateTime firstSunday = a.StartDate.AddDays(-(int)a.StartDate.DayOfWeek);
DateTime lastSunday = a.EndDate.AddDays(-(int)a.EndDate.DayOfWeek);
int weeks = (lastSunday - firstSunday).Days / 7 + 1;
// Enumerate one Sunday per week
return Enumerable.Range(0, weeks).Select(i => firstSunday.AddDays(7 * i));
})
.Distinct()
.ToList();
List<List<DateTime>> BenchWeeklist = benchedweek
.Select(a =>
{
DateTime firstSunday = a.StartDate.AddDays(-(int)a.StartDate.DayOfWeek);
DateTime lastSunday = a.EndDate.AddDays(-(int)a.EndDate.DayOfWeek);
int weeks = (lastSunday - firstSunday).Days / 7 + 1;
// Enumerate one Sunday per week
return new List<DateTime>() { firstSunday, lastSunday }.Distinct().ToList();
})
.Distinct()
.ToList();
int assignedweekcount = AssignedWeeklist.Count();
var benchedweekcount = 0;
foreach (var l in BenchWeeklist)
{
var found = false;
foreach (var it in l)
{
if (AssignedWeeklist.Contains(it)) found = true;
}
if (!found) benchedweekcount++;
}
Console.WriteLine($"AssignedWeek{n} count: {assignedweekcount}, BenchWeek{n} count: {benchedweekcount}");
}
result:
AssignedWeek1 count: 4, BenchWeek1 count: 1
AssignedWeek2 count: 2, BenchWeek2 count: 0
AssignedWeek3 count: 2, BenchWeek3 count: 0
I am stuck in a requirement.
I get two flags from the database, namely lastMonthNumber and lastMonthName, these can range from 1 to 12 and January to December.
Now I have a requirement such that if lastMonthName="March" and lastMonthNumner=12, then the parent list should be as below:
1, April
2, May
3, June
4, July
5, August
6, September
7, October
8, November
9, December
10, January
11, February
12, March
if lastMonthName="April" and lastMonthNumber=6, then the list should be:
7, November
8, December
9, January
10, February
11, March
12, April
This lastMonthNumber can range from 1 to 12 and lastMonthName too can range from Jan to Dec. The parent list needs to be dynamic.
If lastMonthNumber=6 and lastMonthName="April", then the list needs to have 6 elements with April as 12 and backtrack to have total 6 elements.
The parent list can be a dictionary, such as:
var monthsDictionary=new Dictionary<int, string>();
I am trying something as below, but not able to visualize further:
var monthsDictionary = new Dictionary<int, string>();
var numbers = new List<int> { 1,2,3,4,5,6,7,8,9,10,11,12};
var months = new List<string> {"January","February","March","April","May","June","July","August","September","October","November","December" };
foreach (var month in months.Select())
{
if (month == lastMonthName)
{
}
}
Please help. Pointers will be very helpful.
Have a constant with month names
private readonly string[] monthNames = { "Januar" ..., "December" };
and an accessor method that is able to cycle the index:
private string GetMonthName(int index)
{
if (index < 0) return monthNames[monthNames.Length - 1 - (index % monthNames.Length)];
return monthNames[index % monthNames.Length];
}
Then create the list:
int indexOfLastMonthName = Array.IndexOf(monthNames, lastMonthName);
var parentData = Enumerable
// Create as many items as last month number
.Range(0, lastMonthNumber)
// Create the list in reverse order, start on LastMonthName and 12,
// then go back with every item
.Select(x => new
{
Number = 12 - x,
Name = GetMonthName(indexOfLastMonthName - x)
}
// Turn it back
.Reverse();
Then fill it to an appropriate data structure. e.g. like this:
List<Tuple<int, string>> parentList = parentData
.Select(x => Tuple.Create(x.Number, x.Name)))
.ToList();
If you prefer a classic solution (using the GetMonthName method from above):
int indexOfLastMonthName = Array.IndexOf(monthNames, lastMonthName);
List<Tuple<int, string>> parentList = new List<Tuple<int, string>>();
for(int i = 0; i < lastMonthNumber; i++)
{
parentList.Add(
Tuple.Create(
12 - i,
GetMonthName(indexOfLastMonthName - i));
}
parentList.Reverse();
Try this linq query
var months = new List<string> {"January","February","March","April","May","June","July","August","September","October","November","December" };
var lastMonthNumber = ...;
var lastMonthName = ....;
var rows = months.Concat(months)
.Reverse()
.SkipWhile(m => m != lastMonthName)
.Take(lastMonthNumber)
.Reverse()
.Select((m, i) => new { id = i + 12 - lastMonthNumber + 1, m})
.ToArray();
It duplicate a months list, reverse it and skip items until lastMonthName found. After that this code limit result items count to lastMonthNumber and reverse list back
try the below fiddle.
https://dotnetfiddle.net/GYP2Go
private static Dictionary<int, string> GetRequiredResult(int lastMonthNumber, string lastMonthName)
{
var indx = months.IndexOf(lastMonthName);
// here this list will have months in required order that ends with lastMonthName
var revisedMonthList = new List<string>();
revisedMonthList.AddRange(months.Skip(indx + 1).Take(12));
revisedMonthList.AddRange(months.Take(indx + 1));
// get count = lastMonthNumber element from last using index, and then convert them to dictionary.
return revisedMonthList
.Select((mn, index) => new {index, mn})
.Where(c => c.index >= months.Count - lastMonthNumber)
.ToDictionary(c=>c.index + 1, c=>c.mn);
}
var monthsDictionary = new Dictionary<int, string>();
var numbers = new List<int> { 1,2,3,4,5,6,7,8,9,10,11,12};
var months = new List<string> {"January","February","March","April","May","June","July","August","September","October","November","December" };
int flag=0;
int items=12;
var numbersList = new List<int>();
var monthsList = new List<string>();
foreach (var month in months)
{
if(flag==0){
monthsList.Insert(items--,month);
if (month == lastMonthName)
{
flag=1;
}
}
else if(flag==1)
{
monthsList.add(month);
}
}
flag=0;
try following:
var months = Enumerable.Range(1, 12).Select(i => new
{
I = i,
M = System.Globalization.DateTimeFormatInfo.CurrentInfo.GetMonthName(i)
});
//string lastMonthName = "March"; int lastMonthNumber = 12;
//string lastMonthName = "April"; int lastMonthNumber = 6;
var selMonthInt = months.Where(x => x.M == lastMonthName).Select(y => y.I).FirstOrDefault();
int endCount = lastMonthNumber + selMonthInt;
if (endCount >= 12) { endCount = selMonthInt; }
var lst1 = months.Where(x => x.I > endCount).Select(z => z.M);
var lst2 = months.Where(x => x.I <= selMonthInt).Select(z => z.M);
var lst = lst1.Union(lst2).ToArray();
var selMonths = Enumerable.Range(0, lastMonthNumber).Select(i => new { I = (13 - lastMonthNumber + i), M = lst[i] });
Here is my problem, I have a query result set that looks like this:
id_1 - 0 - datetime - gps coordinates
id_2 - 0 - datetime - gps coordinates
id_3 - 1 - datetime - gps coordinates
id_4 - 1 - datetime - gps coordinates
id_5 - 1 - datetime - gps coordinates
id_6 - 1 - datetime - gps coordinates
id_7 - 0 - datetime - gps coordinates
id_8 - 0 - datetime - gps coordinates
id_9 - 0 - datetime - gps coordinates
id_10 - 0 - datetime - gps coordinates
id_11 - 1 - datetime - gps coordinates
id_12 - 1 - datetime - gps coordinates
id_13 - 0 - datetime - gps coordinates
id_14 - 0 - datetime - gps coordinates
This, obviusly returns a list, but I would like to have the sets with value 1 in the second column kept in a list of lists, where each set of lists is one that previously had a 0.
It would look something like this:
List1: [id_3], [...] , [id_6]
List2: [id_11], [...], [id_12]
I dont know the number of elements with 0 or 1 so this has to be generic in that sense
I'm using C# 4.5, and was thinking of using Linq to do this, instead of the old fashioned foreach.
Can anyone point me in the right direction?
I don't think there are something builtin in the framework but you can create an extention method for it :
public static class LinqHelper
{
public static IEnumerable<List<Item>> Partition(this IEnumerable<Item> source, Func<Item, bool> selector)
{
List<Item> currentList = new List<Item>();
foreach (var item in source)
{
if (selector(item))
{
currentList.Add(item);
}
else if (currentList.Count != 0)
{
yield return currentList;
currentList = new List<Item>();
}
}
if (currentList.Count != 0)
{
yield return currentList;
}
}
}
public class Item
{
public int Id { get; set; }
public int Val { get; set; }
}
void Main()
{
var list = new List<Item>(){
new Item{ Id = 1, Val = 0 },
new Item{ Id = 2, Val = 0 },
new Item{ Id = 3, Val = 1 },
new Item{ Id = 4, Val = 1 },
new Item{ Id = 5, Val = 1 },
new Item{ Id = 6, Val = 1 },
new Item{ Id = 7, Val = 0 },
new Item{ Id = 8, Val = 0 },
new Item{ Id = 9, Val = 0 },
new Item{ Id = 10, Val = 0 },
new Item{ Id = 11, Val = 1 },
new Item{ Id = 12, Val = 1 },
new Item{ Id = 13, Val = 0 },
new Item{ Id = 14, Val = 0 },
};
var result = list.Partition(i => i.Val == 1).Where(i => true).ToList();
}
If you're looking to avoid foreach, here's a way to do it with LINQ's Aggregate extension.
Given the following class:
public class SomeType
{
public int Id {get;set;}
public int Val {get;set;}
}
and having generated the following items:
var items = new List<SomeType>();
for(var i = 1; i <= 14; i++)
{
var val = 0;
if((3 <= i && i <= 6) || (11 <= i && i <= 12))
val = 1;
items.Add(new SomeType { Id = i, Val = val});
}
You can get the List of Lists of items with a value of 1 like so:
var grouped = items.Aggregate(new List<List<SomeType>>() { new List<SomeType>() },
(acc,elem) =>
{
if(elem.Val == 0 && acc.Last().Count != 0)
acc.Add(new List<SomeType>());
else if(elem.Val == 1)
acc.Last().Add(elem);
return acc;
}, acc => acc.Where(x => x.Count != 0));
int groupNum = 1;
foreach (var group in grouped)
{
Console.WriteLine($"Group {groupNum++}");
foreach (var item in group)
Console.WriteLine($"{item.Id} - {item.Val}");
}
/* output:
Group 1
3 - 1
4 - 1
5 - 1
6 - 1
Group 2
11 - 1
12 - 1
*/
This assumes that it's okay to add entries with the 1 value before a 0 value has occurred, and I'm not sure that's it's especially more readable than using a foreach, and you would likely be better off implementing an extension method that would likely use foreach anyways.
Try this solution;
var prev = 0;
List<int> bag = null;
var result = new List<List<int>>();
foreach(var item in list)
{
if(item.Val == 1)
{
if(prev == 0)
{
if(bag != null)
result.Add(bag);
bag = new List<int>();
}
bag.Add(item.Id);
}
prev = item.Val;
}
if(bag != null)
result.Add(bag);
result.ForEach(x => {
Console.WriteLine(String.Join(", ", x));
});
//3, 4, 5, 6
//11, 12
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I have the following scenario (based on an actual scenario that cannot be shown here). Two inputs
A number x
A series of positive integers given as IEnumerable<int>
Now I want to find all groups of three consecutive integers in the series that can divide x with no remainder, and one group cannot overlap the other.
For instance: My series is 1, 2, 3, 4, 5, 6 and user inputs x=24
Then my LINQ will give 1, 2, 3, but will not give 2, 3, 4 because those two groups will overlap in the series.
Now I could run a LINQ query that would basically run a "window" of 3 on the series and give me the several groups, and run a Where clause to find the group that will have all 3 numbers successfully dividing x. But every time I find a match, I need some kind of a negative feedback to tell my filter to omit the subsequent groups that will be overlapping to the matching one.
To understand it better, in the above example. I can generate several groups like
1, 2, 3
2, 3, 4
3, 4, 5,
and so on
then I could say
myWindowsOf3Numbers
.Where(w => x % w.Number1 == 0 && x % w.Number2 == 0 && x % w.Number3 == 0)
So I end up with code like this
var series = new[] { 1, 2, 3, 4, 5, 6, 7 };
var x = 24;
var windows = series.Select((number, index) => index + 2 < series.Count()? new { Number1 = series[index], Number2 = series[index+1], Number3 = series[index+2] } : null )
.Where(groups => groups != null);
var matching = windows.Where(w => x % w.Number1 == 0 && x % w.Number2 == 0 && x % w.Number3 == 0);
The above will give me 1, 2, 3 and 2, 3, 4 both, but I want only the first one of the two overlapping.
Is there any LINQ-trick to do that or I have to use foreach (Like the following code)?
private class Match
{
public int N1 { get; set; }
public int N2 { get; set; }
public int N3 { get; set; }
}
var series = new[] { 1, 2, 3, 4, 5, 6, 7 };
var x = 24;
var windows = series.Select((number, index) => index + 2 < series.Count() ? new { Number1 = series[index], Number2 = series[index + 1], Number3 = series[index + 2] } : null)
.Where(groups => groups != null);
var matches = new List<Match>();
for (var i = 0; i < (series.Count() - 2); i ++)
{
if (x % series[i] == 0 && x % series[i + 1] == 0 && x % series[i + 2] == 0)
{
matches.Add(new Match() { N1 = series[i], N2 = series[i + 1], N3 = series[i + 2] });
i += 3;
}
}
For the sake of the challenge, here is one possible "pure" LINQ solution, but frankly I'd never use something like this:
int x = 60;
var series = new [] { 1, 2, 3, 4, 5, 6 }.AsEnumerable();
var matches = series
// Create the sliding window
.Select((e, i) => new { index = i, group = series.Skip(i).Take(3).ToArray() })
// Remove the non matching
.Where(e => e.group.Length == 3 && e.group.All(v => (x % v) == 0))
// Remove the overlapping
.Aggregate(new { next = 0, result = Enumerable.Empty<int[]>() }, (prev, next) =>
next.index >= prev.next ?
new { next = next.index + 3, result = prev.result.Concat(new[] { next.group }) } :
prev).result;
Well, you said LINQ ...:
var series = new[] { 1, 2, 3, 4, 5, 6, 7 };
var x = 24;
var matchingWindows = series
.Select((number, index) => index + 2 < series.Length ?
new { Number1 = series[index], Number2 = series[index + 1], Number3 = series[index + 2], Index = index } :
null)
.Where(groups => groups != null)
.Where(w => x % w.Number1 == 0 && x % w.Number2 == 0 && x % w.Number3 == 0)
.ToList();
int lastTakenIndex = -1;
var nonOverlapping = matchingWindows.Where(w =>
{
if (lastTakenIndex >= 0)
{
if (w.Index <= lastTakenIndex + 2) return false;
lastTakenIndex = w.Index;
return true;
}
lastTakenIndex = w.Index;
return true;
}).ToList();
It uses a lastTakenIndex (= the start index of the 3-group that was last taken that matched the condition) which is modified as a side-effect during the Where filtering.
I'm sure there is a more consise way of achieving this result? However I created the following method to work out the previous quarter from the current month or DateTime passed in.
void Main()
{
var q = Helpers.PreviousQuarter();
q.Dump();
}
public class Helpers
{
public static int PreviousQuarter(DateTime? date = null)
{
var quarters = new Dictionary<int, int[]>();
quarters.Add(1, new[] { 1, 2, 3 });
quarters.Add(2, new[] { 4, 5, 6 });
quarters.Add(3, new[] { 7, 8, 9 });
quarters.Add(4, new[] { 10, 11, 12 });
foreach (var q in quarters)
{
if (q.Value.Any(m=> m == (date.HasValue ? date.Value.Month : DateTime.Now.AddMonths(-3).Month)))
return q.Key;
}
throw new ArgumentException("Could not calulate quarter.");
}
}
private int PreviousQuarter(DateTime date)
{
return (int)Math.Ceiling((double)date.AddMonths(-3).Month / (double)3);
}
This should do the trick
public static int PreviousQuarter(DateTime? date = null)
{
int month;
if (date.HasValue){
month = date.Value.Month;
} else {
month = DateTime.Now.AddMonths(-3).Month)
}
float quarter = month / 4;
return (int)Math.Ceiling(quarter +0.1);
}
Your current code is returning current quarter for a date you can modify your method like:
public static int CurrentQuarter(DateTime? date = null)
{
return (((date ?? DateTime.Now.AddMonths(-3)).Month - 1) / 3) + 1;
}
If you want to calculate previous quarter then you can use a single array instead of a dictionary like:
public static int PreviousQuarter(DateTime? date = null)
{
int[] previousQuarter = new[] {4, 4, 4, 1, 1, 1, 2, 2, 2, 3, 3, 3};
return previousQuarter[(date ?? DateTime.Now.AddMonths(-3)).Month];
}