Efficiency of using LINQ, USING with StreamReader and File classes in C# - c#

All I have is a file and a string to be searched inside it
Key
Value1
Value2
Value3
Key1
Value1
This is the structure of the file. Now, I search for a key and then read all values under it
until I find a newline (or simply a empty line)
I use this algorithm.
var valuelist = new List<string>();
using(var reader = new StreamReader(#"c:\test.txt"))
{
String a;
while( (a=reader.ReadLine())!=null)
{
if(!a.Equals("Key")) continue;
while( a == reader.ReadLine() != null) //check whether end of file is not reached.
{
if(a.Length == 0) break; //a empty line is reached.hence comeout.
valuelist.add(a);
}
}
}
I am using "using" because it automatically disposes the "reader" object ? Is my approach right in this case ?
How Can I use a LINQ expression here in this context ?
I tried the following code
var all_lines = File.ReadAllLines(#"C:\test.txt");
//How to retrieve "Values" for a given key using LINQ ?

The use of using in this context is very appropriate
To retrieve the values for a given key, you can use SkipWhile looking for the key by name, followed by TakeWhile looking for a blank line.
var list = new List<string>{
"junk", " a", " b", "", "key", " c", " d", " e", "", "more", "f"
};
var vals = list.SkipWhile(s => s != "key").Skip(1).TakeWhile(s => s != "");
foreach (var s in vals) {
Console.WriteLine(s);
}

Related

Search multiple words in a text file

I made a code to search for several words in a text file but only the last word is searched, I would like to solve it
code:
string txt_text;
string[] words = {
"var",
"bob",
"for",
"example"
};
StreamReader file = new StreamReader("test.txt");
foreach(string _words in words) {
while ((txt_text = file.ReadToEnd()) != null) {
if (txt_text.Contains(_words)) {
textBox1.Text = "founded";
break;
} else {
textBox1.Text = "nothing founded";
break;
}
}
}
First of all, you can get rid of StreamReader and loop and query the file with a help of Linq
using System.Linq;
using System.IO;
...
textBox1.Text = File
.ReadLines("test.txt")
.Any(line => words.Any(word => line.Contains(word)))
? "found"
: "nothing found";
If you insist on loop, you should drop else:
// using - do not forget to Dispose IDisposable
using StreamReader file = new StreamReader("test.txt");
// shorter version is
// string txt_text = File.ReadAllText("test.txt");
string txt_text = file.ReadToEnd();
bool found = false;
foreach (string word in words)
if (txt_text.Contains(word)) {
// If any word has been found, stop further searching
found = true;
break;
} // no else here: keep on looping for other words
textBox1.Text = found
? "found"
: "nothing found";
I'd save the text in a variable and then loop over your words to check if it exists in the file. Something like this:
string[] words = { "var", "bob", "for", "example"};
var text = file.ReadToEnd();
List<string> foundWords = new List<string>();
foreach (var word in words)
{
if (text.Contains(word))
foundWords.Add(word);
}
Then, the list foundWords contains all matching words.
(PS: Don't forget to put your StreamReader in a using statement so it gets disposed correctly)

How can I specify region of reading from file?

I have the following txt file
//test.txt
information needed[12334,56565]important numbers
I want to read from [ until ]
string print= File.ReadAllText(#"C:/Users/kokos/Desktop/test.txt");
Console.WriteLine(print);
The above is reading the whole file, but i want to print only
[12334,56565]
string pattern = #"\[(.*?)\]";
string print = File.ReadAllText(#"C:/Users/kokos/Desktop/test.txt");
var result = Regex.Matches(print, pattern);
foreach (Match r in result)
{
Console.WriteLine(r.Groups[1]);
}
As mentioned by Matthew, here is a solution using regex. At the top of your .cs. Add the line: using System.Text.RegularExpressions;
Note
This answer assumes the OP desires to load in the entire file to memory.
You can do this with LINQ.
var text = File.ReadAllText(#"C:/Users/kokos/Desktop/test.txt");
var print = new string(text.SkipWhile(c => c != '[')
.TakeWhile(c => c != ']')
.ToArray())+"]";
// print = "[12334,56565]"
... if you don't want the leading [ then do this...
var print = new string(text.SkipWhile(c => c != '[').Skip(1)
.TakeWhile(c => c != ']')
.ToArray());
// print = "12334,56565"
Here are a few more options if you just want to mess around with the string. (these are more error prone.)
var print = text.Substring(text.IndexOf('['), text.IndexOf(']') - text.IndexOf('[') + 1);
... or ...
var print = "[" + text.Split('[')[1].Split(']')[0] + "]";
... regex would probably look nicer.
var data = Encoding.UTF8.GetBytes("here is a simulated file [here's the data I'm after]");
var read = new StringBuilder();
var inScope = false;
using(var ms = new MemoryStream(data)) {
using(var sr = new StreamReader(ms)) {
while(!sr.EndOfStream) {
var by = sr.Read();
if (((char)by) == '[') {
inScope = true;
continue;
}
else if (((char)by) == ']') {
inScope = false;
break;
}
if (inScope) {
read.Append((char)by);
}
}
}
}
read.ToString().Dump();
The above code is a LINQPad snippet, that shows how you can read a stream byte by byte and pull out the data you're after without loading the whole thing into memory.
Instead of using a memory stream, just use a file stream for the file you want to read.
This is less than optimal with all the casting (just do it once), but it should be enough to demonstrate the basic idea.
The output of this is: "here's the data I'm after"
WARNING be sure to use the encoding object for whichever encoding your file is using!

Copying CSV file while reordering/adding empty columns

Copying CSV file while reordering/adding empty columns.
For example if ever line of incoming file has values for 3 out of 10 columns in order different from output like (except first which is header with column names):
col2,col6,col4 // first line - column names
2, 5, 8 // subsequent lines - values for 3 columns
and output expected to have
col0,col1,col2,col3,col4,col5,col6,col7,col8,col9
then output should be "" for col0,col1,col3,col5,col7,col8,col9,and values from col2,col4,col4 in the input file. So for the shown second line (2,5,8) expected output is ",,2,,5,,8,,,,,"
Below code I've tried and it is slower than I want.
I have two lists.
The first list filecolumnnames is created by splitting a delimited string (line) and this list gets recreated for every line in the file.
The second list list has the order in which the first list needs to be rearranged and re concatenated.
This works
string fileName = "F:\\temp.csv";
//file data has first row col3,col2,col1,col0;
//second row: 4,3,2,1
//so on
string fileName_recreated = "F:\\temp_1.csv";
int count = 0;
const Int32 BufferSize = 1028;
using (var fileStream = File.OpenRead(fileName))
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize))
{
String line;
List<int> list = new List<int>();
string orderedcolumns = "\"\"";
string tableheader = "col0,col1,col2,col3,col4,col5,col6,col7,col8,col9,col10";
List<string> tablecolumnnames = new List<string>();
List<string> filecolumnnames = new List<string>();
while ((line = streamReader.ReadLine()) != null)
{
count = count + 1;
StringBuilder sb = new StringBuilder("");
tablecolumnnames = tableheader.Split(',').ToList();
if (count == 1)
{
string fileheader = line;
//fileheader=""col2,col1,col0"
filecolumnnames = fileheader.Split(',').ToList();
foreach (string col in tablecolumnnames)
{
int index = filecolumnnames.IndexOf(col);
if (index == -1)
{
sb.Append(",");
// orderedcolumns=orderedcolumns+"+\",\"";
list.Add(-1);
}
else
{
sb.Append(filecolumnnames[index] + ",");
//orderedcolumns = orderedcolumns+ "+filecolumnnames["+index+"]" + "+\",\"";
list.Add(index);
}
// MessageBox.Show(orderedcolumns);
}
}
else
{
filecolumnnames = line.Split(',').ToList();
foreach (int items in list)
{
//MessageBox.Show(items.ToString());
if (items == -1)
{
sb.Append(",");
}
else
{
sb.Append(filecolumnnames[items] + ",");
}
}
//expected format sb.Append(filecolumnnames[3] + "," + filecolumnnames[2] + "," + filecolumnnames[2] + ",");
//sb.Append(orderedcolumns);
var result = String.Join (", ", list.Select(index => filecolumnnames[index]));
}
using (FileStream fs = new FileStream(fileName_recreated, FileMode.Append, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine(sb.ToString());
}
}
I am trying to make it faster by constructing a string orderedcolumns and remove the second for each loop which happens for every row and replace it with constructed string.
so if you uncomment the orderedcolumns string construction orderedcolumns = orderedcolumns+ "+filecolumnnames["+index+"]" + "+\",\""; and uncomment the append sb.Append(orderedcolumns); I am expecting the value inside the constructed string but when I append the orderedcolumns it is appending the text i.e.
""+","+filecolumnnames[3]+","+filecolumnnames[2]+","+filecolumnnames[1]+","+filecolumnnames[0]+","+","+","+","+","+","+","
i.e. I instead want it to take the value inside the filecolumnnames[3] list and not the filecolumnnames[3] name itself.
Expected value: if that line has 1,2,3,4
I want the output to be 4,3,2,1 as filecolumnnames[3] will have 4, filecolumnnames[2] will have 3..
String.Join is the way to construct comma/space delimited strings from sequence.
var result = String.Join (", ", list.Select(index => filecolumnnames[index]);
Since you are reading only subset of columns and orders in input and output don't match I'd use dictionary to hold each row of input.
var row = tablecolumnnames
.Zip(line.Split(','), (Name,Value)=> new {Name,Value})
.ToDictionary(x => x.Name, x.Value);
For output I'd fill sequence from defaults or input row:
var outputLine = String.Join(",",
filecolumnnames
.Select(name => row.ContainsKey(name) ? row[name] : ""));
Note code is typed in and not compiled.
orderedcolumns = orderedcolumns+ "+filecolumnnames["+index+"]" + "+\",\""; "
should be
orderedcolumns = orderedcolumns+ filecolumnnames[index] + ",";
you should however use join as others have pointed out. Or
orderedcolumns.AppendFormat("{0},", filecolumnnames[index]);
you will have to deal with the extra ',' on the end

Getting Data From IEnumerable

I have text file which contains airport Codes in this format:
"AAA","","Anaa Arpt","PF","","","AAA","2","N","272"
I used a StreamReader to to read the line from file and then I add that line to string list finally I convert that list to IEnumerable type.
Can you please help me how could I get only three values from each line for example
AAA is airportCode
Anna Arpt airport name
PF is country Code
I want to get only these three values from each row.
Please find below the code.
using (StreamReader sr = new StreamReader("C:/AirCodes/RAPT.TXT"))
{
String line;
while ((line = sr.ReadLine()) != null)
{
aircodesFromTravelPort.Add(line);
Console.WriteLine(line);
}
}
var codes = (IEnumerable<String>)aircodesFromTravelPort;
foreach (var aircode in codes)
It seems that you can try using Linq, something like that:
var codes = File
.ReadLines(#"C:/AirCodes/RAPT.TXT")
.Select(line => line.Split(','))
.Select(items => new {
// I've created a simple anonymous class,
// you'd probably want to create you own one
Code = items[0].Trim('"'), //TODO: Check numbers
Airport = items[2].Trim('"'),
Country = items[3].Trim('"')
})
.ToList();
...
foreach(var item in codes)
Console.WriteLine(item);
You'll probably want to make use of String's Split function on each line to get the values into an array.
while ((line = sr.ReadLine()) != null)
{
var values = line.Split(","); // here you have an array of strings containing the values between commas
var airportCode = values[0];
var airportName = values[2];
var airportCountry = values[3];
var airportInfo = airportCode + "," + airportName + "," + airportCountry;
aircodesFromTravelPort.Add(airportInfo );
// what you actually do with the values is up to you, I just tried to make it as close to the original as possible.
Console.WriteLine(airportInfo);
}
Hope this helps!
I like Regex with named groups:
var line = #"""AAA"","""",""Anaa Arpt"",""PF"","""","""",""AAA"",""2"",""N"",""272""";
var pattern = #"^""(?<airportCode>\w+)"",""(\w*)"",""(?<ariportName>[\w\s]+)"",""(?<cuntryCode>\w+)""";
Match match = Regex.Match(line, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
string airportCode = match.Groups["airportCode"].Value;
string ariportName = match.Groups["ariportName"].Value;
string cuntryCode = match.Groups["cuntryCode"].Value;
}

StreamReader case sensitive

My program currently reads a text file and compares it with the value in a text box and then tells me how many matches, this currently works.
My query is that it is case sensitive. Is there any way to make it so it doesn't matter whether it is in upper or lower case?
This is my code below:
if (!String.IsNullOrEmpty(CustodianEAddress.Text))
{
for (AddressLength1 = 0; AddressLength1 < Length; AddressLength1++)
{
List<string> list1 = new List<string>();
using (StreamReader reader = new StreamReader(FileLocation))
{
string line1;
//max 500
string[] LineArray1 = new string[500];
while ((line1 = reader.ReadLine()) != null)
{
list1.Add(line1); // Add to list.
if (line1.IndexOf(cust1[AddressLength1].ToString()) != -1)
{
count1++;
LineArray1[count1] = line1;
}
}
reader.Close();
using (System.IO.StreamWriter filed =
new System.IO.StreamWriter(FileLocation, true))
{
filed.WriteLine("");
filed.WriteLine("The email address " +
cust1[AddressLength1].ToString() + " was found " + count1 +
" times within the recipient's inbox");
}
string count1a;
count1a = count1.ToString();
}
}
}
else
{
MessageBox.Show("Please Enter an Email Address");
}
So basically, I need to compare the value in cust1[AddressLength1] with any values found in an array which is in the text file.
String.Compare() takes in an optional parameter that let's you specify whether or not the equality check should be case sensitive.
Edited in response to code being posted
Compare and Index of both take in an optional enumeration, StringComparison. If you choose StringComparison.OrdinalIgnoreCase then case will be ignored.
Here's a quick way to compare two strings without checking case:
string a;
string b;
string.Compare(a, b, true);
The true here is passed as the value of the ignoreCase parameter, meaning that upper and lower-case letters will be compared as if they were all the same case.
EDIT:
I've cleaned up your code a bit, and also put in the compare function. I included comments where I changed stuff:
// Not needed: see below. List<string> list1 = new List<string>();
using (StreamReader reader = new StreamReader(FileLocation))
{
string line1;
//max 500
List<string> LineArray1 = new List<string>();
while ((line1 = reader.ReadLine()) != null)
{
// list1.Add(line1); // Add to list.
// By adding to the list, then searching it, you are searching the whole list for every single new line - you're searching through the same elements multiple times.
if (string.Compare(line1, cust1[AddressLength1].ToString(), true) == 0)
{
// You can just use LineArray1.Count for this instead. count1++;
LineArray1.Add(line1);
}
}
// Not needed: using() takes care of this. reader.Close();
using (System.IO.StreamWriter filed =
new System.IO.StreamWriter(FileLocation, true))
{
filed.WriteLine(); // You don't need an empty string for a newline.
filed.WriteLine("The email address " +
cust1[AddressLength1].ToString() + " was found " + LineArray1.Count +
" times within the recipient's inbox");
}
string count1a;
count1a = LineArray1.Count.ToString();
}
The fact you are reading from a file or not it does not matter, when compare
use the static string Comapare function:
public static int Compare(
string strA,
string strB,
bool ignoreCase
)
and pass true as a last parameter.

Categories