C# index out of range String Array and List<string> - c#

After trying a few error checking methods I have come to the conclusion I need help solving this problem.
How am I not catch this "index out of range" error. What can I do to avoid this problem in the future for good practice?
public void loadFromFile()
{
OpenFileDialog oFile = new OpenFileDialog();
oFile.Title = "Open text file";
oFile.Filter = "Text Files (*.txt)|*.txt|All Files (*.*)|*.*";
oFile.FilterIndex = 1;
oFile.InitialDirectory = Application.StartupPath;
oFile.AddExtension = true;
oFile.CheckFileExists = true;
oFile.CheckPathExists = true;
// Open and clean Duplicates
String[] lines;
List<string> temp = new List<string>();
List<string> newlist = new List<string>();
if(oFile.ShowDialog() == DialogResult.OK)
{
// Dummy file has 6 lines of text. Filename:DuplicatFile.txt
// 3 duplicate lines and 3 not.
lines = File.ReadAllLines(oFile.FileName, System.Text.Encoding.UTF8);
// Copy array to temporary array
for (int index=0; index < lines.Length; index++)
{
// System.ArgumentOutOfRangeException was unhandled
// Index was out of range. Must be non-negative and less than the size of the collection.
if (lines[index].Length >= 0)
{
temp[index] = lines[index];
}
}
// Check for duplicates. If duplicate ignore if non-duplicate add to list.
foreach (string line in temp)
{
if (!newlist.Contains(line))
{
newlist.Add(line);
}
}
// Clear listbox and add new list to listbox.
lstBox.Items.Clear();
foreach (string strNewLine in newlist)
{
lstBox.Items.Add(strNewLine);
}
}
}

List<string> temp = new List<string>();
...
temp[index] = lines[index];
temp starts out with 0 size. Any index is out of range.
You can fix this by using temp.Add, to make the list grow dynamically:
temp.Add(lines[index]);

Mud has the correct answer for the ArgumentOutOfRangeException. You can simplify all the code in your if statement with Linq to be the following:
lines = File.ReadAllLines(oFile.FileName, System.Text.Encoding.UTF8);
lstBox.Items.AddRange(lines.Distinct().ToArray());

The problem isn't that the "lines" index is out of range - it's that the "temp" index is out of range... you have created a new List called "temp" but there is nothing in it.. it's length is 0!
Instead of copying from one index to another, you should use the .Add method:
temp.Add(lines[index])
of course... there better ways to duplicate an array, but this is closest to what you present above and answers your question directly.

You get that error, because there is no elements at that index in list temp. (temp is empty). You can fill it with temp.Add(value).
Another way to create temp list is to use temp = newlist.ToList().
I would suggest to use LINQ: You can use
lstBox.Items.Clear();
foreach (var line in lines.Distinct())
lstBox.Items.Add(line);
instead of all this code:
// Copy array to temporary array
for (int index=0; index < lines.Length; index++)
{
// System.ArgumentOutOfRangeException was unhandled
// Index was out of range. Must be non-negative and less than the size of the collection.
if (lines[index].Length >= 0)
{
temp[index] = lines[index];
}
}
// Check for duplicates. If duplicate ignore if non-duplicate add to list.
foreach (string line in temp)
{
if (!newlist.Contains(line))
{
newlist.Add(line);
}
}
lstBox.Items.Clear();
foreach (string strNewLine in newlist)
{
lstBox.Items.Add(strNewLine);
}
to simple:

Related

How do I take each element off of a list and push it onto a stack?

string filePath = #"C:\Users\Me\Desktop\Palindromes\palindromes.txt";
List<string> lines = File.ReadAllLines(filePath).ToList();
var meStack = new Stack<string>();
for (int i = 0; i < lines.Count; i++)
{
string pali;
pali = lines.RemoveAt(i);
meStack.Push(pali[i]);
}
Basically I need to Remove each element (in the txt there are 40 lines) from the list and then Push each one onto a stack.
Why even make a list List<String>? ReadAllLines responds with a String[]. And Stack takes an array as constructor parameter... So, would code below do the job for you?
string filePath = #"C:\Users\Me\Desktop\Palindromes\palindromes.txt";
var meStack = new Stack<string>(File.ReadAllLines(filePath));
Do not RemoveAt but Clear (if necessary) the lines list at the very end:
for (int i = 0; i < lines.Count; ++i)
meStack.Push(lines[i]);
lines.Clear();
Or even (we can get rid of list at all):
string filePath = #"C:\Users\Me\Desktop\Palindromes\palindromes.txt";
var meStack = new Stack<string>();
foreach (var item in File.ReadLines(filePath))
meStack.Push(item);
You can simplify it to
lines.ForEach(meStack.Push);
lines.Clear();
Your code with some comments:
string filePath = #"C:\Users\Me\Desktop\Palindromes\palindromes.txt";
List<string> lines = File.ReadAllLines(filePath).ToList();
var meStack = new Stack<string>();
for (int i = 0; i < lines.Count; i++)
{
string pali;
pali = lines.RemoveAt(i); // < this will return AND REMOVE the line from the list.
// now, what was line i+1 is now line i, next iteration
// will return and remove (the new) line i+1, though,
// skipping one line.
meStack.Push(pali[i]); // here you push one char (the ith) of the string (the line you
// just removed) to the stack which _may_ cause an
// IndexOutOfBounds! (if "i" >= pali.Length )
}
Now since I do not want to reiterate the other (great) answers, here is one where you can actually use RemoveAt:
while( lines.Count > 0 ) // RemoveAt will decrease Count with each iteration
{
meStack.Push(lines.RemoveAt(0)); // Push the whole line that is returned.
// Mind there is hardcoded "0" -> we always remove and push the first
// item of the list.
}
Which is not the best solution, just another alternative.

Read and process 100 text files in c# in parallel

I have project that reads 100 text file with 5000 words in it.
I insert the words into a list. I have a second list that contains english stop words. I compare the two lists and delete the stop words from first list.
It takes 1 hour to run the application. I want to be parallelize it. How can I do that?
Heres my code:
private void button1_Click(object sender, EventArgs e)
{
List<string> listt1 = new List<string>();
string line;
for (int ii = 1; ii <= 49; ii++)
{
string d = ii.ToString();
using (StreamReader reader = new StreamReader(#"D" + d.ToString() + ".txt"))
while ((line = reader.ReadLine()) != null)
{
string[] words = line.Split(' ');
for (int i = 0; i < words.Length; i++)
{
listt1.Add(words[i].ToString());
}
}
listt1 = listt1.ConvertAll(d1 => d1.ToLower());
StreamReader reader2 = new StreamReader("stopword.txt");
List<string> listt2 = new List<string>();
string line2;
while ((line2 = reader2.ReadLine()) != null)
{
string[] words2 = line2.Split('\n');
for (int i = 0; i < words2.Length; i++)
{
listt2.Add(words2[i]);
}
listt2 = listt2.ConvertAll(d1 => d1.ToLower());
}
for (int i = 0; i < listt1.Count(); i++)
{
for (int j = 0; j < listt2.Count(); j++)
{
listt1.RemoveAll(d1 => d1.Equals(listt2[j]));
}
}
listt1=listt1.Distinct().ToList();
textBox1.Text = listt1.Count().ToString();
}
}
}
}
I fixed many things up with your code. I don't think you need multi-threading:
private void RemoveStopWords()
{
HashSet<string> stopWords = new HashSet<string>();
using (var stopWordReader = new StreamReader("stopword.txt"))
{
string line2;
while ((line2 = stopWordReader.ReadLine()) != null)
{
string[] words2 = line2.Split('\n');
for (int i = 0; i < words2.Length; i++)
{
stopWords.Add(words2[i].ToLower());
}
}
}
var fileWords = new HashSet<string>();
for (int fileNumber = 1; fileNumber <= 49; fileNumber++)
{
using (var reader = new StreamReader("D" + fileNumber.ToString() + ".txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
foreach(var word in line.Split(' '))
{
fileWords.Add(word.ToLower());
}
}
}
}
fileWords.ExceptWith(stopWords);
textBox1.Text = fileWords.Count().ToString();
}
You are reading through the list of stopwords many times as well as continually adding to the list and re-attempting to remove the same stopwords over and again due to the way your code is structured. Your needs are also better matched to a HashSet than to a List, as it has set based operations and uniqueness already handled.
If you still wanted to make this parallel, you could do it by reading the stopword list once and passing it to an async method that will read the input file, remove the stopwords and return the resulting list, then you would need to merge the resulting lists after the asynchronous calls came back, but you had better test before deciding you need that, because that is quite a bit more work and complexity than this code already has.
If I understand you correctly, you want to:
Read all words from a file into a List
Remove all "stop words" from the List
Repeat for 99 more files, saving only the unique words
If this is correct, the code is pretty simple:
// The list of words to delete ("stop words")
var stopWords = new List<string> { "remove", "these", "words" };
// The list of files to check - you can get this list in other ways
var filesToCheck = new List<string>
{
#"f:\public\temp\temp1.txt",
#"f:\public\temp\temp2.txt",
#"f:\public\temp\temp3.txt"
};
// This list will contain all the unique words from all
// the files, except the ones in the "stopWords" list
var uniqueFilteredWords = new List<string>();
// Loop through all our files
foreach (var fileToCheck in filesToCheck)
{
// Read all the file text into a varaible
var fileText = File.ReadAllText(fileToCheck);
// Split the text into distinct words (splitting on null
// splits on all whitespace) and ignore empty lines
var fileWords = fileText.Split(null)
.Where(line => !string.IsNullOrWhiteSpace(line))
.Distinct();
// Add all the words from the file, except the ones in
// your "stop list" and those that are already in the list
uniqueFilteredWords.AddRange(fileWords.Except(stopWords)
.Where(word => !uniqueFilteredWords.Contains(word)));
}
This can be condensed into a single line with no explicit loop:
// This list will contain all the unique words from all
// the files, except the ones in the "stopWords" list
var uniqueFilteredWords = filesToCheck.SelectMany(fileToCheck =>
File.ReadAllText(fileToCheck)
.Split(null)
.Where(word => !string.IsNullOrWhiteSpace(word) &&
!stopWords.Any(stopWord => stopWord.Equals(word,
StringComparison.OrdinalIgnoreCase)))
.Distinct());
This code processed over 100 files with more than 12000 words each in less than a second (WAY less than a second... 0.0001782 seconds)
One issue I see here that can help improve performance is listt1.ConvertAll() will run in O(n) on the list. You are already looping to add the items to the list, why not convert them to lower case there. Also why not store the words in a hash set, so you can do look up and insertion in O(1). You could store the list of stop words in a hash set and when you are reading your text input see if the word is a stop word and if its not add it to the hash set to output the user.

How do I make the foreach instruction iterate in 2 places?

how do I make the foreach instruction iterate both in the "files" variable and in the "names" array?
var files = Directory.GetFiles(#".\GalleryImages");
string[] names = new string[8] { "Matt", "Joanne", "Robert","Andrei","Mihai","Radu","Ionica","Vasile"};
I've tried 2 options.. the first one gives me lots of errors and the second one displays 8 images of each kind
foreach(var file in files,var i in names)
{
//Do stuff
}
and
foreach(var file in files)
{
foreach (var i in names)
{
//Do stuff
}
}
You can try using the Zip Extension method of LINQ:
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
foreach (var item in numbersAndWords)
Console.WriteLine(item);
Would look something like this:
var files = Directory.GetFiles(#".\GalleryImages");
string[] names = new string[] { "Matt", "Joanne", "Robert", "Andrei", "Mihai","Radu","Ionica","Vasile"};
var zipped = files.Zip(names, (f, n) => new { File = f, Name = n });
foreach(var fn in zipped)
Console.WriteLine(fn.File + " " + fn.Name);
But I haven't tested this one.
It's not clear what you're asking. But, you can't iterate two iterators with foreach; but you can increment another variable in the foreach body:
int i = 0;
foreach(var file in files)
{
var name = names[i++];
// TODO: do something with name and file
}
This, of course, assumes that files and names are of the same length.
You can't. Use a for loop instead.
for(int i = 0; i < files.Length; i++)
{
var file = files[i];
var name = names[i];
}
If the both array have the same length this should work.
You have two options here; the first works if you are iterating over something that has an indexer, like an array or List, in which case use a simple for loop and access things by index:
for (int i = 0; i < files.Length && i < names.Length; i++)
{
var file = files[i];
var name = names[i];
// Do stuff with names.
}
If you have a collection that doesn't have an indexer, e.g. you just have an IEnumerable and you don't know what it is, you can use the IEnumerable interface directly. Behind the scenes, that's all foreach is doing, it just hides the slightly messier syntax. That would look like:
var filesEnum = files.GetEnumerator();
var namesEnum = names.GetEnumerator();
while (filesEnum.MoveNext() && namesEnum.MoveNext())
{
var file = filesEnum.Current;
var name = namesEnum.Current;
// Do stuff with files and names.
}
Both of these assume that both collections have the same number of items. The for loop will only iterate as many times as the smaller one, and the smaller enumerator will return false from MoveNext when it runs out of items. If one collection is bigger than the other, the 'extra' items won't get processed, and you'll need to figure out what to do with them.
I guess the files array and the names array have the same indices.
When this is the case AND you always want the same index at one time you do this:
for (int key = 0; key < files.Length; ++key)
{
// access names[key] and files[key] here
}
You can try something like this:
var pairs = files.Zip(names, (f,n) => new {File=f, Name=n});
foreach (var item in pairs)
{
Console.Write(item.File);
Console.Write(item.Name);
}

Randomly place images in multiple Image controls

I am creating a simple "Pairs" game in WPF. I have 12 Image controls on MainWindow. What I need to do, is to use OpenFileDialog to select multiple Images (can be less then all 6) and then randomly place them into Image controls. Each picture should appear twice. How would I be able to achieve this? I am stuck here for a while and only have following code at the moment. I am not asking for a solution, I only need a few pointers on how to deal with this. Thank you.
> public ObservableCollection<Image> GetImages()
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Multiselect = true;
ObservableCollection<Image> imagesList = new ObservableCollection<Image>();
if (dlg.ShowDialog() == true)
{
foreach (String img in dlg.FileNames)
{
Image image = new Image();
image.Name = "";
image.Location = img;
imagesList.Add(image);
}
}
return imagesList;
}
There are many ways to achieve your required results. A good way would be to use the Directory.GetFiles method, which will return a collection of string file paths:
string [] filePaths = Directory.GetFiles(targetDirectory);
You can then use a method to randomise the order of the collection. From the C# Shuffle Array page on DotNETPerls:
public string[] RandomizeStrings(string[] arr)
{
List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
// Add all strings from array
// Add new random int each time
foreach (string s in arr)
{
list.Add(new KeyValuePair<int, string>(_random.Next(), s));
}
// Sort the list by the random number
var sorted = from item in list
orderby item.Key
select item;
// Allocate new string array
string[] result = new string[arr.Length];
// Copy values to array
int index = 0;
foreach (KeyValuePair<int, string> pair in sorted)
{
result[index] = pair.Value;
index++;
}
// Return copied array
return result;
}
Then add your duplicate file paths, re-randomise the order again and populate your UI property with the items:
string[] filePathsToUse = new string[filePaths.Length * 2];
filePaths = RandomizeStrings(filePaths);
for (int count = 0; count < yourRequiredNumber; count++)
{
filePathsToUse.Add(filePaths(count));
filePathsToUse.Add(filePaths(count));
}
// Finally, randomize the collection again:
ObservableCollection<string> filePathsToBindTo = new
ObservableCollection<string>(RandomizeStrings(filePathsToUse));
Of course, you could also do it in many other ways, some easier to understand, some more efficient. Just pick a method that you feel comfortable with.

How to find an Index of a string in a list

So what I am trying do is retrieve the index of the first item, in the list, that begins with "whatever", I am not sure how to do this.
My attempt (lol):
List<string> txtLines = new List<string>();
//Fill a List<string> with the lines from the txt file.
foreach(string str in File.ReadAllLines(fileName)) {
txtLines.Add(str);
}
//Insert the line you want to add last under the tag 'item1'.
int index = 1;
index = txtLines.IndexOf(npcID);
Yea I know it isn't really anything, and it is wrong because it seems to be looking for an item that is equal to npcID rather than the line that begins with it.
If you want "StartsWith" you can use FindIndex
int index = txtLines.FindIndex(x => x.StartsWith("whatever"));
if your txtLines is a List Type, you need to put it in a loop, after that retrieve the value
int index = 1;
foreach(string line in txtLines) {
if(line.StartsWith(npcID)) { break; }
index ++;
}
Suppose txtLines was filled, now :
List<int> index = new List<int>();
for (int i = 0; i < txtLines.Count(); i++)
{
index.Add(i);
}
now you have a list of int contain index of all txtLines elements. you can call first element of List<int> index by this code : index.First();

Categories

Resources