Is there any easy LINQ expression to concatenate my entire List<string> collection items to a single string with a delimiter character?
What if the collection is of custom objects instead of string? Imagine I need to concatenate on object.Name.
string result = String.Join(delimiter, list);
is sufficient.
Warning - Serious Performance Issues
Though this answer does produce the desired result, it suffers from poor performance compared to other answers here. Be very careful about deciding to use it
By using LINQ, this should work;
string delimiter = ",";
List<string> items = new List<string>() { "foo", "boo", "john", "doe" };
Console.WriteLine(items.Aggregate((i, j) => i + delimiter + j));
class description:
public class Foo
{
public string Boo { get; set; }
}
Usage:
class Program
{
static void Main(string[] args)
{
string delimiter = ",";
List<Foo> items = new List<Foo>() { new Foo { Boo = "ABC" }, new Foo { Boo = "DEF" },
new Foo { Boo = "GHI" }, new Foo { Boo = "JKL" } };
Console.WriteLine(items.Aggregate((i, j) => new Foo{Boo = (i.Boo + delimiter + j.Boo)}).Boo);
Console.ReadKey();
}
}
And here is my best :)
items.Select(i => i.Boo).Aggregate((i, j) => i + delimiter + j)
Note: This answer does not use LINQ to generate the concatenated string. Using LINQ to turn enumerables into delimited strings can cause serious performance problems
Modern .NET (since .NET 4)
This is for an array, list or any type that implements IEnumerable:
string.Join(delimiter, enumerable);
And this is for an enumerable of custom objects:
string.Join(delimiter, enumerable.Select(i => i.Boo));
Old .NET (before .NET 4)
This is for a string array:
string.Join(delimiter, array);
This is for a List<string>:
string.Join(delimiter, list.ToArray());
And this is for a list of custom objects:
string.Join(delimiter, list.Select(i => i.Boo).ToArray());
using System.Linq;
public class Person
{
string FirstName { get; set; }
string LastName { get; set; }
}
List<Person> persons = new List<Person>();
string listOfPersons = string.Join(",", persons.Select(p => p.FirstName));
Good question. I've been using
List<string> myStrings = new List<string>{ "ours", "mine", "yours"};
string joinedString = string.Join(", ", myStrings.ToArray());
It's not LINQ, but it works.
You can simply use:
List<string> items = new List<string>() { "foo", "boo", "john", "doe" };
Console.WriteLine(string.Join(",", items));
Happy coding!
I think that if you define the logic in an extension method the code will be much more readable:
public static class EnumerableExtensions {
public static string Join<T>(this IEnumerable<T> self, string separator) {
return String.Join(separator, self.Select(e => e.ToString()).ToArray());
}
}
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString() {
return string.Format("{0} {1}", FirstName, LastName);
}
}
// ...
List<Person> people = new List<Person>();
// ...
string fullNames = people.Join(", ");
string lastNames = people.Select(p => p.LastName).Join(", ");
List<string> strings = new List<string>() { "ABC", "DEF", "GHI" };
string s = strings.Aggregate((a, b) => a + ',' + b);
I have done this using LINQ:
var oCSP = (from P in db.Products select new { P.ProductName });
string joinedString = string.Join(",", oCSP.Select(p => p.ProductName));
Put String.Join into an extension method. Here is the version I use, which is less verbose than Jordaos version.
returns empty string "" when list is empty. Aggregate would throw exception instead.
probably better performance than Aggregate
is easier to read when combined with other LINQ methods than a pure String.Join()
Usage
var myStrings = new List<string>() { "a", "b", "c" };
var joinedStrings = myStrings.Join(","); // "a,b,c"
Extensionmethods class
public static class ExtensionMethods
{
public static string Join(this IEnumerable<string> texts, string separator)
{
return String.Join(separator, texts);
}
}
This answer aims to extend and improve some mentions of LINQ-based solutions. It is not an example of a "good" way to solve this per se. Just use string.Join as suggested when it fits your needs.
Context
This answer is prompted by the second part of the question (a generic approach) and some comments expressing a deep affinity for LINQ.
The currently accepted answer does not seem to work with empty or singleton sequences. It also suffers from a performance issue.
The currently most upvoted answer does not explicitly address the generic string conversion requirement, when ToString does not yield the desired result. (This can be remedied by adding a call to Select.)
Another answer includes a note that may lead some to believe that the performance issue is inherent to LINQ. ("Using LINQ to turn enumerables into delimited strings can cause serious performance problems.")
I noticed this comment about sending the query to the database.
Given that there is no answer matching all these requirements, I propose an implementation that is based on LINQ, running in linear time, works with enumerations of arbitrary length, and supports generic conversions to string for the elements.
So, LINQ or bust? Okay.
static string Serialize<T>(IEnumerable<T> enumerable, char delim, Func<T, string> toString)
{
return enumerable.Aggregate(
new StringBuilder(),
(sb, t) => sb.Append(toString(t)).Append(delim),
sb =>
{
if (sb.Length > 0)
{
sb.Length--;
}
return sb.ToString();
});
}
This implementation is more involved than many alternatives, predominantly because we need to manage the boundary conditions for the delimiter (separator) in our own code.
It should run in linear time, traversing the elements at most twice.
Once for generating all the strings to be appended in the first place, and zero to one time while generating the final result during the final ToString call. This is because the latter may be able to just return the buffer that happened to be large enough to contain all the appended strings from the get go, or it has to regenerate the full thing (unlikely), or something in between. See e.g. What is the Complexity of the StringBuilder.ToString() on SO for more information.
Final Words
Just use string.Join as suggested if it fits your needs, adding a Select when you need to massage the sequence first.
This answer's main intent is to illustrate that it is possible to keep the performance in check using LINQ. The result is (probably) too verbose to recommend, but it exists.
You can use Aggregate, to concatenate the strings into a single, character separated string but will throw an Invalid Operation Exception if the collection is empty.
You can use Aggregate function with a seed string.
var seed = string.Empty;
var seperator = ",";
var cars = new List<string>() { "Ford", "McLaren Senna", "Aston Martin Vanquish"};
var carAggregate = cars.Aggregate(seed,
(partialPhrase, word) => $"{partialPhrase}{seperator}{word}").TrimStart(',');
you can use string.Join doesn’t care if you pass it an empty collection.
var seperator = ",";
var cars = new List<string>() { "Ford", "McLaren Senna", "Aston Martin Vanquish"};
var carJoin = string.Join(seperator, cars);
Related
What I have that I start with is three string variables for example
string myVar1 = "hi";
string myVar2 = "bye";
string myVar3 = "who dis";
CURRENTLY I am doing this, It "works" but I feel like there is redundancy:
List<string> listOne= new List<string>
{
myVar1 , myVar2,myVar3
};
listOne= listOne.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t).ToList();
var whatIFinallyWantToHave= new HashSet<string>(listOne, StringComparer.OrdinalIgnoreCase);
I am thinking there should be a way to get rid of listOne and do it in one go. Do you know how? ( note we still need to ignore the empty or null strings that listOne is cleaning.)
I think it's worth adding an extension in this case so you can chain from the list to the set directly. This way you don't have to break the flow to explicitly construct the hash set. Perhaps:
public static class Extensions
{
public static HashSet<T> ToHashSet<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
}
And use it like so:
string myVar1 = "hi";
string nully = null;
string myVar2 = "bye";
string empty = "";
string myVar3 = "who dis";
string dup = "WHO DIS";
List<string> listOne = new List<string>
{
nully, myVar1, empty, myVar2 ,myVar3, dup
};
listOne
.Where(t => !string.IsNullOrWhiteSpace(t))
.ToHashSet(StringComparer.OrdinalIgnoreCase)
.ToList()
.ForEach(Console.WriteLine);
// hi
// bye
// who dis
There is no reason to create a List or have a separate variable, but you have to combine the individual values somehow. Also, there is no reason to create (yet) another List just because you are filtering.
string myVar1 = "hi";
string myVar2 = "bye";
string myVar3 = "who dis";
var whatIFinallyWantToHave = new[] { myVar1, myVar2, myVar3 }
.Where(s => !String.IsNullOrWhiteSpace(s))
.ToHashSet(StringComparer.OrdinalIgnoreCase);
NOTE: This requires .Net 4.7.2 for the ToHashSet method.
I have a list of strings (thing1-3, else1-3, other1-3), and I want to create a simplified list with just (thing, else, other). Seems straight forward (or at least this was with the Classic VB Dictionary .Exists function), but I'm stuck. So I'm checking if the string startswith one of my simplified strings, then if the simplified list does not contain that string, add it. But checking if the simplified list contains the string already is throwing me off.
List<string> myList = new List<string>(new string[] { "thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3" });
List<string> myListSimplified = new List<string>();
foreach (string s in myList)
{
if (s.StartsWith("thing"))
{
if (!myListSimplifed.Contains("thing")) { myListSimplifed.Add("thing"); }
}
if (s.StartsWith("else"))
{
if (!myListSimplifed.Contains("else")) { myListSimplifed.Add("else"); }
}
if (s.StartsWith("other"))
{
if (!myListSimplifed.Contains("other")) { myListSimplifed.Add("other"); }
}
}
I would expect this mySimplifiedList to contain "thing", "else", "other", but it contains thing1-3, else1-2, other1-3.
if (myListSimplified.Exists("thing")) { }
IntelliSense returns "cannot convert from 'string' to 'System.Predicate'
ok.. so this:
if (!myListSimplified.Any(str => str.Contains("thing"))) { myListSimplified.Add("thing"); }
Or
if (!myListSimplified.Exists(str => str.Contains("thing"))) { myListSimplified.Add("thing"); }
None of these work.
Obviously I can create a method to iterate through the list and compare it to a string, but this functionality seems to be too fundamental to lists that MS left it out... Also seems silly to be passing lists around...
private bool Exists(List<string> lList, string sCompare)
{
bool bVal = false;
foreach (string s in lList)
{
if (s == sCompare) { bVal = true; }
break;
}
return bVal;
}
I'm not sure what your problem is:
First of all, it seems your first code snippet contains a typo: you have List<string> myListSimplified but then inside the foreach you reference myListSimplifed (missing 'i' after the 'f').
If I correct that typo and run your code, then I get a list containing {"thing", "else", "other" }, which seems to be what you also expect.
Besides the typo in myListSimplifed vs myListSimplified your code sample produces what you want it to do.
Not what you ask for, but you can have the same effect with far fewer lines of code:
var myList = new List<string> {"thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3"};
var myListSimplified = myList.Select(s => new string(s.Where(char.IsLetter).ToArray())).Distinct();
Lists are generic data types. They don't know what strings are, and so they're not provided out of the box with facilities to search for items that StartWith or Contain other strings. This sort of operation doesn't make sense, generically speaking. This is why you have operations like Any that take a lambda function to provide your own logic:
if (myList.Any(str => str.StartsWith("thing")))
mySimplifiedList.Add("thing");
I've tested this and it works fine.
Of course, if you have multiple strings you want to extract, you should probably make this more generic. The simplest way is to extract the lines above to a method that receives the substring ("thing", in this case) as a parameter. A more generic approach (assuming it matches your data and logic) would be to go over all strings, strip all numerals from it, and store that. Assuming StripNumerals is a method that receives a string, it could look like this, with Distinct ensuring you have only one instance of each string.
var simplifiedList = myList.Select(StripNumerals).Distinct().ToList();
I have noticed your typo when putting the code in Visual Studio. The result is correct, but your algorithm is far from being generic. What you can try:
var is useful to simplify declaration
list initialization can be simplified
obtain a list by stripping all digits and perform a distinct on it
var myList = new List<string>() { "thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3" };
var listWithoutNumbers = myList.Select(s =>
{
Regex rgx = new Regex("[0-9]");
return rgx.Replace(s, "");
});
var simplifiedList = listWithoutNumbers.Distinct();
Your original solution works, apart from the typo.
However, if you want more generic solution, you could use something like this
List<string> myList = new List<string>(new string[] { "thing1", "thing2", "thing3", "else1", "else2", "else3", "other1", "other2", "other3" });
List<string> myListSimplified = myList.Select(s => new String(s.Where(Char.IsLetter).ToArray())).Distinct().ToList();
Don't forget to add
using System.Linq;
if you will try this solution.
I have a list of part numbers:
var parts = new List<string> {"part1", "part2", "part3"};
I also have a dictionary of quantities for these part numbers:
var quantities = new Dictionary<string, int> {{"part1", 45}, {"part3", 25}};
Given a delimiter of |, I need to arrange these values in a flat file like so:
SalesRep|part1|part2|part3
Mr. Foo|45||25
What I'd like to do is define a string that no matter what values are in parts and quantities, I can tack this on to the sales rep name to resemble the example above.
It seems like I should be able to do this with a string.Join() on an enumerable LINQ operation, but I can't figure out what statement will get me the IEnumerable<string> result from joining parts and quantities. It thought that would be a .Join(), but the signature doesn't seem right. Can someone enlighten me?
Something like this perhaps?
var partValues = parts.Select(x => quantities.ContainsKey(x) ? quantities[x] : 0);
Basically for each item in the parts list you either pick the value from your dictionary, or if it doesn't exist 0.
To make this a little more interesting you could define a generic extension method on IDictionary<T,U> that makes this a little more readable:
public static class DictionaryExtensions
{
public static U GetValueOrDefault<T,U>(this IDictionary<T, U> dict, T key)
{
if(dict.ContainsKey(key))
{
return dict[key];
}
return default(U);
}
}
Then you can simply write:
var partValues = parts.Select(quantities.GetValueOrDefault);
var parts = new List<string> { "part1", "part2", "part3" };
var quantities = new Dictionary<string, int> { { "part1", 45 }, { "part3", 25 } };
var result = string.Join("|",
from p in parts select quantities.ContainsKey(p)
? quantities[p].ToString() : "");
The goal is to sort through a text (i.e. a speech) and output a list of the distinct words in the speech to a textbox. I have read through a lot of tips on the boards and played around a lot but at this point am at that point where I am more confused then when I started. Here is my code
private void GenerateList(string[] wordlist)
{
List<string> wordList = new List<string>();
for (int i = 0; i < wordlist.Length; i++)
{
wordList.Add(wordlist[i]);
}
var uniqueStr = from item in wordList.Distinct().ToList()
orderby item
select item;
for (int i = 0; i < uniqueStr.Count(); i++ )
{
txtOutput.Text = uniqueStr.ElementAt(i) + "\n";
}
}
At this point I am getting a return of one word. For the text I am using (the gettysburg address) it is the word "year" and it is the only instance of that word in the text.
I am passing the function each individual word loaded into a string array that is then put into a list (which may be redundant?).
I hope this does what you need in a simple and efficient manner (using .Dump() from LINQPad)
void Main()
{
// can be any IEnumerable<string> including string[]
var words = new List<string>{"one", "two", "four", "three", "four", "a", "z"};
words.ToDistinctList().Dump();
// you would use txtOutput.Text = words.ToDistinctList()
}
static class StringHelpers
{
public static string ToDistinctList(this IEnumerable<string> words)
{
return string.Join("\n", new SortedSet<string>(words));
}
}
A few tips regarding your question:
There is no reason to turn the array into list, because LINQ extension methods are defined on IEnumerable<T>, which is implemented by both the array and the list
Make sure that all letters are in the same case - use ToLower, for instance
You are overwriting txtOutput.Text in every iteration. Instead of setting the new value, append new part to the existing value
Here is the simple piece of code which produces the output you wanted:
IEnumerable<string> distinct =
wordList
.Select(word => word.ToLower())
.Distinct()
.OrderBy(word => word);
txtOutput.Text = string.Join("\n", distinct.ToArray());
On a related note, here is a very simple LINQ expression which returns distinct words from a text, where the whole text is specified as one string:
public static IEnumerable<string> SplitIntoWords(this string text)
{
string pattern = #"\b[\p{L}]+\b";
return
Regex.Matches(text, pattern)
.Cast<Match>() // Extract matches
.Select(match => match.Value.ToLower()) // Change to same case
.Distinct(); // Remove duplicates
}
You can find more variations of regex pattern for the same problem here: Regex and LINQ Query to Split Text into Distinct Words
Here's how I'd simplify your code, as well as achieve what you want to achieve.
private void GenerateList(string[] wordlist)
{
List<string> wordList = wordlist.ToList(); // initialize the list passing in the array
var uniqueStr = from item in wordList.Distinct().ToList()
orderby item
select item;
txtOutput.Text = String.Join("\n", uniqueStr.ToArray());
}
You can use the fact that the StringBuilder class has a fluent interface along with LINQ to simplify this greatly.
First, you can create the StringBuilder and concatenate all of the words into the same instance like so:
// The builder.
var builder = new StringBuilder();
// A copy of the builder *reference*.
var builderCopy = builder;
// Get the distinct list, order by the string.
builder = wordList
// Get the distinct elements.
.Distinct()
// Order the words.
.OrderBy(w => w).
// Append the builder.
Select(w => builderCopy.AppendLine(word)).
// Get the last or default element, this will
// cycle through all of the elements.
LastOrDefault();
// If the builder is not null, then assign to the output, otherwise,
// assign null.
txtOutput.Text = builder == null ? null : builder.ToString();
Note, you don't have to actually materialize the list, as wordList is already a materialized list, it's an array (and as a side note, typed arrays in C# implement the IList<T> interface).
The AppendLine method (and most of the methods on StringBuilder) return the instance of the StringBuilder that the operation was performed on, which is why the LastOrDefault method call works; simply call the operation and return the result (each item returned will be the same reference).
The builderCopy variable is used to avoid access to a modified closure (it never hurts to be safe).
The null check at the end is for the case where wordList doesn't contain any elements. In this case, the call to LastOrDefault will return null.
How can I replace ConvertListToString(extensions) with an elegant LINQ statement?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestExtn2343
{
class Program
{
public static void Main(string[] args)
{
string[] files = { "test.txt", "test2.txt",
"test.as", "notes.doc",
"data.xml", "test.xml",
"test.html", "notes.txt",
"test.xls" };
List<string> extensions = (from file in files
let index = file.LastIndexOf('.') + 1
select file.Substring(index)).Distinct().ToList<string>();
Console.WriteLine("The kinds of file extensions used are {0}.", ConvertListToString(extensions));
Console.ReadLine();
}
public static string ConvertListToString(List<string> list) {
StringBuilder sb = new StringBuilder();
int count = 1;
foreach (var listItem in list)
{
sb.Append(listItem.ToUpper());
if (count != list.Count)
sb.Append(", ");
count++;
}
return sb.ToString();
}
}
}
var s = string.Join(", ", files.Select(file => Path.GetExtension(file))
.Distinct(StringComparer.InvariantCultureIgnoreCase).ToArray());
Here's how:
String s = String.Join(", ", (from extension in extensions select extension.ToUpper()).ToArray());
Note, I would probably not write this as one line, rather like this:
String s = String.Join(", ",
(from extension in extensions
select extension.ToUpper()).ToArray());
If you don't mind just going for the Linq extension methods directly, instead of the Linq query syntax, you can use this:
String s = String.Join(", ", extensions.Select(e => e.ToUpper()).ToArray());
Another variant would be to just call ToUpper on the final string instead:
String s = String.Join(", ", extensions.ToArray()).ToUpper();
And finally, in .NET 4.0, String.Join finally supports IEnumerable<String> directly, so this is possible:
String s = String.Join(", ", extensions).ToUpper();
Note that per your question, this might lead to duplicates nonetheless. Consider what would happen if your original list of filenames contained both "filename.txt" and "filename.TXT", these would be counted as two distinct extensions.
The call to ToUpper should be moved up before the call to Distinct to fix this.
Instead of the original Linq expression + code, I would rewrite the whole thing to this:
String[] distinctExtensions = files
.Select(fileName => Path.GetExtension(fileName).ToUpper())
.Distinct()
.ToArray();
String distinctExtensionsAsString = String.Join(", ", distinctExtensions);
If you add the following utility method to your code library, you can simplify it further:
public static class StringExtensions
{
public static String Join(this IEnumerable<String> elements, String separator)
{
if (elements is String[])
return String.Join(separator, (String[])elements);
else
return String.Join(separator, elements.ToArray());
}
}
and then your code can look like this:
String distinctExtensionsAsString = files
.Select(fileName => Path.GetExtension(fileName).ToUpper())
.Distinct()
.Join(", ");
How about this...
public static string ConvertListToString(List<string> list)
{
return list.Aggregate((x, y) => x + ", " + y).ToUpper();
}
This does it in one line, and I've moved the "ToUpper" out onto the final string so it's only called once.
Clearly you could then throw away the method ConvertListToString and inline if you wanted.
How about this:
String output = String.Join(", ",(from file in files
let index = file.LastIndexOf('.') + 1
select file.Substring(index)).Distinct().ToArray<string>());