randomly select a line in a textfile c# streamreader - c#

Hi I am reading from a text file and would like each line to be put into a seperate variable. From what I remember from my programming classes arrays cannot be dynamic. So if I set 15 arrays, and the text file has 1000 lines what can I do and how do I implement it.
The thing is only one line will be needed but I want the line to be randomly selected. the linetext is the whole text file with \r\n appended to the end of every request.
Maybe randomly select the \r\n then count 4 and add the string after it till the next \r\n. The problem with this idea is the strings getting called will also contain \ so any ideas?
if (crawler == true)
{
TextReader tr = new StreamReader("textfile.txt");
while (tr.Peek() != -1)
{
linktext = linktext + tr.ReadLine() + "\r\n";
}
//link = linktext;
hi.Text = linktext.ToString();
timer1.Interval = 7000; //1000ms = 1sec 7 seconds per cycle
timer1.Tick += new EventHandler(randomLink); //every cycle randomURL is called.
timer1.Start(); // start timer.
}

File.ReadAllLines(...) will read every line of the given file into an array of strings. I think that should be what you want but your question is kind of hard to follow.

You don't need to keep more than two lines in memory at a time... there's a sneaky trick you can use:
Create an instance of Random, or take one as a parameter
Read the first line. This automatically becomes the "current" line to return
Read the second line, and then call Random.Next(2). If the result is 0, make the second line the "current" line
Read the third line, and then call Random.Next(3). If the result is 0, make the third line the "current" line
... etc
When you reach the end of the file (reader.ReadLine returns null) return the "current" line.
Here's a general implementation for an IEnumerable<T> - if you're using .NET 4, you can use File.ReadLines() to get an IEnumerable<string> to pass to it. (This implementation has a bit more in it than is really needed - it's optimized for IList<T> etc.)
public static T RandomElement<T>(this IEnumerable<T> source,
Random random)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (random == null)
{
throw new ArgumentNullException("random");
}
ICollection collection = source as ICollection;
if (collection != null)
{
int count = collection.Count;
if (count == 0)
{
throw new InvalidOperationException("Sequence was empty.");
}
int index = random.Next(count);
return source.ElementAt(index);
}
ICollection<T> genericCollection = source as ICollection<T>;
if (genericCollection != null)
{
int count = genericCollection.Count;
if (count == 0)
{
throw new InvalidOperationException("Sequence was empty.");
}
int index = random.Next(count);
return source.ElementAt(index);
}
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
throw new InvalidOperationException("Sequence was empty.");
}
int countSoFar = 1;
T current = iterator.Current;
while (iterator.MoveNext())
{
countSoFar++;
if (random.Next(countSoFar) == 0)
{
current = iterator.Current;
}
}
return current;
}
}

A List<T> is a dynamically expanding list. You might want to use that instead of an array.
If there is only 1000 elements, just read them into the list and select a random element.

Regarding the array thing.. you could use a List<> instead, which is dynamic
Here is an example of how this can be achieved:
public static string GetRandomLine(ref string file) {
List<string> lines = new List<string>();
Random rnd = new Random();
int i = 0;
try {
if (File.Exists(file)) {
StreamReader reader = new StreamReader(file);
while (!(reader.Peek() == -1))
lines.Add(reader.ReadLine());
i = rnd.Next(lines.Count);
reader.Close();
reader.Dispose();
return lines[i].Trim();
}
else {
return string.Empty;
}
}
catch (IOException ex) {
MessageBox.Show("Error: " + ex.Message);
return string.Empty;
}
}

If you create the file then the ideal way would be to store meta data about the file, like the number of lines, before hand, and then decide which 'random' line to choose.
Otherwise, you cant get around the "array" problem by not using them. Instead use a List which stores any number of strings. After that picking a random one is as simple as generating a random number between 0 and the size of the list.
Your problem has been done before, I recommend googling for "C# read random line from file".

Related

Creating a List in an if statement and adding onto it in the else

A bit new to Stack + C#, sorry if this question has unfamiliar wording.
I'm trying to create a program that will have an input of plain text separated by lines.
If the line is a specific keyword (in this case "group") it'll create a new List. In the else statement, it'll assign the text to the list until the reader reaches another "group".
How could I add to grouping[0] and grouping[1]... etc. through my else statement?
The current error I get is "Object reference not set to an instance of an object." I'm assuming I am doing something wrong with the "n" variable, but I'm not sure what.
while ((line = Console.ReadLine()) != null) {
List<String>[] grouping = new List<String>[20];
int count = 0;
int n = 0;
if (line == "group"){
n++;
grouping[n] = new List<String>();
}
else {
grouping[n].Add(line);
}}
TLDR: What am I doing wrong?
You should move indexer n, and array declaration out of loop, so that values remain after each iteration. also indexes are 0 based in c#. so assign values to array from index 0, not 1.
int n = -1;
List<string>[] grouping = new List<string>[20];
while ((line = Console.ReadLine()) != null)
{
if (line == "group"){
n++;
grouping[n] = new List<string>();
}
else
{
grouping[n].Add(line);
}
}

Reading the 2 last line from a text

i am new to c# and i am working on an app that display the time difference from two date on the last two line on a text file.
I want to read the before last line from a file text, i already know how to read the last line but i need to read the before last.
This is my code :
var lastLine = File.ReadAllLines("C:\\test.log").Last();
richTextBox1.Text = lastLine.ToString();
All the previous answers eagerly load all the file up in memory before returning the requested last lines. This can be an issue if the file is big. Luckily, it is easily avoidable.
public static IEnumerable<string> ReadLastLines(string path, int count)
{
if (count < 1)
return Enumerable.Empty<string>();
var queue = new Queue<string>(count);
foreach (var line in File.ReadLines(path))
{
if (queue.Count == count)
queue.Dequeue();
queue.Enqueue(line);
}
return queue;
}
This will only keep in memory the last n read lines avoiding memory issues with large files.
Since
File.ReadAllLines("C:\\test.log");
returns an array you can take the last two items of the array:
var data = File.ReadAllLines("C:\\test.log");
string last = data[data.Length - 1];
string lastButOne = data[data.Length - 2];
In general case with long files (and that's why ReadAllLines is a bad choice) you can implement
public static partial class EnumerableExtensions {
public static IEnumerable<T> Tail<T>(this IEnumerable<T> source, int count) {
if (null == source)
throw new ArgumentNullException("source");
else if (count < 0)
throw new ArgumentOutOfRangeException("count");
else if (0 == count)
yield break;
Queue<T> queue = new Queue<T>(count + 1);
foreach (var item in source) {
queue.Enqueue(item);
if (queue.Count > count)
queue.Dequeue();
}
foreach (var item in queue)
yield return item;
}
}
...
var lastTwolines = File
.ReadLines("C:\\test.log") // Not all lines
.Tail(2);
You can try to do this
var lastLines = File.ReadAllLines("C:\\test.log").Reverse().Take(2).Reverse();
But depending on how large your file is there are probably more efficient methods to process this than reading all lines at once. See Get last 10 lines of very large text file > 10GB and How to read last “n” lines of log file
Simply store the result of ReadAllLines to a variable and than take the two last ones:
var allText = File.ReadAllLines("C:\\test.log");
var lastLines = allText.Skip(allText.Length - 2);
You can use Skip() and Take() like
var lastLine = File.ReadAllLines("C:\\test.log");
var data = lastLine.Skip(lastLine.Length - 2);
richTextBox1.Text = lastLine.ToString();
You can use StreamReader in a combination of Queue<string> since you have to read whole file either way.
// if you want to read more lines change this to the ammount of lines you want
const int LINES_KEPT = 2;
Queue<string> meQueue = new Queue<string>();
using ( StreamReader reader = new StreamReader(File.OpenRead("C:\\test.log")) )
{
string line = string.Empty;
while ( ( line = reader.ReadLine() ) != null )
{
if ( meQueue.Count == LINES_KEPT )
meQueue.Dequeue();
meQueue.Enqueue(line);
}
}
Now you can just use these 2 lines like such :
string line1 = meQueue.Dequeue();
string line2 = meQueue.Dequeue(); // <-- this is the last line.
Or to add this to the RichTextBox :
richTextBox1.Text = string.Empty; // clear the text
while ( meQueue.Count != 0 )
{
richTextBox1.Text += meQueue.Dequeue(); // add all lines in the same order as they were in file
}
Using File.ReadAllLines will read the whole text and then using Linq will iterate through already red lines. This method does everything in one run.
string line;
string[] lines = new string[]{"",""};
int index = 0;
using ( StreamReader reader = new StreamReader(File.OpenRead("C:\\test.log")) )
{
while ( ( line = reader.ReadLine() ) != null )
{
lines[index] = line;
index = 1-index;
}
}
// Last Line -1 = lines[index]
// Last line = lines[1-index]

How to Stream string data from a txt file into an array

I'm doing this exercise from a lab. the instructions are as follows
This method should read the product catalog from a text file called “catalog.txt” that you should
create alongside your project. Each product should be on a separate line.Use the instructions in the video to create the file and add it to your project, and to return an
array with the first 200 lines from the file (use the StreamReader class and a while loop to read
from the file). If the file has more than 200 lines, ignore them. If the file has less than 200 lines,
it’s OK if some of the array elements are empty (null).
I don't understand how to stream data into the string array any clarification would be greatly appreciated!!
static string[] ReadCatalogFromFile()
{
//create instance of the catalog.txt
StreamReader readCatalog = new StreamReader("catalog.txt");
//store the information in this array
string[] storeCatalog = new string[200];
int i = 0;
//test and store the array information
while (storeCatalog != null)
{
//store each string in the elements of the array?
storeCatalog[i] = readCatalog.ReadLine();
i = i + 1;
if (storeCatalog != null)
{
//test to see if its properly stored
Console.WriteLine(storeCatalog[i]);
}
}
readCatalog.Close();
Console.ReadLine();
return storeCatalog;
}
Here are some hints:
int i = 0;
This needs to be outside your loop (now it is reset to 0 each time).
In your while() you should check the result of readCatalog() and/or the maximum number of lines to read (i.e. the size of your array)
Thus: if you reached the end of the file -> stop - or if your array is full -> stop.
static string[] ReadCatalogFromFile()
{
var lines = new string[200];
using (var reader = new StreamReader("catalog.txt"))
for (var i = 0; i < 200 && !reader.EndOfStream; i++)
lines[i] = reader.ReadLine();
return lines;
}
A for-loop is used when you know the exact number of iterations beforehand. So you can say it should iterate exactly 200 time so you won't cross the index boundaries. At the moment you just check that your array isn't null, which it will never be.
using(var readCatalog = new StreamReader("catalog.txt"))
{
string[] storeCatalog = new string[200];
for(int i = 0; i<200; i++)
{
string temp = readCatalog.ReadLine();
if(temp != null)
storeCatalog[i] = temp;
else
break;
}
return storeCatalog;
}
As soon as there are no more lines in the file, temp will be null and the loop will be stopped by the break.
I suggest you use your disposable resources (like any stream) in a using statement. After the operations in the braces, the resource will automatically get disposed.

find all next lines when previous line contains a string

I'm working on an ASP mvc application and i'm trying to get all the next lines when previous line contains a word
I've used the code below but i just can get the last line that contains the word given
int counter = 0;
string line;
List<string> found = new List<string>();
// Read the file and display it line by line.
System.IO.StreamReader file = new System.IO.StreamReader("C:\\Users\\Chaimaa\\Documents\\path.txt");
while ((line = file.ReadLine()) != null)
{
if (line.Contains("fact"))
{
found.Add(line);
}
foreach (var i in found)
{
var output = i;
ViewBag.highlightedText = output;
}
}
Any help on what should I add to
1- get ALL lines that contains the word
2- and preferably get the ALL NEXT lines
You can use an overload of Where that provides an index, store indexes in a hash set, and use the containment check to decide if a line should be kept or not, like this:
var seen = new HashSet<int>();
var res = data.Where((v, i) => {
if (v.Contains("fact")) {
seen.Add(i);
}
return seen.Contains(i-1);
});
Demo.
As a side benefit, seen would contain indexes of all lines where the word "fact" has been found.
You can write a PairWise method that takes in a sequence of values and returns a sequence containing each item paired with the item that came before it:
public static IEnumerable<Tuple<T, T>> Pairwise<T>(this IEnumerable<T> source)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
T prev = iterator.Current;
while (iterator.MoveNext())
{
yield return Tuple.Create(prev, iterator.Current);
prev = iterator.Current;
}
}
}
With this method we can pair off each of the lines, get the lines where the previous value contains a word, and then project out the second value, which is the line after it:
var query = lines.Pairwise()
.Where(pair => pair.Item1.Contains(word))
.Select(pair => pair.Item2);

OutOfMemoryException when updating a large list?

I have a large list and I would like to overwrite one value if required. To do this, I create two subsets of the list which seems to give me an OutOfMemoryException. Here is my code snippet:
if (ownRG != "")
{
List<string> maclist = ownRG.Split(',').ToList();
List<IVFile> temp = powlist.Where(a => maclist.Contains(a.Machine)).ToList();
powlist = powlist.Where(a => !maclist.Contains(a.Machine)).ToList(); // OOME Here
temp.ForEach(a => { a.ReportingGroup = ownRG; });
powlist.AddRange(temp);
}
Essentially I'm splitting the list into the part that needs updating and the part that doesn't, then I perform the update and put the list back together. This works fine for smaller lists, but breaks with an OutOfMemoryException on the third row within the if for a large list. Can I make this more efficient?
NOTE
powlist is the large list (>1m) items. maclist only has between 1 and 10 but even with 1 item this breaks.
Solving your issue
Here is how to rearrange your code using the enumerator code from my answer:
if (!string.IsNullOrEmpty(ownRG))
{
var maclist = new CommaSeparatedStringEnumerable(str);
var temp = powlist.Where(a => maclist.Contains(a.Machine));
foreach (var p in temp)
{
p.ReportingGroup = ownRG;
}
}
You should not use ToList in your code.
You don't need to remove thee contents of temp from powlist (you are re-adding them anyway)
Streaming over a large comma-separated string
You can iterate over the list manually instead of doing what you do now, by looking for , characters and remembering the position of the last found one and the one before. This will definitely make your app work because then it won't need to store the entire set in the memory at once.
Code example:
var str = "aaa,bbb,ccc";
var previousComma = -1;
var currentComma = 0;
for (; (currentComma = str.IndexOf(',', previousComma + 1)) != -1; previousComma = currentComma)
{
var currentItem = str.Substring(previousComma + 1, currentComma - previousComma - 1);
Console.WriteLine(currentItem);
}
var lastItem = str.Substring(previousComma + 1);
Console.WriteLine(lastItem);
Custom iterator
If you want to do it 'properly' in a fancy way, you can even write a custom enumerator:
public class CommaSeparatedStringEnumerator : IEnumerator<string>
{
int previousComma = -1;
int currentComma = -1;
string bigString = null;
bool atEnd = false;
public CommaSeparatedStringEnumerator(string s)
{
if (s == null)
throw new ArgumentNullException("s");
bigString = s;
this.Reset();
}
public string Current { get; private set; }
public void Dispose() { /* No need to do anything here */ }
object IEnumerator.Current { get { return this.Current; } }
public bool MoveNext()
{
if (atEnd)
return false;
atEnd = (currentComma = bigString.IndexOf(',', previousComma + 1)) == -1;
if (!atEnd)
Current = bigString.Substring(previousComma + 1, currentComma - previousComma - 1);
else
Current = bigString.Substring(previousComma + 1);
previousComma = currentComma;
return true;
}
public void Reset()
{
previousComma = -1;
currentComma = -1;
atEnd = false;
this.Current = null;
}
}
public class CommaSeparatedStringEnumerable : IEnumerable<string>
{
string bigString = null;
public CommaSeparatedStringEnumerable(string s)
{
if (s == null)
throw new ArgumentNullException("s");
bigString = s;
}
public IEnumerator<string> GetEnumerator()
{
return new CommaSeparatedStringEnumerator(bigString);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Then you can iterate over it like this:
var str = "aaa,bbb,ccc";
var enumerable = new CommaSeparatedStringEnumerable(str);
foreach (var item in enumerable)
{
Console.WriteLine(item);
}
Other thoughts
Can I make this more efficient?
Yes, you can. I suggest to either work with a more efficient data format (you can take a look around databases or XML, JSON, etc. depending on your needs). If you really want to work with comma-separated items, see my code examples above.
There's no need to create a bunch of sub-lists from powlist and reconstruct it. Simply loop over the powlist and update the ReportingGroup property accordingly.
var maclist = new HashSet<string>( ownRG.Split(',') );
foreach( var item in powlist) {
if( maclist.Contains( item.Machine ) ){
item.ReportingGroup = ownRG;
}
}
Since this changes powlist in place, you won't allocate any extra memory and shouldn't run into an OutOfMemoryException.
In a loop find the next ',' char. Take the substring between the ',' and the previous ',' position. At the end of the loop save a reference to the previous ',' position (which is initially set to 0). So you parse the items one-by-one rather than all at once.
You can try looping the items of your lists, but this will increase processing time.
foreach(var item in powlist)
{
//do your opeartions
}

Categories

Resources