Let's assume that I have a collection of strings, like so:
IList<string> myList = new List<string>(){"one", "two", "three"};
Using myList.Aggregate I would like to end up with "'one', 'two', 'three'" (including single quotes)
Does someone have a sleak way of doing this, only using the Aggregate function? I was thinking something in the lines of
myList.Aggregate((increment, seed) => string.Concat(increment, ", ", seed));
but that's only half the solution.
Any reason to use Aggregate rather than the simpler string.Join?
string joined = string.Join(", ", myCollection.Select(x => "'" + x + "'"));
(Add a ToArray call if you're using .NET 3.5.)
You can implement string.Join using Aggregate (ideally using a StringBuilder) but it's not terribly pleasant. Assuming a non-empty collection, I think this should do it:
string csv = myCollection.Aggregate(new StringBuilder("'"),
(sb, x) => sb.AppendFormat("{0}', '"),
sb => { sb.Length -= 3;
return sb.ToString(); });
Since you specify "only using the Aggregate function", how about:
myCollection.Aggregate(string.Empty, (soFar, next) =>
soFar + (soFar == string.Empty ? soFar : ", ")
+ "'" + next + "'")
(or)
myCollection.Aggregate(string.Empty, (soFar, next) =>
string.Format("{0}{1}'{2}'",
soFar, soFar == string.Empty ? soFar : ", ", next))
Using Aggregate in this manner for string concatenation is really inefficient though.
EDIT: Updated to get rid of the leading ,.
Related
I have a list I want to pass into SQL but they must be single quote and comma delimited.
So List = a b c
But I need a string = 'a','b','c'
How can I accomplish this in a concise manner?
Something like this I think but can't see within LINQ how to add to beginning and end:
String.Join(",", arr.Select(p => p.ToString()).ToArray())
Maybe something along the lines of:
String.Join(",", arr.Select(p=> "'" + p.ToString() + "'").ToArray());
// or is p already a string
String.Join(",", arr.Select(p => "'" + p + "'").ToArray());
You can do this:
String.Join(",", arr.Select(p => $"'{p.ToString()}'").ToArray());
And that will put the ' on either side of p.ToString() for each element.
Use an ORM that supports the following construction:
string[] items = GetItems();
var query = db.Rows.Where(row => items.Contains(row.ColumnName));
This way, you do not open yourself to sql injection by constructing strings to hand to the database yourself.
You should also be careful if your source data contains "'" in which case you need to escape the "'". Try this:
var arr = new [] { "a'c", "b" };
var output = String.Join(",", arr.Select(p => $"'{p.Replace("'", "''")}'"));
That gives "'a''c','b'".
I successfully created a CSV file from a while loop. I ultimately need my code to generate another CSV file that has GroupBy information.
It looks something like this:
while (code)
{
output3 = split[5].Replace(',', ' ') + ',' +
split[0].Substring(0, 2) + "-" +
split[0].Substring(2, 4) + ',' +
split[4] + ",," + sentprice + ',' +
split[3] + ',' +
split[2] + ',' + calccharge + ',' +
split[1] + ",,," + "NA" + ',' + "A" + ',' +
split[0].Substring(6, 5) + Environment.NewLine + output3;
}
Now I'm trying to split output3 so that I can group by one of the columns (i.e. split[1]).
I've tried:
var table = File.ReadAllLines(FilePath)
.Select(record => record.Split(','))
.Select(cell => new { AFPCode = cell[1] })
.GroupBy(x => x.AFPCode);
but that result gives me an index was outside the bounds of the array. As you can see the index is inside the bounds of the array.
I've also tried:
IEnumerable<string> table =
from row in File.ReadAllLines(FilePath)
let elements = row.Split(',')
let cell = new {AFPCode = elements[1]}
group p by p.AFPcode into g
That is also not working. Any help will be much appreciated.
Some rows are missing the 2nd column. Did you try filtering out those rows like below?
var table = File.ReadAllLines(FilePath)
.Select(record => record.Split(','))
.Where(cell => cell.Length >= 2) // Add filter clause here
.Select(cell => new { AFPCode = cell[1] })
.GroupBy(x => x.AFPCode);
You have an empty line at the end of your file. That empty line doesn't have a second item when you split it into fields. Either remove the empty line at the end, or filter out the empty line in the code you're using to read in the file.
Some side notes, you shouldn't be constructing large files by using the + operator to concat the strings to each other in a loop. It's a particularly inefficient operation. You can use string.Join to efficiently join a bunch of strings together (either all fields on a line or all lines in a file).
The other option is to write out each line to the file as you compute it (this is actually better still, as you then never have more than one line in memory). This can be done rather easily using File.WriteAllLines if you have a sequence of all of your lines.
I'm trying to build a SQL query using StringBuilder and I've become stuck trying to do part of the WHERE clause.
I have a list box with a bunch of values and allows multiple selection. I need to iterate through the selected items and put then in an IN statement like...
WHERE SOME_FIELD IN ('Value','NextValue','AnotherValue')
so far I've written the code like this...
if (lstSalesGroup.SelectedItem != null)
{
selectQuery.Append("AND SALES_GROUP IN (");
foreach (ListItem item in lstSalesGroup.Items)
{
if (item.Selected)
selectQuery.Append("'" + item.Value + "',");
}
selectQuery.Append(")");
}
I need to test if the item is the last in the loop so that it doesn't put on the "," before the closing ")".
How can I do this? Or if there's a better way to build this part of the query please do suggest, I am still learning, we all have to start somewhere! :)
Eventually this will be a query for a part search.
Thanks in advance
Couple of ways for doing that.
You can use string.TrimEnd to remove the extra comma from the string or you can create a new string using string.Join like
string InPartQuery = string.Join(",", lstSalesGroup.Items
.Cast<ListItem>()
.Where(t => t.Selected)
.Select(r => "'" + r.Value + "'"));
You could use String.Join with some Linq
For clearity I've put the code in variables.
if (lstSalesGroup.SelectedItem != null)
{
var queryStr = "AND SALES_GROUP IN ({0})";
var selectedItemValues = (from itm in lstSalesGroup.Items.Cast<ListItem>()
where itm.Selected
select String.Format("'{0}'", itm));
selectQuery.Append(String.Format(queryStr, String.Join(",", selectedItemValues)));
}
Try using linq
selectQuery.Append("AND SALES_GROUP IN (");
selectQuery.Append(string.Join(",", lstSalesGroup.Items.Select(i => "'" + i.Value + "'")));
selectQuery.Append(")");
This will solve your problem, but you have a problem with SQL injection here. I would strongly advice you to use parameters in your query.
Dim s As String = "'"
For i = 0 To ListBox1.Items.Count - 1
s = s & ListBox1.Items.Item(i) & "','"
Next
Try this:
if (lstSalesGroup.SelectedItem != null)
{
selectQuery.Append("AND SALES_GROUP IN (");
var local = lstSalesGroup.Items.Where(c => c.Selected)
.Select(c => "'"+c.Value+"'")
.Aggregate((c,n) => c+ ", "+n);
selectQuery.Append(local);
selectQuery.Append(")");
}
Look at this example for more info on the .Aggragate(...) method
Is it not allowed to have a conditional operator in a lambda expression in ForEach?
List<string> items = new List<string>{"Item 1", "Item 2", "Item I Care About"};
string whatICareAbout = "";
// doesn't compile :(
items.ForEach(item => item.Contains("I Care About") ?
whatICareAbout += item + "," : whatICareAbout += "");
Compilation error -> "Only assignment, call, increment, decrement, and new object expressions can be used as a statement"
Trying to use a normal if doesn't work either:
// :(
items.ForEach(item => if (item.Contains("I Care About")) {whatICareAbout += item + ", ";}
Just not possible?
You're using the shorter form of lambda expressions, which only allow a single expressions.
You need to the long form, which allows multiple statements.
For example:
items.ForEach(item => {
if (item.Contains("I Care About"))
whatICareAbout += item + ", ";
});
What are you trying to acheive? Are you trying to form a string of comma separated items where they contain a particular value? In linq you would achieve this using the following:
List<string> items = new List<string> { "Item 1", "Item 2", "Item I Care About", "Item I Care About", "Item I Care About" };
string whatICareAbout = items.Where(x => x.Contains("I Care About"))
.Aggregate( (y, z) => y + ", " + z);
The output from this is "Item I Care About, Item I Care About, Item I Care About".
Note: Aggregate is a great way of ensuring there is no trailing ","
The problem was that expression
item.Contains("I Care About") ? whatICareAbout += item + "," : whatICareAbout += ""
is not a statement. It just returns a value which has type string.
There is a trick to make it work (just for fun):
items.ForEach(item => (item.Contains("I Care About") ?
whatICareAbout += item + "," : whatICareAbout += "").GetType());
I simply added call to .GetType() method to create a statement from initial expression, and it compiled.
Try parentheses:
items.ForEach(item => item.Contains("I Care About") ? (whatICareAbout += item + ",") : (whatICareAbout += "") );
+= has a higher precedence than ?, that may be why you're getting the error. With parentheses, the error may go away. Not 100% sure of this, though... lambda expressions may have additional restrictions which prevent use of assignment statements.
UPDATE:
Instead of multiple += statements, it's a lot cleaner to put the conditional on the right-hand side of the assignment, like this:
List<string> items = new List<string> { "one", "two", "three" };
string whatICareAbout = "";
items.ForEach(item => whatICareAbout += item.Contains("I Care About") ? (item + ",") : "");
UPDATE 2:
But it's even better to just use Aggregate() since it's designed for exactly this scenario. Here's one sample:
string whatICareAbout = items.Aggregate("", (total, item) => item.Contains("I Care About") ? (total + item + ",") : total);
But I think #Matt Breckon's answer above (that I just saw as I was about to post this)is even better than my example since it deals with removing the terminal ",". Look at his answer... :-)
I have a listbox being populated from a combo box. What i need to do is string all the contents of the listbox together and then Aggregate it.
string cols = listbox1.items.Aggregate((f, s) => f.value + "," + s.value);
doesnt work.
Items is an ObjectCollection, so all you know for sure is that it contains objects. You can call ToString on any object:
string[] items = listBox1.Items
.OfType<object>()
.Select(item => item.ToString())
.ToArray();
string result = string.Join(",", items);
Note that this is both more readable and more efficient than using aggregate, which causes multiple string concatenations.
pretty old thread, but here's my solution if anybody need it still
string cols = string.Join(",", listBox1.Items.Cast<String>());
Supposing that you have strings in the listbox, try this:
string cols =
String.Join(",", listbox1.Items
.OfType<Object>()
.Select(i => i.ToString())
.ToArray());
Generally String.Join is used to join a string. This is faster than using a StringBuilder as the size of the new string is already known and it doesn't have to copy everything twice.
string cols = listBox1.Items.Cast<string>().Aggregate((f, s) => f + "," + s);
I think you need to explicitly do ToString()
string cols = listbox1.items.Aggregate((f, s) => f.value.ToString() + "," + s.value.ToString());