I have two arrays of dates. I want to merge them into one array that stores each (distinct) dates and their occurrences in their parent arrays.
For example:
var today = new DateTime (year:2022, month:01, day:01);
var firstDateArray = new DateTime[] { today.AddDays(1), today.AddDays(7), today, today.AddDays(3), today };
var secondDateArray = new DateTime[] { today.AddDays(7), today, today.AddDays(3), today.AddDays(3), today.AddDays(1) };
I want to get something like this as a result.
How do I go about this please?
Here I used a tuple (DateTime, int, int) to represent the data:
List<(DateTime, int, int)> result = firstDateArray.
Concat(secondDateArray).
Distinct().
Select(x => (
x,
firstDateArray.Where(y => y == x).Count(),
secondDateArray.Where(z => z == x).Count()
)).
ToList();
You can use .GroupBy, .Select, and .Join for this:
var today = new DateTime (year:2022, month:01, day:01);
var firstDateArray = new DateTime[] { today.AddDays(1), today.AddDays(7), today, today.AddDays(3), today };
var secondDateArray = new DateTime[] { today.AddDays(7), today, today.AddDays(3), today.AddDays(3), today.AddDays(1) };
var firstGrouped = firstDateArray
.GroupBy(d => d)
.Select(g => new { Date = g.Key, Count = g.Count() });
var secondGrouped = secondDateArray
.GroupBy(d => d)
.Select(g => new { Date = g.Key, Count = g.Count() });
var joined = firstGrouped
.Join(secondGrouped, f => f.Date, s => s.Date, (f, s) => new { Date = f.Date, FirstCount = f.Count, SecondCount = s.Count })
.OrderBy(j => j.Date)
.ToList();
GroupBy basically collects items with a common key (in this case the DateTime) and then lets you iterate through the collections.
Join takes 4 arguments: the second enumerable, the key selector for the first, the key selector for the second (it uses these to facilitate the join), and the result selector to produce the result you want.
Documentation:
GroupBy
Select
Join
OrderBy
ToList
Try it online
I am probably overthinking this all and I'm lost. I'm new to C# and don't know what is the best way to solve this.
string[] input = new string { "FR_Paris", "UK_London", "UK_Bristol" };
Desirable output in console is ordered by occurrence of cities in a country and cities are sorted alphabetically.
In this situation it is:
UK 2x Bristol, London
FR 1x Paris
I'm not going to lie, this is my homework. I know how to parse the input and I think for cities has to be used a collection which can be sorted but don't know which type. I'm kind of lost when it comes to nested collections.
Please give me at least a direction.
Thanks a lot!
Try This
// Init Array
string[] input = new string[] { "FR_Paris", "UK_London", "UK_Bristol" };
//Get All Codes
List<string> Codes = new List<string>();
foreach (var CityName in input)
{
var Name = CityName.Split('_')[0]; // Get Name After _ Paris
if (!Codes.Contains(Name))
Codes.Add(va);
}
// Print
foreach (var Code in Codes.OrderBy(x => x))
{
var AllNames = input.Where(x => x.StartsWith(Code + "_")).Select(x => x.Split('_')[1]);
Console.WriteLine(Code + " " + xd.Count() + "x " + string.Join(",", AllNames.OrderBy(x => x)));
}
You can try with linq
var input = new List<string> { "FR_Paris", "UK_London", "UK_Bristol" };
var result = input.Select(x => new { Country = x.Split("_")[0], City = x.Split("_")[1] })
.GroupBy(x => x.Country)
.Select(x => $"{x.Key} {x.Count()}x {String.Join(", ", x.OrderBy(x => x.City).Select(x => x.City))} ");
foreach (var item in result)
{
Console.WriteLine(item);
}
OUTPUT
FR 1x Paris
UK 2x Bristol, London
Using LINQ:
var grouped = input
.Select(x => x.Split('_')) // Split all strings into array[2]
.GroupBy(x => x[0]) // Group by country
.OrderByDecending(x => x.Count()); // Order by number of cities
I have a string like this -
var roleDetails = "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1,24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1,76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1,B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1,CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1,E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1";
Spliting it with "," returns this -
[0] "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1"
[1] "24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1"
[2] "6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1"
[3] "76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1"
[4] "B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1"
[5] "CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1"
[6] "D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1"
[7] "E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1"
All elements contains carat (^). so spliting each element further with ^ symbol will return four element.
But I want to join all first element then all second element and then third and so on and get the result like this -
[0]: 09A880C2-8732-408C-BA09-4AD6F0A65CE9, 24B11B23-1669-403F-A24D-74CE72DFD42A, 6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6, 76B3B165-0BB4-4E3E-B61F-0C0292342CE2, B3C0CE51-00EE-4A0A-B208-98653E21AE11, CBA225BC-680C-4627-A4F6-BED401682816, D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9, E0275936-FBBB-4775-97D3-9A7D19D3E1B4
[1]: Z:WB:SELECT_DOWNLOAD:0000,Z:WB:TRAINING_SUBSCRIBER:0000, Z:WB:LIC_MGR_HOME_REDL:0000,Account Admin, Z:WB:1BENTLEY_ISA_ADMIN:0000, ReadOnly, Z:WB:MY_SELECT_CD:0000, Z:WB:LICENSE_MANAGER
[2]: Product Delivery - Download, Training Subscriber, License Manager - Home use, Account Admin, Co-Administrator, ReadOnly, Product Delivery - DVD, License Manager
[3]: 1,1,1,1,1,1,1,1
What is the quickest and simplest way of achieving this?
EDIT
This is what I tried so far -
var rolearray = roleDetails.Split(',').Select(s => s.Split('^')).Select(a => new { RoleId = a[0], RoleNme = a[1], FriendlyName = a[2], IsUserInRole = a[3] });
but again this is not returning the way I need it. But I want to join all a[0]s , then all a[1] and so on
SOLUTION:
After comparing solutions and ran it 10 times in a loop to see the performance I found solution suggested by Jamiec is taking less time. So selecting this solution.
Pure LINQ solution:
roleDetails.Split(',')
.SelectMany(x => x.Split('^').Select((str, idx) => new {str, idx}))
.GroupBy(x => x.idx)
.Select(grp => string.Join(", ", grp.Select(x => x.str)))
The easiest way to do this, is to simply do:
var split = roleDetails.Split(',')
.Select(x => x.Split('^').ToArray())
.ToArray();
You would then access the elements like a multi dimensional jagged array
Console.WriteLine(split[0][0]);
// result: 09A880C2-8732-408C-BA09-4AD6F0A65CE9
Live example: http://rextester.com/NEUVOR15080
And if you then want all the elements grouped
Console.WriteLine(String.Join(",",split.Select(x => x[0])));
Console.WriteLine(String.Join(",",split.Select(x => x[1])));
Console.WriteLine(String.Join(",",split.Select(x => x[2])));
Console.WriteLine(String.Join(",",split.Select(x => x[3])));
Live example: http://rextester.com/BZXLG67151
Here you can user Aggregate and Zip extension method of Linq.
Aggregate: Performs a specified operation to each element in a collection, while carrying the result forward.
Zip: The Zip extension method acts upon two collections. It processes each element in two series together.
var roleDetails = "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1,24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1,76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1,B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1,CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1,E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1";
var rolearray = roleDetails.Split(',')
.Select(s => s.Split('^'))
.Aggregate((s1Array, s2Array) => s1Array.Zip(s2Array, (s1, s2) => s1 + "," + s2).ToArray());
string roleDetails = "09A880C2-8732-408C-BA09-4AD6F0A65CE9^Z:WB:SELECT_DOWNLOAD:0000^Product Delivery - Download^1,24B11B23-1669-403F-A24D-74CE72DFD42A^Z:WB:TRAINING_SUBSCRIBER:0000^Training Subscriber^1,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6^Z:WB:LIC_MGR_HOME_REDL:0000^License Manager - Home use^1,76B3B165-0BB4-4E3E-B61F-0C0292342CE2^Account Admin^Account Admin^1,B3C0CE51-00EE-4A0A-B208-98653E21AE11^Z:WB:1BENTLEY_ISA_ADMIN:0000^Co-Administrator^1,CBA225BC-680C-4627-A4F6-BED401682816^ReadOnly^ReadOnly^1,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9^Z:WB:MY_SELECT_CD:0000^Product Delivery - DVD^1,E0275936-FBBB-4775-97D3-9A7D19D3E1B4^Z:WB:LICENSE_MANAGER:0000^License Manager^1";
var RawItems = roleDetails.Split(',').Select(x=> x.Split('^'));
var Items1 = RawItems.Select(x=> x.ElementAt(0));
var Items2 = RawItems.Select(x=> x.ElementAt(1));
var Items3 = RawItems.Select(x=> x.ElementAt(2));
var Items4 = RawItems.Select(x=> x.ElementAt(3));
If you don't like the LINQ solutions, here's a solution without:
var result = new string[4];
var i = 0;
foreach(var line in roleDetails.Split(','))
foreach(var piece in line.Split('^'))
result[i++ % 4] += (i <= 4 ? "" : ",") + piece;
Basically, you split on commas and carets, and foreach on each, using a counter that tells us which array element to concatenate in, and whether to use a comma separator or not.
If your initial string is much bigger than in this example, consider first creating an array of StringBuilders first as these are better performing with concatenations:
var stringBuilders = new StringBuilder[4];
var result = new string[4];
var i = 0;
for (var i = 0; i < 4; i++)
stringBuilders[i] = new StringBuilder();
foreach(var line in roleDetails.Split(','))
foreach(var piece in line.Split('^'))
stringBuilders[i++ % 4].Append((i <= 4 ? "" : ",") + piece);
foreach (var stringBuilder in stringBuilders)
result[i++ % 4] = stringBuilder.ToString();
One more LINQ solution. But not as clean as #Pavel's:
string a = "", b = "", c = "", d = "";
roleDetails.Split(',').ToList().ForEach(x =>
{
a += x.Split('^')[0] + ',';
b += x.Split('^')[1] + ',';
c += x.Split('^')[2] + ',';
d += x.Split('^')[3] + ',';
});
MessageBox.Show(a.Trim(','));
MessageBox.Show(b.Trim(','));
MessageBox.Show(c.Trim(','));
MessageBox.Show(d.Trim(','));
OUTPUT:
a = 09A880C2-8732-408C-BA09-4AD6F0A65CE9,24B11B23-1669-403F-A24D-74CE72DFD42A,6A4A6543-DB9F-46F2-B3C9-62D69D28A0B6,76B3B165-0BB4-4E3E-B61F-0C0292342CE2,B3C0CE51-00EE-4A0A-B208-98653E21AE11,CBA225BC-680C-4627-A4F6-BED401682816,D80CF5CF-CB6E-4424-9D8F-E29F96EBD4C9,E0275936-FBBB-4775-97D3-9A7D19D3E1B4
b = Z:WB:SELECT_DOWNLOAD:0000,Z:WB:TRAINING_SUBSCRIBER:0000,Z:WB:LIC_MGR_HOME_REDL:0000,Account Admin,Z:WB:1BENTLEY_ISA_ADMIN:0000,ReadOnly,Z:WB:MY_SELECT_CD:0000,Z:WB:LICENSE_MANAGER:0000
c = Product Delivery - Download,Training Subscriber,License Manager - Home use,Account Admin,Co-Administrator,ReadOnly,Product Delivery - DVD,License Manager
d = 1,1,1,1,1,1,1,1
Fairly clean and fast...
var sets = new[]
{
new List<string>(),
new List<string>(),
new List<string>(),
new List<string>(),
};
foreach (var role in roleDetails.Split(','))
{
var details = role.Split('^');
sets[0].Add(details[0]);
sets[1].Add(details[1]);
sets[2].Add(details[2]);
sets[3].Add(details[3]);
}
var lines = sets.Select(set => string.Join(",", set)).ToArray();
... little nuts to understand and doesn't really save anything on performance ...
var ret = roleDetails.Split(',')
.Aggregate(seed: new { SBS = new[] { new StringBuilder(), new StringBuilder(),
new StringBuilder(), new StringBuilder(), },
Start = true },
func: (seed, role) =>
{
var details = role.Split('^');
if (seed.Start)
{
seed.SBS[0].Append(details[0]);
seed.SBS[1].Append(details[1]);
seed.SBS[2].Append(details[2]);
seed.SBS[3].Append(details[3]);
return new
{
seed.SBS,
Start = false,
};
}
else
{
seed.SBS[0].Append(',').Append(details[0]);
seed.SBS[1].Append(',').Append(details[1]);
seed.SBS[2].Append(',').Append(details[2]);
seed.SBS[3].Append(',').Append(details[3]);
return seed;
}
},
resultSelector: result => result.SBS.Select(sb => sb.ToString()).ToArray()
);
You can use Tuple here
var roles = roleDetails.Split(',')
.Select(x => x.Split('^'))
.Where(x=>x.Length==4)
.Select(x=>
new Tuple<string, string, string, string>(x[0], x[1], x[2], x[3]))
.ToList();
var item1 = string.Join(",", roles.Select(x=>x.Item1).ToArray());
var item2 = string.Join(",", roles.Select(x => x.Item2).ToArray());
var item3 = string.Join(",", roles.Select(x => x.Item3).ToArray());
var item4 = string.Join(",", roles.Select(x => x.Item4).ToArray());
Your attempt tries to do everything in a single line, which is making it much harder for you to understand what's happening.
You're already using all the tools you need (Select() and Split()). If you make your code more readable by separating everything into separate lines of code, then it becomes much easier to find your way:
//Your data string
string myDataString = "...";
//Your data string, separated into a list of rows (each row is a string)
var myDataRows = myDataString.Split(',');
//Your data string, separated into a list of rows (each row is a STRING ARRAY)
var myDataRowsAsStringArrays = myDataRows.Select(row => row.Split('^'))
And now, all you need to do is retrieve the correct data.
var firstColumnValues = myDataRowsAsStringArrays.Select(row => row[0]);
var secondColumnValues = myDataRowsAsStringArrays.Select(row => row[1]);
var thirdColumnValues = myDataRowsAsStringArrays.Select(row => row[2]);
var fourthColumnValues = myDataRowsAsStringArrays.Select(row => row[3]);
And if you so choose, you can join the values into a single comma separated string:
var firstColumnString = String.Join(", ", firstColumnValues);
var secondColumnString = String.Join(", ", secondColumnValues);
var thirdColumnString = String.Join(", ", thirdColumnValues);
var fourthColumnString = String.Join(", ", fourthColumnValues);
I have two arrays of student names and test scores.
Each array contains only distinct students (no duplicates) and is structured such that arrStudentGroup1[0][0] = "Bob" and arrStudentGroup1[0][1] = "98".
Given two arrays is it possible to use Intersect to create a third array of students that exists in both arrStudentGroup1 and arrStudentGroup2?
I'd like the third array to have their names and test scores. How do I do this?
If you want just the names students that are in both groups and not the associated test scores, just intersect on the name array element:
var students1 = arrStudentGroup1.Select(group => group[0]);
var students2 = arrStudentGroup2.Select(group => group[0]);
var studentsInBoth = students1.Intersect(students2);
If you also want the associated test scores you'll need to implement an IEqualityComparer<T> that compares the first element of each array.
If you want the associated test scores then join the two arrays:
var intersection = from s1 in arrStudentGroup1
join s2 in arrStudentGroup2 on s1[0] equals s2[0]
select new {Name = s1[0], Score1 = s1[1], Score2 = s2[1]}
foreach (var item in intersection)
{
Console.Writeline("{0}: s1={1}, s2={2}", Name, Score1, Score2);
}
First zip (in the general sense, rather than the Zip method) the arrays into a structure that groups students with scores:
Given:
string[][] arrStudentGroup1 = new string[][]{new string[]{"Bob","98"}, new string[]{"Alice","98"}, new string[]{"Charles","78"}, new string[]{"Dariah","99"}};
string[][] arrStudentGroup2 = new string[][]{new string[]{"Bob","98"}, new string[]{"Fiona","98"}, new string[]{"Eve","78"}, new string[]{"Dariah","99"}};
Then:
var zipped1 = arrStudentGroup1.Select(student => new {Name = student[0], Score = student[1]});
var zipped2 = arrStudentGroup2.Select(student => new {Name = student[0], Score = student[1]});
Now get the intersection. Note that if the same student name was in one but with a different score, it would not count as an intersection. That can be dealt with too, but I'm interpreting your question as not wanting that case. Let me know if I read you wrong:
var inter = zipped1.Intersect(zipped2);
Now, you can ideally work with this anyway, or even with new {Name = student[0], Score = int.Parse(student[1])} above and have a number instead of a string (more useful in most cases), which frankly is nicer than dealing with an array of arrays, along with being more typesafe. Still, if you really want it in the same format string[] format:
var interArray = inter.Select(st => new string[]{st.Name, st.Score});
And if you really, really want the whole thing in the same string[][] format:
var interArrays = interArray.ToArray();
Or for the one-line wonder (less good readability mostly, but sometimes it's nice to put a query on one line if there's other things going on in the same method):
var interArrays = arrStudentGroup1
.Select(student => new {Name = student[0], Score = student[1]})
.Intersect(
arrStudentGroup2
.Select(student => new {Name = student[0], Score = student[1]})
).Select(st => new string[]{st.Name, st.Score}).ToArray()
Output:
{"Bob", "98"},{"Dariah", "99"}
Edit: Alternatively, define an IEqualityComparer<string[]> like:
public class StudentComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if(ReferenceEquals(x, y))
return true;
if(x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] arr)
{
return arr == null ? 0 : arr.Select(s => s == null ? 0 : s.GetHashCode()).Aggregate((x, y) => x ^ y);
}
}
Then just use it directly:
var intersection = arrStudentGroup1.Intersect(arrStudentGroup2, new StudentComparer());
Gives the same output. Simpler really, but my instincts upon seeing arrays being used as objects was to get it into a real object as soon as I can, and really, it's not a bad instinct - it can make much else easier too.
Well, you could do somthing like this,
var studentsInGroup1 = arrStudentGroup1.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInGroup2 = arrStudentGroup2.Select(s => new
{
Name = s[0],
Score = s[1]
});
var studentsInBothGroups = studentsInGroup1.Join(
studentsInGroup2,
s => s.Name,
s => s.Name,
(one, two) => new
{
Name = one.Name,
Scores = new[] { one.Score, two.Score }
});
Which should give you a handy anonymous type you can access like this.
foreach(var student in studentsInBothGroups)
{
var Name = student.Name;
var Group1Score = student.Scores[0];
var Group2Score = student.Scores[1];
}