C# Using ArrayList.IndexOf with multiple identical items - c#

I've got a situation where I'm using an ArrayList to store a list of file names (full file path). When I add multiple items of the same file to the array, then use ArrayList.IndexOf to find the index (I'm reporting progress to a BackgroundWorker), it always returns the index of the first item since it's searching by the file name. This causes a problem with a progress bar, i.e. I'm processing 3 files, and when complete, the bar is only 1/3 full.
Here's some sample code (I'm simply adding items here, but in the real code they are added from a ListBox):
ArrayList list = new ArrayList();
list.Add("C:\Test\TestFile1.txt");
list.Add("C:\Test\TestFile1.txt");
list.Add("C:\Test\TestFile1.txt");
var files = list;
foreach (string file in files)
backgroundWorker1.ReportProgress(files.IndexOf(file) + 1);
When this runs, only 1 "percent" of progress is reported, since the IndexOf is finding the same file every time. Is there any way around this? Or does anyone have a suggestion on another way to get the index (or any unique identifier for each item)?

The simplest approach is just to use the index to iterate:
for (int i = 0; i < list.Count; i++)
{
backgroundWorks.ReportProgress(i + 1);
// Do stuff with list[i]
}

To achieve what you want, I would recomment using a for list. You won't have to search for any indexes and can report the progress easily to the backgroundWorker1:
for (int counter = 0; counter < files.Count; counter++)
{
backgroundWorker1.ReportProgress(counter + 1);
}
By doing this, you don't get problems with same filenames.
This would be the equivalent with foreach:
int counter = 1;
foreach (string file in files)
{
backgroundWorker1.ReportProgress(counter);
counter++;
}
But I think it's better to use for in this case.

You could make a copy of your list, and then remove each item from it as it is processed. Then you could return the percentage complete as a function of the count of the temp list divided by the count of the original list.
You could also just add to an int every time your background workers process a file and use that int to track the progress (percent complete should be the int divided by the number of files).
Of course with either approach you need to make sure you are modifying the variables in question in a thread-safe manner.

Try this and it works fine for me in case of reading files having so many lines of data.
int percentage = (int)((count / (double)lineCount) * 100.0);
backgroundWorker5.ReportProgress(percentage);
Here count is total count and linecount is present running count. By using thedse two,calculate the percentage. Keep this code in your for loop or while loop.

Assuming that progressbar max value is 100, and with one file processed out of three it should show 33(%) progress, you could count progress for each file processed and recalculate overall progress :
// list contains x file paths
var files = list;
double progressStep = 1 / files.Count;
for(int i = 0; i < files.Count; i++)
{
backgroundWorker1.ReportProgress((i + 1) * progressStep);
...
}
All that remained is to assign progress value in background worker to progress bar.

Related

Something working on most systems but not everywhere c#

I have a console application in C# for Windows and RPi (Mono). I make a filelist and store it in an array. Then there are two files which I don't want in that list, so I want to remove those from the list. This works on most systems where the application runs (Windows and RPi's alike) but there is one RPi - so far - where it refuses to delete one of those files from the list. As if it cannot find the file (because I don't get the message within the if-statement). I am looking for possible causes. Any suggestions?
MonthfileList = Directory.GetFiles("data/", "*log.txt");
for (int i=0; i<MonthfileList.Length; i++)
{
if (MonthfileList[i].Contains("alltimelog") )
{
Sup.LogDebugMessage(message: $"MonthfileList removing: {MonthfileList[i]}");
var foos = new List<string>(MonthfileList);
foos.RemoveAt(i);
MonthfileList = foos.ToArray();
}
}
The stated problem could happen if you have two files with the required string stored in your array MonthFileList in two adiacent indexes.
In this case, the logic inside the loop causes the second file to be evaluated.
Suppose you have an array of 5 elements (index 0-4) where at index 2 and 3 there is a file with the searched word.
When the first index is evaluated (2) the file is removed from the list, then you rebuild the array without the removed file, but this has a secondary effect. The file that was at index 3 is now at index 2 and your indexer that was at 2 is incremented to 3, thus the old element, now at index 2, is not evaluated and remains into the array.
Fix is simple. loop backwards
// Invert logic, so you don't need a conversion list/array each time you find a match
var foos = new List<string>(MonthfileList);
for (int i=foos.Count - 1; i >= 0; i--)
{
if (foos[i].Contains("alltimelog") )
{
Sup.LogDebugMessage(message: $"MonthfileList removing: {foos[i]}");
foos.RemoveAt(i);
}
}
MonthfileList = foos.ToArray();
#Steve made the correct hint. I changed the code with i-- to account for the missing entry:
MonthfileList = Directory.GetFiles("data/", "*log.txt");
for (int i=0; i<MonthfileList.Length; i++)
{
if (MonthfileList[i].Contains("alltimelog") )
{
Sup.LogDebugMessage(message: $"MonthfileList removing: {MonthfileList[i]}");
var foos = new List<string>(MonthfileList);
foos.RemoveAt(i--);
MonthfileList = foos.ToArray();
}
}

How to create progressbars with name from string?

As the name implies I'm trying to create progress bars (at runtime). I need this because I have to display in a graphical manner how much, out of 100%, some elements have reached. The elements are extracted from a database and may vary in number so I can't make a fix number, I need a loop that creates the progressbars... I think.
Any way possible?
First Extract all the elements from Database............and then set the Extracted elements count to Progress bar.
You can do st. like this:
progressBar.Step = 1;
progressBar.Minimum = 0;
progressBar.Maximum = elements.Count;
...
for (int index = 0; index < progressBar.Maximum; index++)
{
// Do your work here ...
progressBar.PerformStep();
}

c# ListView.Items[i].remove is very slow

It is my first time here and I am struggling to solve this issue.
I have this piece of code:
try
{
progressBar1.Maximum = lista.Items.Count;
lista.BeginUpdate();
for (int i = 0; lista.Items.Count > i; i++)
//for (int i = lista.Items.Count - 1; -1 < i; i--)
{
if (lista.Items[i].SubItems[1].Text.ToLower().Contains(Text) == false)
{
lista.Items[i].Remove();
}
progressBar1.Value = progressBar1.Value + 1;
}
lista.EndUpdate();
progressBar1.Value = 0;
}
catch (Exception errore)
{
txt_info.Text = "" + errore.Message;
progressBar1.Value = 0;
}
The method lista.items[i].remove is extremely slow.
lista is a ListView and I am working on a log file bigger than 50,000 lines.
Is there anyway to speed up the process?
I would take a different approach and use LINQ, something like this :
lista.Items = lista.Items.Where(x=>x.SubItems[1].Text.ToLower.Contains(Text)).AsParallel().ToList();
Basically, rebuilding the list once rather than trying to remove individual items over and over again.
ListViewItem[] allElements = new ListViewItem[listView1.Items.Count];
listView1.Items.CopyTo(allElements, 0);
List < ListViewItem > list = allElements.ToList();
list.RemoveAll(item => item.SubItems[1].Text.ToLower().Contains(TextToFind) == false);
listView1.BeginUpdate();
listView1.Clear();
listView1.Items.AddRange(list.ToArray());
listView1.EndUpdate();
First Rule is never update list in for loop. Your logic will only run till half of the list. I guess that's not what you want.
I've seen that manipulating listview.items is very slow even after using BeginUpdate and EndUpdate. Key is to do the manipulation outside (in list or so) and then re populate the list with AddRange (which is much faster than Add).
The simplest option would be to use the list's own RemoveAll method.
list.RemoveAll(x => !x.SubItems[1].Text.ToLower().Contains(Text))
P.S.
You might want to look for speed gains in the actual comparison.
Using String.Compare is much faster if your requirement fits it. If you want to check for a sub-string, I would suggest using ToUpperInvariant for invariance related matters - it's designed to be faster.
You could stick it in a background worker and have it do this on it's own. Therefore your users could still use the program while this process is occuring.
If you remove an item in for loop, you should set the counter to 1 less so you won't miss one. Because you remove [i], [old i+1] becomes the [new i] (Items.Count decreases 1 also) and you will miss checking the [new i].
e.g:
ListView.Items.Remove[i];
i--;
Lists are slow to access dynamically and much better suited to iteration. I would suggest manipulating the data outside of the listview (maybe by copying the rows you want to display into a temporary list one at a time as you iterate across the source collection) and then assigning to the listview at the end. This can be done on a different thread (to improve UI performance) and doesn't require expensive lookups.

Looping and changing a list - remove doesn't always work

I'm trying to go through a loop 40 times and changing a list in the process.
This is the code:
for (int i = 0; i < 40; i++)
{
location = rand.Next(rows.Count);
rank = rand2.Next(pondRanks.Count);
ComputerPonds[rows[location]].Rank = (PondRank)pondRanks[rank];
rows.Remove(location);
pondRanks.Remove(rank);
}
For some reason the remove doesn't happen all the time, and only sometimes. Anyone has a suggestion?
Both of the list are List , they have 40 elements, and I want to remove the element itself.
Even when debugging I can see that the list count isn't the same (they both have the same initial numbers and they both need to do remove at this loop). If it matters, I'm working on windows phone platform..
I'm pretty sure you should be using List.RemoveAt not List.Remove. RemoveAt will remove the item at the specified index, whereas Remove will look for that object you passed in and remove it from the List if it's in there. But I'm pretty sure that looking at your code that location and rank represent the index, not the objects themselves.
for (int i = 0; i < 39; i++)
{
location = rand.Next(rows.Count);
rank = rand2.Next(pondRanks.Count);
ComputerPonds[location].Rank = (PondRank)pondRanks[rank];
rows.RemoveAt(location);
pondRanks.RemoveAt(rank);
}
EDIT: You may also want to consider making sure that your rows and pondRanks have enough elements (39) before starting the loop (or altering the i < 39 to max out at the upper limit of their length)

Create text files of every combination of specific lines within a base text file

Ok, so hopefully I can explain this in enough detail for somebody to be able to help me.. I am writing a program in C# that is supposed to take a text file and replace specific text, which happen to be names of files, and print a new text file for every single combination of the given filenames. The specific places to change the text of filenames have their own set of possible filenames, listed as an array described below. The program should run regardless of how many filenames are available for each location as well as how many total locations for the filenames. If you really wanted to make it awesome, it can be slightly optimized knowing that no filenames should be duplicated throughout any single text file.
text is an array of lines that make up the base of the total file.
lineNum holds an array of the line locations of the filename entries.
previousFiles is an array of previously used filenames, starting with what is already in the file.
files is a jagged 2-dimensional array of possible filenames where files[1] would be an array of all the possible filenames for the 2nd location
Here is an example of how it would work with 3 separate filename locations, the first one given 3 possible filenames, the second given 8 possible filenames, and the third given 3 possible filenames.
Oh and assume buildNewFile works.
int iterator = 0;
for (int a = 0; a < 3; a++)
{
for (int b = 0; b < 8; b++)
{
for (int c = 0; c < 3; c++)
{
iterator++;
text[lineNums[0]] = text[lineNums[0]].Replace(previousFiles[0], files[0][a]);
text[lineNums[1]] = text[lineNums[1]].Replace(previousFiles[0], files[0][a]);
text[lineNums[2]] = text[lineNums[2]].Replace(previousFiles[1], files[1][b]);
text[lineNums[3]] = text[lineNums[3]].Replace(previousFiles[1], files[1][b]);
text[lineNums[4]] = text[lineNums[4]].Replace(previousFiles[2], files[2][c]);
text[lineNums[5]] = text[lineNums[5]].Replace(previousFiles[2], files[2][c]);
previousFiles = new string[] { files[0][a], files[1][b], files[2][c] };
buildNewFile(text, Info.baseFolder + "networks\\" + Info.dsnFilename + iterator + ".dsn");
}
}
}
If you guys can help me, thank you so much, I just can't figure out how to do it recursively or anything. If you have any questions I'll answer them and edit up here to reflect that.
It took me a little while to figure out what you really wanted to do. This problem can be solved without recursion, the trick is to look at the data you have and get it into a more usable format.
Your "files" array is the one that is the most inconvenient. The trick is to transform the data into usable permutations. To do that, I suggest taking advantage of yield and using a method that returns IEnumerable. The code for it is here:
public IEnumerable<string[]> GenerateFileNameStream(string[][] files)
{
int[] current_indices = new int[files.Length];
current_indices.Initialize();
List<string> file_names = new List<string>();
while (current_indices[0] < files[0].Length)
{
file_names.Clear();
for (var index_index = 0; index_index < current_indices.Length; index_index++)
{
file_names.Add(files[index_index][current_indices[index_index]]);
}
yield return file_names.ToArray();
// increment the indices, trickle down as needed
for (var check_index = 0; check_index < current_indices.Length; check_index++)
{
current_indices[check_index]++;
// if the index hasn't rolled over, we're done here
if (current_indices[check_index] < files[check_index].Length) break;
// if the last location rolls over, then we are totally done
if (check_index == current_indices.Length - 1) yield break;
// reset this index, increment the next one in the next iteration
current_indices[check_index] = 0;
}
}
}
Basically, it keeps track of the current index for each row of the files 2D array and returns the file name at each current index. Then it increments the first index. If the first index rolls over, then it resets to 0 and increments the next index instead. This way we can iterate through every permutation of the file names.
Now, looking at the relationship between lineNum and files, I assume that each location in the file is copied to two lines. The rest of the code is here:
public void MakeItWork(string[][] files, int[] lineNum, string[] text, string[] previousFiles)
{
var iterator = 0;
var filenames = GenerateFileNameStream(files);
// work a copy of the text, assume the "previousFiles" are in this text
var text_copy = new string[text.Length];
foreach (var filenameset in filenames)
{
iterator++;
Array.Copy(text, text_copy, text.Length);
for (var line_index = 0; line_index < lineNum.Length; line_index++)
{
var line_number = lineNum[line_index];
text[line_number] = text[line_number].Replace(previousFiles[line_index], filenameset[line_index / 2]);
}
buildNewFile(text_copy, Info.baseFolder + "networks\\" + Info.dsnFilename + iterator + ".dsn");
}
}
This code just takes the results from the enumerator and generates the files for you. The assumption based on your sample code is that each filename location is used twice per file (since the lineNum array was twice as long as the files location count.
I haven't fully tested all the code, but the crux of the algorithm is there. The key is to transform your data into a more usable form, then process it. The other suggestion I have when asking a question here is to describe the problem more as a "problem" and not in the terms of your current solution. If you detailed the goal you are trying to achieve instead of showing code, you can get more insights into the problem.

Categories