In order to generate a graph using d3 I need to convert my list of time entries to several arrays.
I store my data in a list of work records per day per staff
I need to be able to get an array of all days, and then a array each per member of staff.
So lets say staff x has 3.5h against 01/1/19 and 4.5h against 03/1/19
Staff y has 6h agaist 2/1/19
I'd expect my arrays to look as following:
Dates[1/1/19, 2/1/19, 3/1/19]
X[3.5,0,4.5]
Y[0,6,0]
Some of my code is:
public IEnumerable<TicketWorkRecord> TimeByDateByStaff { get; set; }
public class TicketWorkRecord
{
public DateTime Date { get; set; }
public decimal TimeSpent { get; set; }
}
Assuming you have a class called StaffMember that looks like this:
public class StaffMember
{
public IEnumerable<TicketWorkRecord> TimeByDateByStaff { get; set; }
// Other properties
}
And after adding the following constructor to your TicketWorkRecord class:
public TicketWorkRecord(DateTime date, decimal timeSpent)
{
Date = date;
TimeSpent = timeSpent;
}
Let's create a dummy data for X and Y staff members:
StaffMember X = new StaffMember
{
TimeByDateByStaff = new List<TicketWorkRecord>()
{
new TicketWorkRecord(DateTime.Today.Date, 3.5M),
new TicketWorkRecord(DateTime.Today.Date.AddDays(2), 4.5M)
}
};
StaffMember Y = new StaffMember
{
TimeByDateByStaff = new List<TicketWorkRecord>()
{ new TicketWorkRecord(DateTime.Today.Date.AddDays(1), 6M) }
};
var staffMembers = new List<StaffMember>() { X, Y };
Now, you can construct your desired 3 arrays using the following code:
var dates = staffMembers.SelectMany(s => s.TimeByDateByStaff)
.Select(t => t.Date)
.Distinct()
.OrderBy(d => d).ToArray();
var xTimes = dates.Select(d => X.TimeByDateByStaff
.FirstOrDefault(t => t.Date == d)?.TimeSpent ?? 0).ToArray();
var yTimes = dates.Select(d => Y.TimeByDateByStaff
.FirstOrDefault(t => t.Date == d)?.TimeSpent ?? 0).ToArray();
To test it:
Console.WriteLine("Dates: " + string.Join(",", dates.Select(d => d.ToShortDateString())));
Console.WriteLine("xTimes: " + string.Join(",", xTimes));
Console.WriteLine("yTimes: " + string.Join(",", yTimes));
Output:
Dates: 12/01/2019,13/01/2019,14/01/2019
xTimes: 3.5,0,4.5
yTimes: 0,6,0
If TicketWorkRecord has a property specifying which staff member it is (X or Y), then this would be pretty straight forward using LINQ:
var dates = TimeByDateByStaff.Select(x => x.Date.ToString("MM/dd/yy")).ToArray();
var staffXTimeSpent = TimeByDateByStaff.Select(x => x.StaffMember == "X" ? x.TimeSpent : 0M).ToArray();
var staffYTimeSpent = TimeByDateByStaff.Select(x => x.StaffMember == "Y" ? x.TimeSpent : 0M).ToArray();
Alternatively, if the exact staff members aren't known at compile time then you can get the time entries by staff member at runtime:
var timeSpentByStaffMembers = TimeByDateByStaff
.Select(x => x.StaffMember)
.Distinct()
.ToDictionary(
key => key,
value => TimeByDateByStaff.Select(x => x.StaffMember == value ? x.TimeSpent : 0M).ToArray());
With the following data:
var TimeByDateByStaff = new List<TicketWorkRecord>
{
new TicketWorkRecord
{
Date = new DateTime(2019, 1, 1),
TimeSpent = 3.5M,
StaffMember = "X"
},
new TicketWorkRecord
{
Date = new DateTime(2019, 2, 1),
TimeSpent = 6M,
StaffMember = "Y"
},
new TicketWorkRecord
{
Date = new DateTime(2019, 3, 1),
TimeSpent = 4.5M,
StaffMember = "X"
}
};
The LINQ statements above produce the following output:
If i understand you correctly, you want to split your list of objects into individuals fields arrays.
If yes, Lets say you have the following list
List<Object> ObjectsList = ObjectsList;
string[] ExtractDates = ObjectsList.Select(x=>x.Date).ToArray();
double[] TimeSpent = ObjectsList.Select(x=> x.TimeSpent).ToArray();
and so forth, you can apply where condition to filter based on members
Related
I have a list of the List of string that is Currency Code.
var currencyCode = new List<string>() { "USD", "SGD", "KWD", "BHD", "LYD" };
And i Have another complex object.
var rate = new List<Rate>()
{
new Rate() { CurrencyName = "USD (SMALL)",CurrencyCode = "USD SMALL",BranchName="Branch1"},
new Rate() { CurrencyName = "SGD BIG",CurrencyCode = "SGD BIG",BranchName="Branch1"},
new Rate() { CurrencyName = "KUWAIT DINAR",CurrencyCode = "KWD",BranchName="Branch1"},
new Rate() { CurrencyName = "USD BIG (100,50)",CurrencyCode = "USD BIG",BranchName="Branch1"},
new Rate() { CurrencyName = "USD MEDIUM (10,20)",CurrencyCode = "USD MEDIUM",BranchName="Branch1"},
};
I will have the matched currency in the below list:
var matchedCurrency = from c in rate
where currency.Any(w => c.CurrencyCode.Contains(w))
select c;
What i wanted is that the matching currency list should be in grouped, grouped by currency code.
I tried by the following way but it did not worked.
var Grp = rate.GroupBy(item => currency.Any(w => item.CurrencyCode.Contains(w)))
.Select(group => new
{
group.Key,
DataList = group.ToList()
});
I don't get i am actually missing. I have tried by various ways.
I know i can loop through the rate and push into another object. But that does not look nice i wanted to do this by using Linq. But i could not achieve the point.
Output will be displayed with this object:
public class CurrencyMap
{
public string Code { get; set; }
public List<Currency> currency { get; set; }
}
public class Currency
{
public string CurrencyName { get; set; }
public string CurrencyCode { get; set; }
public string BranchName { get; set; }
}
enter code here
EDIT:
I missed the things at first but i also need to have the empty list if the matching code was not found in the rate.
In Rate there is not the matching list for "BHD", "LYD". But i also need to have the empty list with the code "BHD", "LYD"
First select the matching currency code, then group by the selected code.
var groupedRates = rate
.Select(r => new
{
rate = r,
code = currencyCode.FirstOrDefault(c => r.CurrencyCode.Contains(c))
})
.GroupBy(x => x.code, x => x.rate); //maybe you don't want to throw away the resolved code like I do in the element selector...
Edit: I guess I was a bit to focused on the grouping aspect. Since you want to include all currency codes and mentioned a specific output structure, forget about grouping and just select your result:
var groupedRatesList = currencyCode
.Select(c => new CurrencyMap
{
Code = c,
currency = rate
.Where(x => x.CurrencyCode.Contains(c))
.Select(x => new Currency
{
BranchName = x.BranchName,
CurrencyCode = x.CurrencyCode, // or maybe you want to insert c here?
CurrencyName = x.CurrencyName
})
.ToList()
})
.ToList();
It is a rather hacky approach but you could use Regex.Match to achieve this. The basic idea is that you need the value from currencyCode as the key for your grouping.
This can be returned by a sucessfull match with regex. The property Match.Value will contain the string value for the key
Disclaimer: Unfortunately all negative matches will be return also as empty groups. You would need to filter then the empty groups out:
var result = currencyCode.SelectMany
(
x=> rate.Where(r=> r.CurrencyCode.Contains(x))
.GroupBy(r=> Regex.Match(r.CurrencyCode, x).Value)
).Where(x=> !string.IsNullOrWhiteSpace(x.Key));
Actually it works also without regex:
var result = rate.GroupBy(r => currencyCode.FirstOrDefault(c=> r.CurrencyCode.Contains(c)))
.Where(x=> !string.IsNullOrWhiteSpace(x.Key));
Disclaimer 2: Like all pattern matching it will lead to problems if you have ambiguous patterns. If a CurrencyCode value contains more than 1 of the abriviations ( may be inside the word ) you can get non sensical results/ or double entries.
Although I found it to be an intriguing problem to solve with linq, personally I would refrain from this approach. If I would have to return to this code after 9 months to maintain it, I would be way more happy to read this:
Dictionary<string,IEnumerable<Rate>> groupedSet = new Dictionary<string, IEnumerable<Rate>>();
foreach (var key in currencyCode)
{
IEnumerable<Rate> _result = rate.Where(x => x.CurrencyCode.Contains(key));
if (_result.Any())
{
groupedSet.Add(key, _result);
}
}
than to start remembering what da hack I wrote back then and what I might have thought of when I wrote it back then....
Not the best way since this approach assumes you have fixed length of Currency but you can try this:-
int currencyLength = currencyCode.First().Length;
var result = rate.Where(x => currencyCode.Any(z => x.CurrencyCode.Contains(z)))
.GroupBy(x => x.CurrencyCode.Substring(0, currencyLength))
.Select(x => new
{
Currency = x.Key,
List = x.ToList()
});
Fiddle
Try this;
var currencyCodes = new List<string>() { "USD", "SGD", "KWD", "BHD", "LYD" };
var currencies = new List<Currency>()
{
new Currency() { CurrencyName = "USD (SMALL)",CurrencyCode = "USD SMALL",BranchName="Branch1"},
new Currency() { CurrencyName = "SGD BIG",CurrencyCode = "SGD BIG",BranchName="Branch1"},
new Currency() { CurrencyName = "KUWAIT DINAR",CurrencyCode = "KWD",BranchName="Branch1"},
new Currency() { CurrencyName = "USD BIG (100,50)",CurrencyCode = "USD BIG",BranchName="Branch1"},
new Currency() { CurrencyName = "USD MEDIUM (10,20)",CurrencyCode = "USD MEDIUM",BranchName="Branch1"},
};
List<CurrencyMap> maps = currencies.Select(c => new
{
Currency = c,
FoundCode = currencyCodes.FirstOrDefault(code => c.CurrencyCode.Contains(code))
})
.Where(o => o.FoundCode != null)
.GroupBy(o => o.FoundCode)
.Select(grp => new CurrencyMap() { Code = grp.Key, Currencies = grp.Select(o => o.Currency).ToList() })
.ToList();
Let's say I have following data:
Time Status
10:00 On
11:00 Off
12:00 Off
13:00 Off
14:00 Off
15:00 On
16:00 On
How could I group that using Linq into something like
[On, [10:00]], [Off, [11:00, 12:00, 13:00, 14:00]], [On, [15:00, 16:00]]
Create a GroupAdjacent extension, such as the one listed here.
And then it's as simple as:
var groups = myData.GroupAdjacent(data => data.OnOffStatus);
You could also do this with one Linq query using a variable to keep track of the changes, like this.
int key = 0;
var query = data.Select(
(n,i) => i == 0 ?
new { Value = n, Key = key } :
new
{
Value = n,
Key = n.OnOffFlag == data[i - 1].OnOffFlag ? key : ++key
})
.GroupBy(a => a.Key, a => a.Value);
Basically it assigns a key for each item that increments when the current item does not equal the previous item. Of course this assumes that your data is in a List or Array, otherwise you'd have to try a different method
Here is a hardcore LINQ solution by using Enumerable.Zip to compare contiguous elements and generate a contiguous key:
var adj = 0;
var t = data.Zip(data.Skip(1).Concat(new TimeStatus[] { null }),
(x, y) => new { x, key = (x == null || y == null || x.Status == y.Status) ? adj : adj++ }
).GroupBy(i => i.key, (k, g) => g.Select(e => e.x));
It can be done as.
Iterate over collection.
Use TakeWhile<Predicate> condition is text of first element of collection On or Off.
Iterate over the subset of from point one and repeat above step and concatenate string.
Hope it helps..
You could parse the list and assign a contiguous key e.g define a class:
public class TimeStatus
{
public int ContiguousKey { get; set; }
public string Time { get; set; }
public string Status { get; set; }
}
You would assign values to the contiguous key by looping through, maintaining a count and detecting when the status changes from On to Off and so forth which would give you a list like this:
List<TimeStatus> timeStatuses = new List<TimeStatus>
{
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "10:00"},
new TimeStatus { ContiguousKey = 1, Status = "On", Time = "11:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "12:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "13:00"},
new TimeStatus { ContiguousKey = 2, Status = "Off", Time = "14:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "15:00"},
new TimeStatus { ContiguousKey = 3, Status = "On", Time = "16:00"}
};
Then using the following query you can extract the Status and grouped Times:
var query = timeStatuses.GroupBy(t => t.ContiguousKey)
.Select(g => new { Status = g.First().Status, Times = g });
I have a C# list items as follows-
List<MyClass> All_Items = GetListItems();
GetListItems() returns the result as follows-
Category StartDate EndDate
AA 2008-05-1
AA 2012-02-1
BB 2009-09-1
BB 2010-08-1
CC 2009-10-1
Using LINQ on All_Items, I want to update EndDate column in a way that if
If the current Category's StartDate is less than the Start Date of next bigger date item within same Category then use one less day than that of bigger date.
If there is no bigger date remaining then update to 2099-12-31
Final result is as follows-
Category StartDate EndDate
AA 2008-05-1 2012-01-31
AA 2012-02-1 2099-12-31
BB 2009-09-1 2010-07-31
BB 2010-08-1 2099-12-31
CC 2009-10-1 2099-12-31
I can only think of getting it done using too many loops. What is the better option?
Try this code. It Loops over all items and selects the next bigger item.StartDate for the same category.
If such an item is not available it sets you default date.
I couldn't Test the code as I'm writing on my mobile, so any correction is welcome.
foreach(var item in All_Items)
{
var nextItem = (from i in All_Items
where i != null &&
i.Category == item.Category &&
i.StartDate > item.StartDate
orderby i.StartDate
select i).FirstOrDefault();
item.EndDate = nextItem != null ? nextItem.StartDate.AddDays(-1) : new DateTime(2099,12,31);
}
LINQ is not good for processing dependencies between elements of a sequence, and for sure is not intended for updating.
Here is the simple and efficient way to achieve the goal:
var groups = All_Items.OrderBy(item => item.StartDate).GroupBy(item => item.Category);
foreach (var group in groups)
{
MyClass last = null;
foreach (var item in group)
{
if (last != null) last.EndDate = item.StartDate.AddDays(-1);
last = item;
}
last.EndDate = new DateTime(2099, 12, 31);
}
So we use LINQ just to order the elements by StartDate and group the result by Category (which preserves the ordering inside each group). Then simply iterate the LINQ query result and update the EndDate accordingly.
You can select dates for each category and put it into dictionary to save time later.
Then you just go through all your items and check if the start date less than next in category or not, according to you requirements.
Here it is:
var categoryDictionary = All_Items
.GroupBy(i => i.Category)
.ToDictionary(
g => g.Key,
g => g.Select(i => i.StartDate));
var defaultDate = DateTime.Parse("2099-12-31");
foreach (var item in All_Items)
{
var nextDateInCategory = categoryDictionary[item.Category]
.Where(i => i > item.StartDate)
.OrderBy(i => i)
.FirstOrDefault();
item.EndDate =
nextDateInCategory != default(DateTime)
? nextDateInCategory.AddDays(-1)
: defaultDate;
}
Let's assume your MyClass looks something like this:
public class MyClass
{
public string Category { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Here is how you can do it, see the comments in the code for an explanation.
IEnumerable<MyClass> All_Items = new List<MyClass>
{
new MyClass { Category = "AA", StartDate = new DateTime(2008, 5, 1) },
new MyClass { Category = "AA", StartDate = new DateTime(2012, 2, 1) },
new MyClass { Category = "BB", StartDate = new DateTime(2009, 9, 1) },
new MyClass { Category = "BB", StartDate = new DateTime(2010, 8, 1) },
new MyClass { Category = "CC", StartDate = new DateTime(2009, 10, 1) }
}
// Group by category
.GroupBy(c => c.Category)
// Colapse the groups into a single IEnumerable
.SelectMany(g =>
{
// Store the already used dates
List<DateTime> usedDates = new List<DateTime>();
// Get a new MyClass that has the EndDate set, from each MyClass in the category
return g.Select(c =>
{
// Get all biggerDates that were not used already
var biggerDates = g.Where(gc => gc.StartDate > c.StartDate && !usedDates.Any(ud => ud == gc.StartDate));
// Set the endDate to the default one
DateTime date = new DateTime(2099, 12, 31);
// If a bigger date was found, mark it as used and set the EndDate to it
if (biggerDates.Any()) {
date = biggerDates.Min(gc => gc.StartDate).AddDays(-1);
usedDates.Add(date);
}
return new MyClass
{
Category = c.Category,
StartDate = c.StartDate,
EndDate = date
};
});
});
In a single LINQ statement (maxEndDate is 2099-12-31):
All_Items.GroupBy(category => category.Category).Select(key =>
{
var maxCategoryStartDate = key.Max(value => value.StartDate);
return key.Select(v => {
if (DateTime.Equals(v.StartDate, maxCategoryStartDate))
{
v.EndDate = maxEndDate;
}
else
{
v.EndDate = maxCategoryStartDate - TimeSpan.FromDays(1);
}
return v;
});
}
).SelectMany(x => x);
I have a little problem with creating a complex Linq query. I have the following tables:
Activities
----------
- Date : DateTime
- ProjectId : int
Projects
--------
- ProjectId
- ProjectNumber
I need to construct a query that returns a dictionary with the distinct years within the Activities table as keys. The value should be another dictionary containing all distinct months within the current distinct year as keys and then for each distinct month, I need a list of strings containing all the project numbers for that month.
So I would end up with something like this:
- 2014 //First distinct year
- 1 //January
- Contoso-2014-01 //Project number
- 3 //March
- IBM-2014-06 //Project number
- 2016 //Second distinct year
- 4 //April
- HP-2016-02 //Project number
Basically, we would have a dictionary containing two keys: 2014 and 2016
The values for the key 2014 would be a Dictionary with two KeyValuePairs. One with the key 1 and another with the key 3. Key 1 would have a list of strings as it's values containing the project number "Contoso-2014-01" and key 3 would contain "IBM-2014-06". And so on...
So now to my question: Is it even possible to query a database and get a return type structure like this? If yes, how can I achieve this?
Considering you have classes such as these
public class Activities
{
public DateTime Date { get; set; }
public Project Project { get; set; }
}
public class Project
{
public int ProjectId { get; set; }
public string ProjectNumber { get; set; }
}
Then I think this should work
public static void GetData()
{
var mainDic = new Dictionary<int, Dictionary<int, List<string>>>();
List<Activities> acts = new List<Activities>(); // Your database context.
acts.Select(x => x.Date.Year).Distinct().ToList().ForEach(
year =>
{
var yearlyDic = new Dictionary<int, List<string>>();
acts.Where(x => x.Date.Year == year).Select(x => x.Date.Month).Distinct().ToList().ForEach(
month =>
{
var projects = acts.Where(x => x.Date.Year == year && x.Date.Month == month)
.Select(x => x.Project.ProjectNumber).ToList();
yearlyDic.Add(month, projects);
});
mainDic.Add(year, yearlyDic);
});
}
I am assuming you are using SQL Server.. Here filtering Year part and Month part in the db query itself.
select YEAR(ac.[Date]) as projectYear, MONTH(ac.[Date]) as projectMonth ,pr.ProjectNumber Info from Activities ac join Projects pr on ac.ProjectId=pr.ProjectId
class ProjectActivity
{
public int Year { get; set; }
public int Month { get; set; }
public string ProjectNumber { get; set; }
public static List<ProjectActivity> GetProjectActivities()
{
//You can use above query and construct the list.
var sampleProjectActivities = new List<ProjectActivity>();
var projActivitySamp1 = new ProjectActivity()
{
Year = 2014,
Month = 1,
ProjectNumber = "Contoso-2014-01"
};
sampleProjectActivities.Add(projActivitySamp1);
var projActivitySamp2 = new ProjectActivity()
{
Year = 2014,
Month = 3,
ProjectNumber = "Contoso-2014-03"
};
sampleProjectActivities.Add(projActivitySamp2);
var projActivitySamp3 = new ProjectActivity()
{
Year = 2016,
Month = 4,
ProjectNumber = "HP-2016-02"
};
sampleProjectActivities.Add(projActivitySamp3);
var projActivitySamp4 = new ProjectActivity()
{
Year = 2016,
Month = 4,
ProjectNumber = "AnotherHP-2016-04"
};
sampleProjectActivities.Add(projActivitySamp4);
return sampleProjectActivities;
}
}
And call the code like this
var sampleProjectActivities = ProjectActivity.GetProjectActivities();
var result = sampleProjectActivities.GroupBy(projectActivity => projectActivity.Year)
.ToDictionary(k => k.Key,
v =>
{
return v.GroupBy(val => val.Month).ToDictionary(a => a.Key, b => b.Select(x => x.ProjectNumber).ToArray());
});
Assuming that your classes are named as you have mentioned (in the tables)..
This lambda should give you the dictionary that you need..
var dictionary = acts.GroupBy(activity => activity.Date.Year) // gives you year-wise groups
.ToDictionary(yearGroup => yearGroup.Key,
yearGroup => yearGroup.ToDictionary(activity => activity.Date.Month, // gives you month-wise groups
activity => yearGroup.Where(a => a.Date.Month == activity.Date.Month)
.Select(a => a.Project.ProjectNumber)
.ToList() // all the project numbers under this year and month
));
This seems to be the most straight-forward way to go:
Dictionary<int, Dictionary<int, string>> query =
(
from a in activities
join p in projects on a.ProjectId equals p.ProjectId
group new
{
a.Date.Month,
p.ProjectNumber,
} by a.Date.Year into gaps
select new
{
gaps.Key,
Value = gaps.ToDictionary(x => x.Month, x => x.ProjectNumber),
}
).ToDictionary(x => x.Key, x => x.Value);
i have this query:
DbQuery<Logs> logs = context.GetQuery<Logs>();
var MessageLogs =
logs.Where(
s =>
s.DATE == date.Date
.GroupBy(s => new {s.DATE, s.ID})
.Select(
g => new {Date = g.Key.DATE, SID = g.Key.ID, Count = g.Count()})
.GroupBy(x => x.SID, x => new {x.Date, x.Count});
and I have these two classess:
public class Data
{
public Values[] Val { get; set; }
public string Key { get; set; }
}
and this:
public class Values
{
public string type1 { get; set; }
public string type2 { get; set; }
}
all i want to do is using that query to return type of Data.
key in class Data is SID and list of values should be counts and date as type1 and type2.
i know i can do this with anonymous type but i dont know how, i tried many ways but all of them was wrong.
EDIT:
i have this query
logs.Where(
s =>
s.DATE == date.Date
.GroupBy(s => new {s.DATE, s.ID})
.Select(
g => new {Date = g.Key.DATE, SID = g.Key.ID, Count = g.Count()})
this query returns something like this:
key date count
----------------------------
1021 2012 1
1021 2013 5
1022 2001 10
1023 2002 14
what i want is base on each id a list of values
in fact return type should be type of Data which this ids are key fore example
key=1021 and Values[] should be type1=2012, type2=1 and type1=2013, type2=5
Given that your current query returns elements with key/date/count, it sounds like you probably just want:
var result = query.GroupBy(
x => x.Key,
(key, rows) => new Data {
Key = key,
Val = rows.Select(r => new Values { type1 = r.Date, type2 = r.Count })
.ToArray();
});
Basically this overload takes:
A source
A key selector
A transformation from a key and matching rows to a result element (an instance of Data in your case)