C# Possible lambda in array's content declaration - c#

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();

Related

How to combine multiple string list with separator

I have three string list, the purpose is combine these list to a single string with separator.
List<string> list1=new List<string>{"A","B","C"};
List<string> list2=new List<string>{"=","<", ">"};
List<string> list3=new List<string>{"1","2","3"};
The final output is like following:
A=1 AND B<2 AND C>3
Is there any easy way to generate the final string? I used for loop, but it seems to be ugly. I know C# string has Join method to combine an array with separator. How to combine multiple arrays with separator?
Below is my code:
StringBuilder str = new StringBuilder();
for(int i=0; i< list1.count; i++)
{
str.AppendFormat("{0}{1}{2} AND ", list1[i], list2[i], list3[i]);
}
str.Length = str.Length -5;
string final = str.ToString();
Use Linq Zip() twice:
string result = string.Join(" AND ", list1.Zip(list2, (l1, l2) => l1 + l2).Zip(list3, (l2, l3) => l2 + l3));
https://dotnetfiddle.net/ZYlejS
You could use a combination of string.Join and linq:
string.Join(" AND ", list1.Select((e1, idx) => $"{e1} {list2[idx]} {list3[idx]}"));
You could use one of overloads EquiZip() in MoreLINQ:
var res = string.Join(" AND ", list1.EquiZip(list2, list3, (x, y, z) => x + y + z));
You could also use a combination of string.Join along with Enumerable.Range like so:
string result = string.Join(" AND ", Enumerable.Range(0, Math.Min(list1.Count,
Math.Min(list2.Count, list3.Count)))
.Select(i => $"{list1[i]} {list2[i]} {list3[i]}"));
if the lists are guaranteed to have the same size then it can be reduced to:
string b = string.Join(" AND ", Enumerable.Range(0, list1.Count)
.Select(i => $"{list1[i]} {list2[i]} {list3[i]}"));

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);

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()))

LINQ list to sentence format (insert commas & "and")

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;
});

What's the easiest way to convert a list of integers to a string of comma separated numbers in C#

I'm looking for the one liner here, starting with:
int [] a = {1, 2, 3};
List<int> l = new List<int>(a);
and ending up with
String s = "1,2,3";
String s = String.Join(",", a.Select(i => i.ToString()).ToArray());
string s = string.Join(",", Array.ConvertAll(a, i => i.ToString()));
or in .NET 4.0 you could try (although I'm not sure it will compile):
string s = string.Join(",", a);
String.Join(",", l);
string.Join(",", l.ConvertAll(i => i.ToString()).ToArray());
This is assuming you are compiling under .NET 3.5 w/ Linq.
int[] array = {1,2,3};
string delimited = string.Join(",", array);
l.Select(i => i.ToString()).Aggregate((s1, s2) => s1 + "," + s2)
Another way of doing it:
string s = a.Aggregate("", (acc, n) => acc == "" ? n.ToString() : acc + "," + n.ToString());
I know you're looking for a one liner, but if you create an extension method, all future usage is a one liner. This is a method I use.
public static string ToDelimitedString<T>(this IEnumerable<T> items, string delimiter)
{
StringBuilder joinedItems = new StringBuilder();
foreach (T item in items)
{
if (joinedItems.Length > 0)
joinedItems.Append(delimiter);
joinedItems.Append(item);
}
return joinedItems.ToString();
}
For your list it becomes: l.ToDelimitedString(",")
I added an overload that always uses comma as the delimiter for convenience.

Categories