Removing a node from a list using a for - c#

Here is what I have done:
// this is an example of my function, the string and the remover should be variables
string delimeter = ",";
string remover="4";
string[] separator = new string[] { "," };
List<String> List = "1,2,3,4,5,6".Split(separator, StringSplitOptions.None).ToList();
for (int i = 0; i < List.Count - 1; i++)
{
if(List[i]==remover)
List.RemoveAt(i);
}
string allStrings = (List.Aggregate((i, j) => i + delimeter + j));
return allStrings;
The problem is the retruned string is the same as the originial one, same as "1,2,3,4,5,6". the "4" is still in it.
How to fix it?
EDIT:
the solution was that i didnt check the last node of the list in that for, it doesnt seem like that in the example because it was an example i gave just now

When you remove items from a list like this you should make your for loop run in reverse, from highest index to lowest.
If you go from lowest to highest you will end up shifting items down as they are removed and this will skip items. Running it in reverse does not have this issue.

Your code, as it stands, produces the expected output. When running it it will return 1,2,3,5,6. If it doesn't it's due to a bug in how you call this method.
That's not to say that you don't have problems.
When you remove an item you still increment the current index, so you skip checking the item after any item you remove.
While there are a number of solutions, the best solution here is to use the RemoveAll method of List. Not only does it ensure that all items are evaluated, but it can do so way more efficiently. Removing an item from a list means shifting all of the items over by one. RemoveAll can do all of that shifting at the end, which is way more efficient if a lot of items are removed.
Another bug that you have is that your for loop doesn't check the last item at all, ever.
On a side note, you shouldn't use Aggregate to join a bunch of strings together given a delimiter. It's extremely inefficient as you need to copy all of the data from the first item into an intermediate string when adding the second, then both of those to a new string when adding the third, then all three of those to a new string when creating a fourth, and so on. Instead you should use string.Join(delimeter, List);, which is not only way more efficient, but is way easier to write and semantically represents exactly what you're trying to do. Win win win.
We can now re-write the method as:
string delimeter = ",";
string remover = "4";
List<String> List = "1,2,3,4,5,6"
.Split(new[] { delimeter }, StringSplitOptions.None).ToList();
List.RemoveAll(n => n == remover);
return string.Join(delimeter, List);
Another option is to avoid creating a list just to remove items from it and then aggregate the data again. We can instead just take the sequence of items that we have, pull out only the items that we want to keep, rather than removing the items we don't want to keep, and then aggregate those. This is functionally the same, but remove the needless effort of building up a list and removing items, pulling out mechanism from the requirements:
string delimeter = ",";
string remover = "4";
var items = "1,2,3,4,5,6"
.Split(new[] { delimeter }, StringSplitOptions.None)
.Where(n => n != remover);
return string.Join(delimeter, items);

Use this for remove
list.RemoveAll(f => f==remover);

Related

Efficiently Sort Collection in C# by Substring and Index

I'm fetching some records from my database using entity framework as the user types into a searchbox and need to sort the items as they are fetched. I'll try to simplify the problem with the below.
Say I have a random list like the below that I would like to sort in place according to the occurrence of a substring
var randomList = new List<string> { "corona", "corolla", "pecoroll", "copper", "capsicum", "because", "cobra" };
var searchText = "cor";
Sort:
var sortedList = testList.OrderBy(x => x.IndexOf("cor"));
Output:
copper -> capsicum -> because -> cobra -> corona -> corolla -> pecoroll
I understand the code works as expected since the list is sorted by the index of the substring which is -1 for the first 4 items in the output, 0 for the 5th and 6th, and 2 for the 7th item.
Problem:
I'm trying to actually sort by the index of the searchsString and it's closest match to provide the user with suggestions of similar items. The expected result would be something like
corolla -> corona -> pecoroll -> cobra -> copper -> capsicum -> because
where the items containing lower indexes of the matching searchtext would appear first and recursively sort the list by 1 less character from the searchText until no characters remain. i.e. priority given to index of "cor" then "co" then "c".
I can probably write a for loop or recursive method for this but is there a built in LINQ method to achieve this objective on a collection or a library that handles searches this way considering that my code fetches records from a database so performance should be considerd? Thanks for your help in advance
To strictly address your question: "is there a built in LINQ method to achieve this(?)", I believe the answer is no. This type of "best match" search is very subjective; for example it could be argued that "cobra" is a better match than "pecoroll" since the user is more likely to have missed a "b" before the required "r", rather than excluding the first two letters, "pe" of the word "pecoroll". I believe that "proper" implementations of this behavior consider key proximity, common misspellings, and any number of other metrics to best auto-complete the entry. There may well be some established libraries available rather than developing your own method.
However, assuming you did want the exact behavior you requested, and whilst it sounds as if you were happy to do this yourself, here is my two cents:
static List<string> SortedList(List<string> baseList, string searchString)
{
// Take a modifiable copy of the base list
List<string> sourceList = new List<string>(baseList);
// Sort it first alphabetically to resolve tie-breakers
sourceList.Sort();
// Create a instance of our list to be returned
List<string> resultList = new List<string>();
while(
// While there are still elements to be sorted
(resultList.Count != baseList.Count) &&
// And there are characters remaining to be searched for
(searchString.Length > 0))
{
// Order the list elements, that contain the full search string,
// by the index of that search string.
var sortedElements = from item in sourceList
where item.Contains(searchString)
orderby item.IndexOf(searchString)
select item;
// For each of the ordered elements, remove it from the source list
// and add it to the result
foreach(var sortedElement in sortedElements)
{
sourceList.Remove(sortedElement);
resultList.Add(sortedElement);
}
// Remove one character from the search to be used against remaining elements
searchString = searchString.Remove(searchString.Length - 1, 1);
}
return resultList;
}
Testing with:
var randomList = new List<string> { "corona", "corolla", "pecoroll", "copper", "capsicum", "because", "cobra" };
var searchText = "cor";
var sortedList = SortedList(randomList, searchText);
foreach(string entry in sortedList)
{
Console.Write(entry + ", ");
}
I get:
corolla, corona, pecoroll, cobra, copper, capsicum, because,
I hope this helps.

adding a string to an array

I'm just doing a little project in C# (I'm a beginner), my code is basically asking you "how many words are in this sentence?" and then asks you for every word, once it gets all of them it prints it out with "ba" attached to every word.
I know I'm a real beginner and my code's probably a joke but could you please help me out with this one?
Console.WriteLine("How many words are in this sentence?");
int WordAmount = Convert.ToInt32(Console.ReadLine());
int i = 1;
while (i <= WordAmount)
{
Console.WriteLine("Enter a word");
string[] word = new string[] { Console.ReadLine() };
i++;
}
Console.WriteLine(word + "ba");
You're close, you've just got one issue.
string[] word = new string[] { Console.ReadLine() };
You are creating a new array list inside the scope of a while loop. Not only will this disappear every loop, meaning you never save the old words, but you also won't be able to use it outside of the loop, making it useless.
Create a string[] words = new string[WordAmount];. Then iterate through it to add your Console.ReadLine() to it, and finally, iterate through it once more and Console.WriteLine(words[i] + "ba");
string[] wordList = new string[WordAmount];
while (i <= WordAmount)
{
Console.WriteLine("Enter a word");
wordList[i-1] = Console.ReadLine() ;
i++;
}
foreach (var item in wordList)
Console.WriteLine(item + "ba");
Working Fiddle: https://dotnetfiddle.net/7UJKwN
your code has multiple issues. First you need to define your array outside of your while loop, and then fill it one by one.
In order to read/write array of strings (string[]), you need to loop through (iterate) it.
My code actually iterates your wordList. In the first While loop I am iterating to fill the wordList array. then printing it in the second loop
First of all, consider storing your words in some kind of collection, for example a list.
List<string> words = new List<string>();
while (i <= WordAmount)
{
Console.WriteLine("Enter a word");
string word = Console.ReadLine();
words.Add(word);
i++;
}
I don't think your code compiles - the reason is you are trying to use the word variable outside of the scope that it is defined in. In my solution I have declared and initialized a list of strings (so list of the words in this case) outside of the scope where user has to input words, it is possible to access it in the inner scope (the area between curly brackets where user enters the words).
To print all the words, you have to iterate over the list and add a "ba" part. Something like this:
foreach(var word in words)
{
Console.WriteLine(word + "ba");
}
Or more concisely:
words.ForEach(o => Console.WriteLine(o + "ba"));
If you want to print the sentence without using line breaks, you can use LINQ:
var wordsWithBa = words.Select(o => o + "ba ").Aggregate((a, b) => a + b);
Console.WriteLine(wordsWithBa);
Although I would recommend learning LINQ after you are a bit more familiarized with C# :)
You can look here and here to familiarize yourself with the concept of collections and scopes of variables.
You could also use a StringBuilder class to do this task (my LINQ method is not very efficient if it comes to memory, but i believe it is enough for your purpose).

C# fastest way to remove duplicates from string: Split vs. Loop

I have a string of dash-separated numbers that I am removing duplicate numbers from
string original = "45-1-3-45-10-3-15";
string new = "45-1-3-10-15";
I have tried two approaches, and used Stopwatch to determine which method is faster, but I am getting inconsistent time elapses so I was hoping for some insight into which method would be more efficient for achieving the new duplicate-free list.
Method 1: While loop
List<string> temp = new List<string>();
bool moreNumbers = true;
while (moreNumbers)
{
if (original.Contains("-"))
{
string number = original.Substring(0, original.IndexOf("-"));
//don't add if the number is already in the list
int index = temp.FindIndex(item => item == number);
if (index < 0)
temp.Add(value);
original = original.Substring(original.IndexOf("-") + 1);
}
else
moreNumbers = false;
}
//add remaining value in
string lastNumber = original;
//don't add if the number is already in the list
int indexLast = temp.FindIndex(item => item == lastNumber);
if (indexLast < 0)
temp.Add(lastNumber);
string new = "";
foreach (string number in temp)
{
new += "-" + number;
}
if (new[0] == '-')
new = new.Substring(1);
Method 2: Split
List<string> temp = original.Split('-').Distinct().ToList();
string new = "";
foreach (string number in temp)
{
new += "-" + number;
}
if (new[0] == '-')
new = new.Substring(1);
I think the second method is more readable, but possibly slower? Which of these methods would be more efficient or a better approach?
This will be highly optimized but you test for performance.
string result = string.Join("-", original.Split('-').Distinct());
You have some inefficiencies in both your examples.
Method 1: manipulating a string is never efficient. Strings are immutable.
Method 2: no need to create a List and use a StringBuilder() instead of using string concatenation.
Lastly, new is a C# reserved word so none of your code will compile.
In the first approach, you're using several Substring calls and several IndexOf calls. I don't know exactly the internal implementation, but I guess they are O(n) in time complexity.
Since, for each number in the list, you'll do a full loop in the other list (you're using strings as lists), you'll have an O(n^2) time complexity.
The second option, I assume it is O(n^2) too, because to make a distinct of the list in an IEnumerable, it will have to iterate the list.
I think one optimezed approach to the problem is:
1) loop the main string and for each "-" or end of string, save the number (this will be more economic than the Split in terms of space).
2) for each number, put it in a Dictionary. This won't be economic in terms of space, but will provide O(1) time to check if the item. Hashing small strings shouldn't be too constly.
3) Loop the Dictionary to retrieve the distinct values.
This implementation will be O(n), better than O(n^2).
Note that only using the dictionary can deliver the result string in a different order. If the order is important, use the Dictionary to check if the item is duplicated, but put in an auxiliary list. Again, this will have a space cost.

Displaying strings from Array without repeats

I working on a project in c# asp.net. I would like to randomly display a string from either an array or list, if certain conditions are met, I would like to display another random string that has not been displayed.
EX.
LIST<string> myString = new List<string> {"one", "two", "three", "four", "five"};
or
string[] myString = new[] {"one", "two", "three", "four", "five"};
void btnAnswer_Click(Object sender, EventArgs e)
{
string Next = myString[random.Next(myString.Length)];
if(my condition is met)
{
lbl.Text = Next;
}
}
I can randomly call a string from my list or array, but not sure how to store repeat results. I've tried using an algorithm that jumbles the array and then a counter for the index, but the counter (counter++) seems to add 1 once and then stops. I've tried using a list and removing the string I use, but it seems to repeat after all strings have been used.
If more code is needed I can provide, just need a point in the right direction.
One option is to display one of the random string and store it in a ViewState.
When you come next time again, check whether new random string is already there in ViewState or not. If it is there in ViewState then get another random string.
Couple of options:
Have an array or list which is a copy of the master array, and whenever you choose one, remove it from the array. Once the array is empty, refresh it from the master array.
Similar, just create your array, shuffle it, and start giving out strings by order. so array[0], next would be array[1], and so on, once you reach the end, shuffle and start again.
There are probably others as well :)
Edit:
If I understand correctly from the comments, you are talking about the option where the input is not unique to begin with. If that is the case, you can create your list with a simple linq query to get only unique values (using distinct), guaranteeing no duplicates.
Have a look at this question for an example.
You can copy the indices into a list, it will save memory while allow us to track all the remaining items:
var indices = Enumerable.Range(0,myString.Count).ToList();
//define some method to get next index
public int? NextIndex(){
if(indices.Count == 0) return null;
int i = random.Next(indices.Count);
int k = indices[i];
indices.RemoveAt(i);
return k;
}
if(my condition is met) {
int? nextIndex = NextIndex();
lbl.Text = nextIndex == null ? "" : myString[nextIndex.Value];
}
Note that the Text is set to empty if there won't be no more remaining string, however you can handle that case yourself in another way such as keep the text unchanged.
You could probably do that, but why not get a unique list of strings first?
var uniqueStrings = myString.Distinct().ToList();
Then as you select strings, do a .Remove() on the last randomly selected value from uniqueStrings.
You said that:
I've tried using a list and removing the string I use, but it seems to repeat after all strings have been used.
The problem here is using the same instance of random for your series after you've run out. If you re-instantiate random = new Random(), the variable is re-seeded and you will have totally different results from what was generated before.

Check if Characters in ArrayList C# exist - C# (2.0)

I was wondering if there is a way in an ArrayList that I can search to see if the record contains a certain characters, If so then grab the whole entire sentence and put in into a string. For Example:
list[0] = "C:\Test3\One_Title_Here.pdf";
list[1] = "D:\Two_Here.pdf";
list[2] = "C:\Test\Hmmm_Joke.pdf";
list[3] = "C:\Test2\Testing.pdf";
Looking for: "Hmmm_Joke.pdf"
Want to get: "C:\Test\Hmmm_Joke.pdf" and put it in the Remove()
protected void RemoveOther(ArrayList list, string Field)
{
string removeStr;
-- Put code in here to search for part of a string which is Field --
-- Grab that string here and put it into a new variable --
list.Contains();
list.Remove(removeStr);
}
Hope this makes sense. Thanks.
Loop through each string in the array list and if the string does not contain the search term then add it to new list, like this:
string searchString = "Hmmm_Joke.pdf";
ArrayList newList = new ArrayList();
foreach(string item in list)
{
if(!item.ToLower().Contains(searchString.ToLower()))
{
newList.Add(item);
}
}
Now you can work with the new list that has excluded any matches of the search string value.
Note: Made string be lowercase for comparison to avoid casing issues.
In order to remove a value from your ArrayList you'll need to loop through the values and check each one to see if it contains the desired value. Keep track of that index, or indexes if there are many.
Then after you have found all of the values you wish to remove, you can call ArrayList.RemoveAt to remove the values you want. If you are removing multiple values, start with the largest index and then process the smaller indexes, otherwise, the indexes will be off if you remove the smallest first.
This will do the job without raising an InvalidOperationException:
string searchString = "Hmmm_Joke.pdf";
foreach (string item in list.ToArray())
{
if (item.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0)
{
list.Remove(item);
}
}
I also made it case insensitive.
Good luck with your task.
I would rather use LINQ to solve this. Since IEnumerables are immutable, we should first get what we want removed and then, remove it.
var toDelete = Array.FindAll(list.ToArray(), s =>
s.ToString().IndexOf("Hmmm_Joke.pdf", StringComparison.OrdinalIgnoreCase) >= 0
).ToList();
toDelete.ForEach(item => list.Remove(item));
Of course, use a variable where is hardcoded.
I would also recommend read this question: Case insensitive 'Contains(string)'
It discuss the proper way to work with characters, since convert to Upper case/Lower case since it costs a lot of performance and may result in unexpected behaviours when dealing with file names like: 文書.pdf

Categories

Resources