LINQ list to sentence format (insert commas & "and") - c#
I have a linq query that does something simple like:
var k = people.Select(x=>new{x.ID, x.Name});
I then want a function or linq lambda, or something that will output the names in sentence format using commas and "ands".
{1, John}
{2, Mark}
{3, George}
to
"1:John, 2:Mark and 3:George"
I'm fine with hardcoding the ID + ":" + Name part, but it could be a ToString() depending on the type of the linq query result. I'm just wondering if there is a neat way to do this with linq or String.Format().
public string ToPrettyCommas<T>(
List<T> source,
Func<T, string> stringSelector
)
{
int count = source.Count;
Func<int, string> prefixSelector = x =>
x == 0 ? "" :
x == count - 1 ? " and " :
", ";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < count; i++)
{
sb.Append(prefixSelector(i));
sb.Append(stringSelector(source[i]));
}
string result = sb.ToString();
return result;
}
Called with:
string result = ToPrettyCommas(people, p => p.ID.ToString() + ":" + p.Name);
Why Linq?
StringBuilder sb = new StringBuilder();
for(int i=0;i<k.Count();i++)
{
sb.Append(String.Format("{0}:{1}", k[i].ID, k[i].Name);
if(i + 2 < k.Count())
sb.Append(", ");
else if(i + 1 < k.Count())
sb.Append(" and ");
}
Really, all Linq will let you do is hide the loop.
Also, make sure you do or do not want the "Oxford Comma"; this algorithm will not insert one, but a small change will (append the comma and space after every element except the last, and also append "and " after the next-to-last).
Just for fun, here’s something that really uses functional LINQ — no loop and no StringBuilder. Of course, it’s pretty slow.
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" } };
var resultAggr = list
.Select(item => item.ID + ":" + item.Name)
.Aggregate(new { Sofar = "", Next = (string) null },
(agg, next) => new { Sofar = agg.Next == null ? "" :
agg.Sofar == "" ? agg.Next :
agg.Sofar + ", " + agg.Next,
Next = next });
var result = resultAggr.Sofar == "" ? resultAggr.Next :
resultAggr.Sofar + " and " + resultAggr.Next;
// Prints 1:John, 2:Mark and 3:George
Console.WriteLine(result);
Much like the rest, this isn't better than using a string builder, but you can go (ignoring the ID, you can add it in):
IEnumerable<string> names = new[] { "Tom", "Dick", "Harry", "Abe", "Bill" };
int count = names.Count();
string s = String.Join(", ", names.Take(count - 2)
.Concat(new [] {String.Join(" and ", names.Skip(count - 2))}));
This approach pretty much abuses Skip and Take's ability to take negative numbers, and String.Join's willingness to take a single parameter, so it works for one, two or more strings.
Using the Select operation that gives you an index, this can be written as a ONE LINE extension method:
public static string ToAndList<T>(this IEnumerable<T> list, Func<T, string> formatter)
{
return string.Join(" ", list.Select((x, i) => formatter(x) + (i < list.Count() - 2 ? ", " : (i < list.Count() - 1 ? " and" : ""))));
}
e.g.
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" } }.ToList();
Console.WriteLine(list.ToAndList(x => (x.ID + ": " + x.Name)));
Improving(hopefully) on KeithS's answer:
string nextBit = "";
var sb = new StringBuilder();
foreach(Person person in list)
{
sb.Append(nextBit);
sb.Append(", ");
nextBit = String.Format("{0}:{1}", person.ID, person.Name);
}
sb.Remove(sb.Length - 3, 2);
sb.Append(" and ");
sb.Append(nextBit);
This is not pretty but will do the job using LINQ
string s = string.Join(",", k.TakeWhile(X => X != k.Last()).Select(X => X.Id + ":" + X.Name).ToArray()).TrimEnd(",".ToCharArray()) + " And " + k.Last().Id + ":" + k.Last().Name;
Y'all are making it too complicated:
var list = k.Select(x => x.ID + ":" + x.Name).ToList();
var str = list.LastOrDefault();
str = (list.Count >= 2 ? list[list.Count - 2] + " and " : null) + str;
str = string.Join(", ", list.Take(list.Count - 2).Concat(new[]{str}));
How about this?
var k = people.Select(x=>new{x.ID, x.Name});
var stringified = people
.Select(x => string.Format("{0} : {1}", x.ID, x.Name))
.ToList();
return string.Join(", ", stringified.Take(stringified.Count-1).ToArray())
+ " and " + stringified.Last();
I have refined my previous answer and I believe this is the most elegant solution yet.
However it would only work on reference types that don't repeat in the collection (or else we'd have to use different means for finding out if item is first/last).
Enjoy!
var firstGuy = guys.First();
var lastGuy = guys.Last();
var getSeparator = (Func<Guy, string>)
(guy => {
if (guy == firstGuy) return "";
if (guy == lastGuy) return " and ";
return ", ";
});
var formatGuy = (Func<Guy, string>)
(g => string.Format("{0}:{1}", g.Id, g.Name));
// 1:John, 2:Mark and 3:George
var summary = guys.Aggregate("",
(sum, guy) => sum + getSeparator(guy) + formatGuy(guy));
This can be the way you can achieve your goal
var list = new[] { new { ID = 1, Name = "John" },
new { ID = 2, Name = "Mark" },
new { ID = 3, Name = "George" }
}.ToList();
int i = 0;
string str = string.Empty;
var k = list.Select(x => x.ID.ToString() + ":" + x.Name + ", ").ToList();
k.ForEach(a => { if (i < k.Count() - 1) { str = str + a; } else { str = str.Substring(0, str.Length -2) + " and " + a.Replace("," , ""); } i++; });
Here's a method that doesn't use LINQ, but is probably as efficient as you can get:
public static string Join<T>(this IEnumerable<T> list,
string joiner,
string lastJoiner = null)
{
StringBuilder sb = new StringBuilder();
string sep = null, lastItem = null;
foreach (T item in list)
{
if (lastItem != null)
{
sb.Append(sep);
sb.Append(lastItem);
sep = joiner;
}
lastItem = item.ToString();
}
if (lastItem != null)
{
if (sep != null)
sb.Append(lastJoiner ?? joiner);
sb.Append(lastItem);
}
return sb.ToString();
}
Console.WriteLine(people.Select(x => x.ID + ":" + x.Name).Join(", ", " and "));
Since it never creates a list, looks at an element twice, or appends extra stuff to the StringBuilder, I don't think you can get more efficient. It also works for 0, 1, and 2 elements in the list (as well as more, obviously).
StringBuilder Approach
Here's an Aggregate with a StringBuilder. There's some position determinations that are made to clean up the string and insert the "and" but it's all done at the StringBuilder level.
var people = new[]
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mark" },
new { Id = 3, Name = "George" }
};
var sb = people.Aggregate(new StringBuilder(),
(s, p) => s.AppendFormat("{0}:{1}, ", p.Id, p.Name));
sb.Remove(sb.Length - 2, 2); // remove the trailing comma and space
var last = people.Last();
// index to last comma (-2 accounts for ":" and space prior to last name)
int indexComma = sb.Length - last.Id.ToString().Length - last.Name.Length - 2;
sb.Remove(indexComma - 1, 1); // remove last comma between last 2 names
sb.Insert(indexComma, "and ");
// 1:John, 2:Mark and 3:George
Console.WriteLine(sb.ToString());
A String.Join approach could have been used instead but the "and" insertion and comma removal would generate ~2 new strings.
Regex Approach
Here's another approach using regex that is quite understandable (nothing too cryptic).
var people = new[]
{
new { Id = 1, Name = "John" },
new { Id = 2, Name = "Mark" },
new { Id = 3, Name = "George" }
};
var joined = String.Join(", ", people.Select(p => p.Id + ":" + p.Name).ToArray());
Regex rx = new Regex(", ", RegexOptions.RightToLeft);
string result = rx.Replace(joined, " and ", 1); // make 1 replacement only
Console.WriteLine(result);
The pattern is simply ", ". The magic lies in the RegexOptions.RightToLeft which makes the match occur from the right and thereby makes the replacement occur at the last comma occurrence. There is no static Regex method that accepts the number of replacements with the RegexOptions, hence the instance usage.
Here's one using a slightly modified version of my answer to Eric Lippert's Challenge which is IMHO the most concise with easy to follow logic (if you're familiar with LINQ).
static string CommaQuibblingMod<T>(IEnumerable<T> items)
{
int count = items.Count();
var quibbled = items.Select((Item, index) => new { Item, Group = (count - index - 2) > 0})
.GroupBy(item => item.Group, item => item.Item)
.Select(g => g.Key
? String.Join(", ", g)
: String.Join(" and ", g));
return String.Join(", ", quibbled); //removed braces
}
//usage
var items = k.Select(item => String.Format("{0}:{1}", item.ID, item.Name));
string formatted = CommaQuibblingMod(items);
static public void Linq1()
{
var k = new[] { new[] { "1", "John" }, new[] { "2", "Mark" }, new[] { "3", "George" } };
Func<string[], string> showPerson = p => p[0] + ": " + p[1];
var res = k.Skip(1).Aggregate(new StringBuilder(showPerson(k.First())),
(acc, next) => acc.Append(next == k.Last() ? " and " : ", ").Append(showPerson(next)));
Console.WriteLine(res);
}
could be optimized by moving k.Last() computation to before the loop
public static string ToListingCommaFormat(this List<string> stringList)
{
switch(stringList.Count)
{
case 0:
return "";
case 1:
return stringList[0];
case 2:
return stringList[0] + " and " + stringList[1];
default:
return String.Join(", ", stringList.GetRange(0, stringList.Count-1))
+ ", and " + stringList[stringList.Count - 1];
}
}
This is the method is faster than the 'efficient' Join method posted by Gabe. For one and two items, it is many times faster, and for 5-6 strings, it is about 10% faster. There is no dependency on LINQ. String.Join is faster than StringBuilder for small arrays, which are typical for human-readable text. In grammar, these are called listing commas, and the last comma should always be included to avoid ambiguity. Here is the resulting code:
people.Select(x=> x.ID.ToString() + ":" + x.Name).ToList().ToListingCommaFormat();
There are ways to optimize this since it isn't very efficient, but something like this may work:
var k = people.Select(x => new {x.ID, x.Name}).ToList();
var last = k.Last();
k.Aggregate(new StringBuilder(), (sentence, item) => {
if (sentence.Length > 0)
{
if (item == last)
sentence.Append(" and ");
else
sentence.Append(", ");
}
sentence.Append(item.ID).Append(":").Append(item.Name);
return sentence;
});
Related
Remove the repeating items and return the order number
I want to remove the repeating items of a list.I can realize it whit Distinct() easily.But i also need to get the order number of the items which have been removed.I can't find any function in linq to solve the problem and finally realize it with the following code: public List<string> Repeat(List<string> str) { var Dlist = str.Distinct(); List<string> repeat = new List<string>(); foreach (string aa in Dlist) { int num = 0; string re = ""; for (int i = 1; i <= str.LongCount(); i++) { if (aa == str[i - 1]) { num = num + 1; re = re + " - " + i; } } if (num > 1) { repeat.Add(re.Substring(3)); } } return repeat; } Is there any other way to solve the problem more simple? Or is there any function in linq I missed?Any advice will be appreciated.
This query does exactly the same as your function, if I'm not mistaken: var repeated = str.GroupBy(s => s).Where(group => group.Any()) .Select(group => { var indices = Enumerable.Range(1, str.Count).Where(i => str[i-1] == group.Key).ToList(); return string.Join(" - ", group.Select((s, i) => indices[i])); }); It firstly groups the items of the original list, so that every item with the same content is in a group. Then it searches for all indices of the items in the group in the original list, so that we have all the indices of the original items of the group. Then it joins the indices to a string, so that the resulting format is similiar to the one you requested. You could also transform this statement lambda to an anonymous lambda: var repeated = str.GroupBy(s => s).Where(group => group.Any()) .Select(group => string.Join(" - ", group.Select((s, i) => Enumerable.Range(1, str.Count).Where(i2 => str[i2 - 1] == group.Key).ToList()[i]))); However, this significantly reduces performance. I tested this with the following code: public static void Main() { var str = new List<string> { "bla", "bla", "baum", "baum", "nudel", "baum", }; var copy = new List<string>(str); var repeated = str.GroupBy(s => s).Where(group => group.Any()) .Select(group => string.Join(" - ", group.Select((s, i) => Enumerable.Range(1, str.Count).Where(i2 => str[i2 - 1] == group.Key).ToList()[i]))); var repeated2 = Repeat(str); var repeated3 = str.GroupBy(s => s).Where(group => group.Any()) .Select(group => { var indices = Enumerable.Range(1, str.Count).Where(i => str[i-1] == group.Key).ToList(); return string.Join(" - ", group.Select((s, i) => indices[i])); }); Console.WriteLine(string.Join("\n", repeated) + "\n"); Console.WriteLine(string.Join("\n", repeated2) + "\n"); Console.WriteLine(string.Join("\n", repeated3)); Console.ReadLine(); } public static List<string> Repeat(List<string> str) { var distinctItems = str.Distinct(); var repeat = new List<string>(); foreach (var item in distinctItems) { var added = false; var reItem = ""; for (var index = 0; index < str.LongCount(); index++) { if (item != str[index]) continue; added = true; reItem += " - " + (index + 1); } if (added) repeat.Add(reItem.Substring(3)); } return repeat; } Which has the followin output: 1 - 2 3 - 4 - 6 5 1 - 2 3 - 4 - 6 5 1 - 2 3 - 4 - 6 5
Inside your repeat method you can use following way to get repeated items var repeated = str.GroupBy(s=>s) .Where(grp=>grp.Count()>1) .Select(y=>y.Key) .ToList();
Split the string and join all first elements then second element and so on in c#
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);
A more clean approach to sorting a string into another string
I have a string that might look something like this: "3, 7, 12-14, 1, 5-6" What i need to do is to change that into a string looking like this: "1, 3, 5, 6, 7, 12, 13, 14" I have made the following code work, but i would very much appreciated help how to do this a cleaner way with less lines of code: private string sortLanes(string lanesString) { List<string> sortedLanes = new List<string>(); if (lanesString.Contains(',') || lanesString.Contains('-')) { List<string> laneParts = lanesString.Split(',').ToList(); foreach (string lanePart in laneParts) { if (lanePart.Contains('-')) { int splitIndex = lanePart.IndexOf('-'); int lanePartLength = lanePart.Length; int firstLane = Convert.ToInt32(lanePart.Substring(0, splitIndex)); int lastLane = Convert.ToInt32(lanePart.Substring(splitIndex + 1, lanePartLength - splitIndex - 1)); while (firstLane != lastLane) { sortedLanes.Add(firstLane.ToString().Trim()); firstLane++; } sortedLanes.Add(lastLane.ToString()); } else { sortedLanes.Add(lanePart.Trim()); } } sortedLanes.Sort(); sortedLanes = sortedLanes.OrderBy(x => x.Length).ToList(); lanesString = ""; foreach (string lane in sortedLanes) { if (lanesString.Length == 0) { lanesString = lane; } else { lanesString = lanesString + ", " + lane; } } } else { return lanesString; } return lanesString; }
I would first split by the , then convert each value into either a single integer or the desired range. Take the results and reorder them and then concatenate back into a string. Something like this. string test = "3, 7, 12-14, 1, 5-6"; var items = test.Split(','); var ints = items.SelectMany(item => Expand(item)); string result = string.Join(", ", ints.OrderBy(i => i).ToArray()); private static IEnumerable<int> Expand(string str) { if (str.Contains('-')) { var range = str.Split('-'); int begin = int.Parse(range[0]); int end = int.Parse(range[1]); for (int i = begin; i <= end; i++) yield return i; } else yield return int.Parse(str); } Of course you might want to add some error checking, but I'll leave that up to you.
This will produce the wanted result (partially based on the incorrect answer from #Tigran): var parts = "3, 7, 12-14, 1, 5-6".Split(new string[] {", "}, StringSplitOptions.None).ToList(); var finalResult = new List<int>(); foreach(var item in parts) { if(item.Contains("-")) { var rangeParts = item.Split('-'); var first = int.Parse(rangeParts[0]); var second = int.Parse(rangeParts[1]); var result = Enumerable.Range(first, second - first + 1); finalResult.AddRange(result); } else { finalResult.Add(int.Parse(item)); } } var sorted = finalResult.OrderBy(i => i); var resultString = string.Join(", ", sorted);
Regex would work nicely here... static void Main() { Assert.AreEqual("1, 3, 5, 6, 7, 12, 13, 14", Transform("3, 7, 12-14, 1, 5-6")); } private static string Transform(string input) { StringBuilder sb = new StringBuilder(); foreach(Match m in new Regex(#"(?<start>\d+)(?:-(?<end>\d+))?(?:,|$)\s*").Matches(input) .OfType<Match>().OrderBy(m => int.Parse(m.Groups["start"].Value))) { int start = int.Parse(m.Groups["start"].Value); int end = !m.Groups["end"].Success ? start : int.Parse(m.Groups["end"].Value); foreach (int val in Enumerable.Range(start, end - start + 1)) sb.AppendFormat("{0}, ", val); } if (sb.Length > 0) sb.Length = sb.Length - 2;//remove trailing comma+space; return sb.ToString(); } Update If I wanted to make it confusing I'd just use a single line of code: return String.Join(", ",new Regex(#"(?<start>\d+)(?:-(?<end>\d+))?(?:,|$)\s*").Matches(input) .OfType<Match>().OrderBy(m => int.Parse(m.Groups["start"].Value)).SelectMany(m => Enumerable.Range(int.Parse(m.Groups["start"].Value), int.Parse(m.Groups["end"].Success ? m.Groups["end"].Value : m.Groups["start"].Value) - int.Parse(m.Groups["start"].Value) + 1)) .Select(i => i.ToString()).ToArray()); LoL :)
If you like Linq: string input = "3, 7, 12-14, 1, 5-6"; List<int> all = input.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(r => new { Range = r, Parts = r.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries) .Select(p => int.Parse(p)) }) .SelectMany(x => Enumerable.Range(x.Parts.First(), 1 + x.Parts.Last() - x.Parts.First())) .ToList(); Demo
C# Possible lambda in array's content declaration
I have such piece of code: string suffix = "wallpapers\\"; string extenstion = ".jpg"; string[] wallpapers; ..... void SetWallPapers() { wallpapers = new string[] { suffix + "1" + extenstion, suffix + "2" + extenstion, suffix + "3" + extenstion, suffix + "4" + extenstion, }; } Is there any variant to make lambda-declaretion in array content like: ( pseudo-code, idea only! ) wallpapers = new string[] { ( () => { for i = 1 till 4 -> suffix + i + extension; } ) } Any suggestions?
string[] wallpapers = Enumerable.Range(1, 4) .Select(i => suffix + i + extenstion) .ToArray();
string template = "wallpapers\\{0}.jpg"; string[] wallpapers = Enumerable.Range(1, 4) .Select(i => string.Format(template, i)) .ToArray();
You can do it using Linq like so: wallpapers = Enumerable.Range(1, 4) .Select(i => String.Concat(suffix, i, extenstion)) .ToArray();
No, there is not. If you're ok with generating your array at runtime, use Enumerable.Range(1, 4).Select(i => string.Format("{0}{1}{2}", suffix, i, extension)).ToArray(); PS Your code can't work anyway, you can't add int to string.
Enumerable.Range(1, 10).Select(a => "string" + a).ToArray();
Using LINQ how to split string (not on character but on index)
I wanted to split a string Input : ABCDEFGHI Output : ABC, DEF, GHI One way is by using For Loop. string str = "ABCDEFGHI"; List<string> lst = new List<string>(); string temp = ""; for(int i = 0; i < str.Length; i++) { temp = str[i].Tostring(); if((i + 1) % 3 == 0) { lst.Add(temp); temp = ""; } } string final_str = string.Join(", ", lst); But how to do that using LINQ?
And another one (without MoreLinq): var str = "ABCDEFGHI"; var tmp = str.Select((i, index) => new { i, index }) .GroupBy(g => g.index / 3, e => e.i) .Select(g => String.Join("", g)); var final_string = String.Join(", ", tmp);
With the help of MoreLinq List<string> lst = str.Batch(3).Select(s => String.Join("",s)).ToList();
using MoreLinq.Batch var result = str.Batch(3); type of result is IEnumerable>, ToArray can be used to make it IEnumerable< char[] > EDIT I forgot last join statement in the first glance var finalStr = String.Join(",",str.Batch(3).Select(x=>new String(x.ToArray())))
var str = "ABCDEFGHI"; var result = testStr.Select(s => testStr.IndexOf(s)) .Where(i => i%3 == 0) .Select(i => testStr.Substring(i,3)) .Aggregate("", (a,s) => a += s + ",");
String.Join("", str.Select((x, i) => (i + 1)%3 == 0 ? x + " " : x.ToString()))