LINQ Groupby C# from CSV - c#

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.

Related

How to turn array into quote and comma delimited string for sql?

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'".

Building SQL IN statement from listbox control

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

How can I use Linq to create an IEnumerable based on line breaks in a string?

I have some C# code in a string that looks like this:
content = 'var expData =
details.Select(x => x.Explanation.TextWithHtml).ToList();
var score = resData.SequenceEqual(ansData);
var explanation = "";';
How can I make it so the code is converted to the following using LINQ?
<td>01</td><td>var expData =
details.Select(x => x.Explanation.TextWithHtml ).ToList();</td>
<td>02</td><td>var score = resData.SequenceEqual(ansData);</td>
<td>03</td><td>var explanation = "";</td>
It sounds like you want something like:
var lines = text.Split(new[] { "\r\n" }, StringSplitOptions.None)
.Select((line, index) =>
string.Format("<td>{0:00}</td><td>{1}</td>",
index + 1, EscapeHtml(line)));
You'd need to write EscapeHtml though - you don't want tags in your C# code to still end up as tags in the HTML!
This should work, you can get the index from Enumerable.Select:
IEnumerable<String> code =
content.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Select((line, index) =>
string.Format("<td>{0}</td><td>{1}</td>",
(index + 1).ToString("D2"), line));
How to: Pad a Number with Leading Zeros

using LINQ to process a text file

Text File Format
headerinfo = "abc"
**part1=001**
element1
element2....
...
element15
end_element
**part2=002**
element1
element2....
...
emelent15
end_element
......
end_header
I want to select all rows of text starting from part1=001 up to but not including part2=002.
So far I have:
var res = (from line in File.ReadAllLines(sExecPath + #"\" + sFileName)
where line == "part1=001"
select line).ToList();
I am trying to use between option in linq, it does not seem to return any result.
var part1= (from prt in File.ReadAllLines(sExecPath + #"\" + sFileName)
where prt.CompareTo("part1=001") >=0
&& prt.CompareTo("part=002") >= 0
select prt);
I think you are looking for TakeWhile:
var linesInPartOne = File
.ReadAllLines(sExecPath + #"\" + sFileName)
.SkipWhile(line => !line.StartsWith("**part1="))
// To skip to part 1 header line, uncomment the line below:
// Skip(1)
.TakeWhile(line => !line.StartsWith("**part2="));
To generalize this to retrieve any given numbered part, something like this would do:
public static IEnumerable<String> ReadHeaderPart(String filePath, int part) {
return File
.ReadAllLines(filePath)
.SkipWhile(line => !line.StartsWith("**part" + part + "="))
// To skip to part 1 header line, uncomment the line below:
// Skip(1)
.TakeWhile(line =>
!line.StartsWith("**part" + (part + 1) + "="
&&
!line.StartsWith("end_header")))
.ToList();
}
EDIT: I had a Skip(1) in there to skip the part 1 header. Removed it since you seem to want to keep that line.
public static IEnumerable<string> GetLinesBetween(
string path,
string fromInclusive,
string toExclusive)
{
return File.ReadLines(path)
.SkipWhile(line => line != fromInclusive)
.TakeWhile(line => line != toExclusive);
}
var path = Path.Combine(sExecPath, sFileName); // don't combine paths like that
var result = GetLinesBetween(path, "part1=001", "part2=002").ToList();
The simplest and streghtforward solution comes to me is something like this:
var lines = File.ReadAllLines(#"C:\Sample.txt").
SkipWhile(line=>!line.Contains("part1")).
Skip(1).TakeWhile(line=>!line.Contains("part2"));
It returns result you want actually.
The logic is simple:
SkipWhile lines till meeting a line that contains "part1"
after Skip(1) (as it will be actually that one that contains "part1" string)
finally Take those ones till getting to the line containing "part2".
Linq probably isn't your best bet here. Just try doing
var lines = File.ReadAllLines(filename);
List<string> linesICareABout = new List<string>();
for(int i = 0; !linesICareAbout[i].Contains("part2=002"); ++i)
{
linesICareABout.Add(lines[i]);
}
Then do whatever you want with the lines you read in.
However, if you're really dedicated to using Linq, try TakeWhile
http://msdn.microsoft.com/en-us/library/bb534804.aspx

How to use Linq aggregate with single quotes

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 ,.

Categories