In many places in our code we have collections of objects, from which we need to create a comma-separated list. The type of collection varies: it may be a DataTable from which we need a certain column, or a List<Customer>, etc.
Now we loop through the collection and use string concatenation, for example:
string text = "";
string separator = "";
foreach (DataRow row in table.Rows)
{
text += separator + row["title"];
separator = ", ";
}
Is there a better pattern for this? Ideally I would like an approach we could reuse by just sending in a function to get the right field/property/column from each object.
string.Join(", ", Array.ConvertAll(somelist.ToArray(), i => i.ToString()))
static string ToCsv<T>(IEnumerable<T> things, Func<T, string> toStringMethod)
{
StringBuilder sb = new StringBuilder();
foreach (T thing in things)
sb.Append(toStringMethod(thing)).Append(',');
return sb.ToString(0, sb.Length - 1); //remove trailing ,
}
Use like this:
DataTable dt = ...; //datatable with some data
Console.WriteLine(ToCsv(dt.Rows, row => row["ColName"]));
or:
List<Customer> customers = ...; //assume Customer has a Name property
Console.WriteLine(ToCsv(customers, c => c.Name));
I don't have a compiler to hand but in theory it should work. And as everyone knows, in theory, practice and theory are the same. In practice, they're not.
I found string.Join and lambda Select<Func<>> helps to write minimum code.
List<string> fruits = new List<string>();
fruits.Add("Mango");
fruits.Add("Banana");
fruits.Add("Papaya");
string commaSepFruits = string.Join(",", fruits.Select(f => "'" + f + "'"));
Console.WriteLine(commaSepFruits);
List<int> ids = new List<int>();
ids.Add(1001);
ids.Add(1002);
ids.Add(1003);
string commaSepIds = string.Join(",", ids);
Console.WriteLine(commaSepIds);
List<Customer> customers = new List<Customer>();
customers.Add(new Customer { Id = 10001, Name = "John" });
customers.Add(new Customer { Id = 10002, Name = "Robert" });
customers.Add(new Customer { Id = 10002, Name = "Ryan" });
string commaSepCustIds = string.Join(", ", customers.Select(cust => cust.Id));
string commaSepCustNames = string.Join(", ", customers.Select(cust => "'" + cust.Name + "'"));
Console.WriteLine(commaSepCustIds);
Console.WriteLine(commaSepCustNames);
Console.ReadLine();
// using System.Collections;
// using System.Collections.Generic;
// using System.Linq
public delegate string Indexer<T>(T obj);
public static string concatenate<T>(IEnumerable<T> collection, Indexer<T> indexer, char separator)
{
StringBuilder sb = new StringBuilder();
foreach (T t in collection) sb.Append(indexer(t)).Append(separator);
return sb.Remove(sb.Length - 1, 1).ToString();
}
// version for non-generic collections
public static string concatenate<T>(IEnumerable collection, Indexer<T> indexer, char separator)
{
StringBuilder sb = new StringBuilder();
foreach (object t in collection) sb.Append(indexer((T)t)).Append(separator);
return sb.Remove(sb.Length - 1, 1).ToString();
}
// example 1: simple int list
string getAllInts(IEnumerable<int> listOfInts)
{
return concatenate<int>(listOfInts, Convert.ToString, ',');
}
// example 2: DataTable.Rows
string getTitle(DataRow row) { return row["title"].ToString(); }
string getAllTitles(DataTable table)
{
return concatenate<DataRow>(table.Rows, getTitle, '\n');
}
// example 3: DataTable.Rows without Indexer function
string getAllTitles(DataTable table)
{
return concatenate<DataRow>(table.Rows, r => r["title"].ToString(), '\n');
}
In .NET 4 you can just do string.Join(", ", table.Rows.Select(r => r["title"]))
You could write a function that transforms a IEnumerable<string> into a comma-separated string:
public string Concat(IEnumerable<string> stringList)
{
StringBuilder textBuilder = new StringBuilder();
string separator = String.Empty;
foreach(string item in stringList)
{
textBuilder.Append(separator);
textBuilder.Append(item);
separator = ", ";
}
return textBuilder.ToString();
}
You can then use LINQ to query your collection/dataset/etc to provide the stringList.
As an aside: The first modification I would make is to use the StringBuilder Class instead of just a String - it'll save resources for you.
I love Matt Howells answer in this post:
I had to make it into an extension:
public static string ToCsv<T>(this IEnumerable<T> things, Func<T, string> toStringMethod)
Usage (I am getting all the emails and turning them into a CSV string for emails):
var list = Session.Find("from User u where u.IsActive = true").Cast<User>();
return list.ToCsv(i => i.Email);
For collections you can use this method as well, for example:
string.Join(", ", contactsCollection.Select(i => i.FirstName));
You can select any property that you want to separate.
string strTest = "1,2,4,6";
string[] Nums = strTest.Split(',');
Console.Write(Nums.Aggregate<string>((first, second) => first + "," + second));
//OUTPUT:
//1,2,4,6
Here's my favorite answer adapted to the question,
and corrected Convert to ConvertAll:
string text = string.Join(", ", Array.ConvertAll(table.Rows.ToArray(), i => i["title"]));
Related
I have been learning C# (I am relatively new) and I have a list of input files consisting file naming format like "inputFile_dateSequence_sequenceNumber.xml". The code that I am using to sort the file lists in ascending order is below:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
string[] inputfiles = { "inputFile_2020-04-10_1.xml",
"inputFile_2020-04-10_2.xml",
"inputFile_2020-04-10_4.xml",
"inputFile_2020-04-10_3.xml",
"inputFile_2020-04-10_10.xml",
"inputFile_2020-05-10_1.xml",
"inputFile_2020-05-10_2.xml",
"inputFile_2020-05-10_10.xml",
"inputFile_2020-05-10_11.xml" };
List<string> stringList = new List<string>();
foreach (string s in inputfiles)
{
string bz = s.Split('.')[0];
stringList.Add(bz);
}
string[] Separator = new string[] { "_" };
var sortedList = stringList.OrderBy(i => i).ThenBy(s => int.Parse(s.Split(Separator, StringSplitOptions.None)[2])).ToList();
foreach (string i in sortedList)
{
Console.WriteLine(i);
}
}
}
But in ascending order, I am getting output as below:
inputFile_2020-04-10_1
inputFile_2020-04-10_10
inputFile_2020-04-10_2
inputFile_2020-04-10_3
inputFile_2020-04-10_4
inputFile_2020-05-10_1
inputFile_2020-05-10_10
inputFile_2020-05-10_11
inputFile_2020-05-10_2
but my desired output is like below:
inputFile_2020-04-10_1.xml
inputFile_2020-04-10_2.xml
inputFile_2020-04-10_3.xml
inputFile_2020-04-10_4.xml
inputFile_2020-04-10_10.xml
inputFile_2020-05-10_1.xml
inputFile_2020-05-10_2.xml
inputFile_2020-05-10_10.xml
inputFile_2020-05-10_11.xml
What modification should the code need in order to get the output like this?
You could achieve your need by using regex:
var sortedList= stringList.OrderBy(x => Regex.Replace(x, #"\d+", m => m.Value.PadLeft(10, '0')));
Loads of ways to solve this, as you can see...
You can order first by just the date part of the name, then by the length of the name string, so smaller numbers like 1, 7 sort before longer numbers like 10, 17.. then by the name itself
.OrderBy(x => x.Remove(20))
.ThenBy(x=>x.Length)
.ThenBy(x=>x)
Perhaps though you'd parse the entire thing:
class MyFile{
string FullName {get;set;}
string Name {get;set;}
DateTime Date {get;set;}
int Num {get;set;}
MyFile(string fullname){
var bits = Path.GetFilenameWithoutExtension( fullname).Split('_');
FullName = FullName;
Name = bits[0];
Date = DateTime.Parse(bits[1]);
Num = int.Parse(bits[2]);
}
Then
var parsed = inputfiles.Select(x => new MyFile(x));
Now you can OrderBy that:
parsed.OrderBy(m => m.Date).ThenBy(m => m.Num);
Try to avoid doing everything at some base level of string/int primitive; this is OO programming! 😀
Use the following code:
var sortedList = stringList
.OrderBy(s => s.Substring(0, s.LastIndexOf('_'))) // sort by inputFile_dateSequence
.ThenBy(s => int.Parse(s.Substring(s.LastIndexOf('_') + 1))) // sort by sequenceNumber as integer
.ToList();
Update. If you want to preserve file extension, you can use the following:
List<string> sortedList = inputfiles
.Select(s =>
{
int nameSeparator = s.LastIndexOf('_');
int extSeparator = s.LastIndexOf('.');
return new
{
FullName = s,
BaseName = s.Substring(0, nameSeparator),
Sequence = int.Parse(s.Substring(nameSeparator + 1, extSeparator - nameSeparator - 1)),
Extension = s.Substring(extSeparator + 1)
};
})
.OrderBy(f => f.BaseName) // sort by inputFile_dateSequence
.ThenBy(f => f.Sequence) // sort by sequenceNumber
.ThenBy(f => f.Extension) // sort by file extension
.Select(f => f.FullName)
.ToList();
Using DataTable and LinQ may help you to do the task done easier.
Below is the code with DataTable that generate your desire output
public static void Main()
{
string[] inputfiles = { "inputFile_2020-04-10_1.xml",
"inputFile_2020-04-10_2.xml",
"inputFile_2020-04-10_4.xml",
"inputFile_2020-04-10_3.xml",
"inputFile_2020-04-10_10.xml",
"inputFile_2020-05-10_1.xml",
"inputFile_2020-05-10_2.xml",
"inputFile_2020-05-10_10.xml",
"inputFile_2020-05-10_11.xml" };
DataTable dt = new DataTable();
dt.Columns.Add("filename", typeof(string));
dt.Columns.Add("date", typeof(DateTime));
dt.Columns.Add("sequence", typeof(int));
foreach (string s in inputfiles)
{
DataRow dr = dt.NewRow();
dr[0] = s;
dr[1] = Convert.ToDateTime(s.Split('_')[1]);
dr[2] = Convert.ToInt32(s.Split('_')[2].Split('.')[0]);
dt.Rows.Add(dr);
}
DataTable sortedDT = dt.AsEnumerable()
.OrderBy(r => r.Field<DateTime>("date"))
.ThenBy(r => r.Field<int>("sequence"))
.CopyToDataTable();
foreach (DataRow dr in sortedDT.Rows)
{
Console.WriteLine(dr[0]);
}
}
Output:
I am searching if a string within an stringarray contains a keyword.
If a string gets a match i want the array(s) which the string was found in to be output on the console.
Sofar i have managed to output every string that contains a keyword within the stringarray.
I have tried to work around this by outputting the array insteed but then i get this message "System.String[]"
However, that is not my intent. I wanted the array to be displayed. I wonder, how can i do this?
//Sorry for bad english.
Here are the relevant parts from my code:
List<string[]> loggbok = new List<string[]> { };
string[] log = new string[3]; //date, title, post
DateTime date = DateTime.Now;
log[0] = "\n\tDate: " + date.ToLongDateString() + " Time: " + date.ToShortTimeString();
Console.Write("\tTitle: ");
log[1] = "\tTitle: " + Console.ReadLine();
Console.Write("\tPost: ");
log[2] = "\tPost: " + Console.ReadLine();
loggbok.Add(log);
log = new string[3];
Console.Write("\n\tSearch: ");
string keyWord;
keyWord = Console.ReadLine();
foreach (string[] item in loggbok)
{
foreach (var s in item)
{
if (s.Contains(keyWord))
{
Console.WriteLine(item);
}
}
}`enter code here`
For displaying the whole array try this:
Console.WriteLine(String.Join("\r\n", item));
You can filter the array like this:
Console.WriteLine(String.Join("\r\n", item.Where(item => item.Contains(keyWord)).ToArray());
or
string[] filtered = item.Where(s => s.Contains(keyWord)).ToArray();
Console.WriteLine(String.Join("\r\n", filtered));
If you want to filter the whole loggbok (list of string arrays) use the SelectMany extension.
string[] filtered = loggbok.SelectMany(s => s.Contains(keyWord)).ToArray();
Console.WriteLine(String.Join("\r\n", filtered));
If I understand your question correctly, you want the whole array in which the keyword has been found.
What you do wrong is you state Console.WriteLine(item) which will only print that item.
Instead, make a function which returns true if the keyword has been found in this array and false if this has not happened. Your code would look something like this:
string keyWord;
keyWord = Console.ReadLine();
foreach (string[] item in loggbok)
{
if (checkItem(item)) {
for(int i = 0; i < item.Length; i++){
Console.WriteLine(item[i]);
}
}
}
public bool checkItem(string[] item, string keyWord) {
foreach(var s in item) {
if(s.Contains(keyWord))
return true;
}
return false;
}
This might help you.
I have a C# List that I want to create a comma separate string. I've found other answers on SO that deal with this, but my particular case I want to only use a portion of the values in the List to create the string.
If my List contained these values:
"Foo"
"Bar"
"Car"
and I wanted to create a string
Foo, Bar and Car.
I could use this code:
string.Format("{0} and {1}.",
string.Join(", ", myList.Take(myList.Count - 1)),
myList.Last());
However, my list is actual formed of jSON values like so
{ Name = "Foo" }
{ Name = "Bar" }
{ Name = "Car" }
So the above code results in:
{ Name = "Foo" }, { Name = "Bar" } and { Name = "Car" }.
How would I construct the string such that I only use the Foo, Bar and Car values in the list?
Update
Thanks to #StevePy, this is what I ended up with:
string.Format("{0} and {1}.",
string.Join(", ", myList.Select(x => x.Name).ToList().Take(myList.Count - 1)),
myList.Select(x => x.Name).ToList().Last());
If you need to operate with strings, just grab the necessary part of each string with, for example, String.IndexOf and String.LastIndexOf methods:
List<string> myList = new List<string> {
"{ Name = \"Foo\" }",
"{ Name = \"Bar\" }",
"{ Name = \"Car\" }"
};
var temp = myList.Select(x =>
{
int index = x.IndexOf("\"") + 1;
return x.Substring(index, x.LastIndexOf("\"") - index);
})
.ToList();
string result = string.Format("{0} and {1}.",
string.Join(", ", temp.Take(myList.Count - 1)),
temp.Last());
Linq should help.
var nameList = myList.Select(x=>x.Name).ToList();
you can use JsonConvert.toString to get the value of your list item, or if you used a json serialization, you could use the JsonConvert.Deserialization
I built a method that will do this for you:
static string ConvertToMyStyle(List<string> input)
{
string result = "";
foreach(string item in input)
{
if(input.IndexOf(item) != input.ToArray().Length-1)
result += item + ", ";
else
result += "and " + item + ".";
}
return result;
}
this handles the single item case
protected string FormatWithOxfordCommas(List<string> reasons)
{
string result = "";
if (reasons.Count == 1)
result += reasons[0];
else
{
foreach (string item in reasons)
{
if (reasons.IndexOf(item) != reasons.Count - 1)
result += item + ", ";
else
result += "and " + item + ".";
}
}
return result;
}
string sStoreStockFeed = "";
string sSeparator = "";
var distinctStoreIDList = skuStoreStockLevels.Select(x => x.Item1).Distinct();
foreach (var storeID in distinctStoreIDList)
{
foreach (var item in skuStoreStockLevels)
{
if (item.Item1 == storeID)
{
// add this one to a job for this store
sStoreStockFeed += sSeparator + item.Item1.ToString() + "," + item.Item2.ToString() + "," + item.Item3.ToString();
sSeparator = "|";
}
}
// some code to process the string before moving on
sStoreStockFeed = "";
sSeparator = "";
}
In the above code snippet skuStoreStockLevels just happens to be a List of type Tuple and Item1 is the StoreID. having got a distinct list it then iterates through the (non-distinct) list to get every applicable item. The inefficiency is that the (big) inner list is iterated throuh repeatedly for each distinct item (StoreID).
UPDATE: pure LINQ solution. This will give you list of strings, created for each group of items.
var query = skuStoreStockLevel.GroupBy(x => x.Item1)
.Select(g => g.Aggregate(new StringBuilder(),
(sb, x) => sb.AppendFormat("{0}{1},{2},{3}", sSeparator, x.Item1, x.Item2, x.Item3),
(sb) => sb.ToString()));
foreach(var feed in query)
// some code to process the string before moving on
Also there are other options - ordering of sequence. Equal items will follow one after another.
int storeID = -1;
StringBuilder builder = new StringBuilder();
foreach (var item in skuStoreStockLevel.OrderBy(x => x.Item1))
{
builder.AppendFormat("{0}{1},{2},{3}", sSeparator, item.Item1, item.Item2, item.Item3);
if (item.Item1 != storeID)
{
// some code to process the string before moving on
storeID = item.Item1;
}
}
Or you can use grouping
StringBuilder builder = new StringBuilder();
foreach (var storeGroup in skuStoreStockLevel.GroupBy(x => x.Item1))
{
foreach (var item in storeGroup)
builder.AppendFormat("{0}{1},{2},{3}", sSeparator, item.Item1, item.Item2, item.Item3);
// some code to process the string before moving on
}
And, of course, it's better to use StringBuilder for creating strings.
Use Linq GroupBy which will build you a list of grouped items:
string sStoreStockFeed = "";
string sSeparator = "";
var itemsByStore = skuStoreStockLevels.GroupBy(x => x.Item1);
foreach (var storeItems in itemsByStore )
{
// storeItems.Key is the storeId, that is x.Item1
foreach(var item in storeItems)
{
sStoreStockFeed += sSeparator + item.Item1.ToString() + "," + item.Item2.ToString() + "," + item.Item3.ToString();
sSeparator = "|";
}
// some code to process the string before moving on
sStoreStockFeed = "";
sSeparator = "";
}
Is there a better way of doing this...
MyString.Trim().Replace("&", "and").Replace(",", "").Replace(" ", " ")
.Replace(" ", "-").Replace("'", "").Replace("/", "").ToLower();
I've extended the string class to keep it down to one job but is there a quicker way?
public static class StringExtension
{
public static string clean(this string s)
{
return s.Replace("&", "and").Replace(",", "").Replace(" ", " ")
.Replace(" ", "-").Replace("'", "").Replace(".", "")
.Replace("eacute;", "é").ToLower();
}
}
Just for fun (and to stop the arguments in the comments)
I've shoved a gist up benchmarking the various examples below.
https://gist.github.com/ChrisMcKee/5937656
The regex option scores terribly; the dictionary option comes up the fastest; the long winded version of the stringbuilder replace is slightly faster than the short hand.
Quicker - no. More effective - yes, if you will use the StringBuilder class. With your implementation each operation generates a copy of a string which under circumstances may impair performance. Strings are immutable objects so each operation just returns a modified copy.
If you expect this method to be actively called on multiple Strings of significant length, it might be better to "migrate" its implementation onto the StringBuilder class. With it any modification is performed directly on that instance, so you spare unnecessary copy operations.
public static class StringExtention
{
public static string clean(this string s)
{
StringBuilder sb = new StringBuilder (s);
sb.Replace("&", "and");
sb.Replace(",", "");
sb.Replace(" ", " ");
sb.Replace(" ", "-");
sb.Replace("'", "");
sb.Replace(".", "");
sb.Replace("eacute;", "é");
return sb.ToString().ToLower();
}
}
If you are simply after a pretty solution and don't need to save a few nanoseconds, how about some LINQ sugar?
var input = "test1test2test3";
var replacements = new Dictionary<string, string> { { "1", "*" }, { "2", "_" }, { "3", "&" } };
var output = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement.Key, replacement.Value));
this will be more efficient:
public static class StringExtension
{
public static string clean(this string s)
{
return new StringBuilder(s)
.Replace("&", "and")
.Replace(",", "")
.Replace(" ", " ")
.Replace(" ", "-")
.Replace("'", "")
.Replace(".", "")
.Replace("eacute;", "é")
.ToString()
.ToLower();
}
}
Maybe a little more readable?
public static class StringExtension {
private static Dictionary<string, string> _replacements = new Dictionary<string, string>();
static StringExtension() {
_replacements["&"] = "and";
_replacements[","] = "";
_replacements[" "] = " ";
// etc...
}
public static string clean(this string s) {
foreach (string to_replace in _replacements.Keys) {
s = s.Replace(to_replace, _replacements[to_replace]);
}
return s;
}
}
Also add New In Town's suggestion about StringBuilder...
There is one thing that may be optimized in the suggested solutions. Having many calls to Replace() makes the code to do multiple passes over the same string. With very long strings the solutions may be slow because of CPU cache capacity misses. May be one should consider replacing multiple strings in a single pass.
The essential content from that link:
static string MultipleReplace(string text, Dictionary replacements) {
return Regex.Replace(text,
"(" + String.Join("|", adict.Keys.ToArray()) + ")",
delegate(Match m) { return replacements[m.Value]; }
);
}
// somewhere else in code
string temp = "Jonathan Smith is a developer";
adict.Add("Jonathan", "David");
adict.Add("Smith", "Seruyange");
string rep = MultipleReplace(temp, adict);
Another option using linq is
[TestMethod]
public void Test()
{
var input = "it's worth a lot of money, if you can find a buyer.";
var expected = "its worth a lot of money if you can find a buyer";
var removeList = new string[] { ".", ",", "'" };
var result = input;
removeList.ToList().ForEach(o => result = result.Replace(o, string.Empty));
Assert.AreEqual(expected, result);
}
I'm doing something similar, but in my case I'm doing serialization/De-serialization so I need to be able to go both directions. I find using a string[][] works nearly identically to the dictionary, including initialization, but you can go the other direction too, returning the substitutes to their original values, something that the dictionary really isn't set up to do.
Edit: You can use Dictionary<Key,List<Values>> in order to obtain same result as string[][]
Regular Expression with MatchEvaluator could also be used:
var pattern = new Regex(#"These|words|are|placed|in|parentheses");
var input = "The matching words in this text are being placed inside parentheses.";
var result = pattern.Replace(input , match=> $"({match.Value})");
Note:
Obviously different expression (like: \b(\w*test\w*)\b) could be used for words matching.
I was hoping it to be more optimized to find the pattern in expression and do the replacements
The advantage is the ability to process the matching elements while doing the replacements
This is essentially Paolo Tedesco's answer, but I wanted to make it re-usable.
public class StringMultipleReplaceHelper
{
private readonly Dictionary<string, string> _replacements;
public StringMultipleReplaceHelper(Dictionary<string, string> replacements)
{
_replacements = replacements;
}
public string clean(string s)
{
foreach (string to_replace in _replacements.Keys)
{
s = s.Replace(to_replace, _replacements[to_replace]);
}
return s;
}
}
One thing to note that I had to stop it being an extension, remove the static modifiers, and remove this from clean(this string s). I'm open to suggestions as to how to implement this better.
string input = "it's worth a lot of money, if you can find a buyer.";
for (dynamic i = 0, repl = new string[,] { { "'", "''" }, { "money", "$" }, { "find", "locate" } }; i < repl.Length / 2; i++) {
input = input.Replace(repl[i, 0], repl[i, 1]);
}