Removing strings from a list line by line - c#

My question is, if I have a list that looks something like the following,
var list = new List<string>();
list.Add("12345");
list.Add("Words");
list.Add("Are");
list.Add("Here");
list.Add("13264");
list.Add("More");
list.Add("Words");
list.Add("15654");
list.Add("Extra");
list.Add("Words");
And I want to be able to delete all the strings that start with numbers from the list and also concatenate the strings in between them so that it looks like the following,
Words Are Here
More Words
Extra Words
How does that logic look? Below is what I have been trying to do, but I can't first out how to delete the strings with number much less create a newline when i delete a string with numbers.
foreach (string s in list)
{
if (s.StartsWith("1"))
s.Remove(0, s.Length);
else
String.Concat(s);
}
foreach (string p in list)
Console.WriteLine(p);

What you're doing is "chunking" or "paging" your data, but you need to walk through each source element one by one to determine where your pages start and stop.
public static IEnumerable<ICollection<T>> ChunkBy<T>(IEnumerable<T> source, Func<T, bool> predicate)
{
ICollection<T> currentChunk = new List<T>();
foreach (var item in source)
{
if (predicate(item))
{
if (currentChunk.Any())
{
yield return currentChunk;
currentChunk = new List<T>();
}
}
else
{
currentChunk.Add(item);
}
}
if (currentChunk.Any())
{
yield return currentChunk;
}
}
This is a reusable method (and you could add this to the start of the first parameter to make it an extension method) that takes advantage of IEnumerable and yield-ing results. In essence, you return a stream of values, where each value is a collection. So it's a list of a list, but with much more fluid terms.
What this does is
Start with an empty temporary list for the current "page" of items.
Loop through each element in your source.
For each element, decide if it's the start of a new page or not.
If it is a new block, return what you've saved up for the current chuck, assuming that saved chunk is not empty.
If it is not a new block, add the entry to the current chunk.
When you're done going through all the entries, send the final chunk, assuming it's not empty.
You can call this method like this:
var output = ChunkBy(list, x => char.IsNumber(x[0]))
.Select(ch => string.Join(" ", ch));
foreach (var o in output)
Console.WriteLine(o);
Which gives
Words Are Here
More Words
Extra Words

You could try something like:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var list = new List<string>();
list.Add("12345");
list.Add("Words");
list.Add("Are");
list.Add("Here");
list.Add("13264");
list.Add("More");
list.Add("Words");
list.Add("15654");
list.Add("Extra");
list.Add("Words");
var resultStrings = new List<string>();
string currentString = "";
foreach (string s in list)
{
if (s.StartsWith("1"))
{
resultStrings.Add(currentString);
currentString = "";
}
else
{
currentString += s + " ";
}
}
resultStrings.Add(currentString);
foreach (string p in resultStrings)
Console.WriteLine(p);
}
}
basically I am looping thou the list if the value is not starting with 1 I add the value to the currentString.
When the value does start with 1 we add the currentString to the resultStrings an start a new currentString.
There are some improvements possible, the if with starts withs 1 is not completely fool prove.
You could use int.Parse.
Is multiple items in a row start with 1 you can end up with empty string in the resultStrings
You could fix this bij checking is the string is empty before adding it to the list

Another approach
var list = new List<string>();
list.Add("12345");
list.Add("Words");
list.Add("Are");
list.Add("Here");
list.Add("13264");
list.Add("More");
list.Add("Words");
list.Add("15654");
list.Add("Extra");
list.Add("Words");
var lines = new List<KeyValuePair<int, string>>();
var currentIndex = 0;
foreach (var line in list.Where(x => x.Length > 0))
{
var firstChar = line.Substring(0, 1)
.ToCharArray()
.First();
if (char.IsNumber(firstChar))
{
currentIndex++;
continue;
}
lines.Add(new KeyValuePair<int, string>(currentIndex, line));
}
foreach (var lineGroup in lines.GroupBy(x => x.Key))
{
Console.WriteLine(string.Join(" ", lineGroup.Select(x => x.Value)));
}

I grouped your words and then printed them like this.
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var list = new List<string>();
// list.Add("Some"); <-- still works
// list.Add("Words");
list.Add("12345");
list.Add("Words");
list.Add("Are");
list.Add("Here");
list.Add("13264");
list.Add("More");
list.Add("Words");
list.Add("15654");
list.Add("Extra");
list.Add("Words");
// list.Add("1234566"); <-- still works
var groups = new Dictionary<int, List<string>>();
int gnum = 0;
foreach (string item in list)
{
if (long.TryParse(item, out long val))
groups.Add(++gnum, new List<string>());
else
{
if (!groups.ContainsKey(gnum))
groups.Add(gnum, new List<string>());
groups[gnum].Add(item);
}
}
foreach (var kvp in groups)
{
string result = string.Join(" ", kvp.Value);
Console.WriteLine(result);
}
}
}

Here is a simple linq that should do what you want.
String.Join(" ", list.Where(m => !char.IsDigit(m[0])))

Related

Checking the equality of lists in foreach loop - C#

I'm trying to check the equality of 2 lists of string but it's not working. This is what I have done:
foreach (List<string> q in questions)
{
if (!groupOfQuestions.Except(q).Any()) //I also tried without '!'
{
questions.Add(groupOfQuestions);
}
}
And declaration of lists:
List<List<string>> questions = new List<List<string>>();
List<string> groupOfQuestions = new List<string>();
You can't modify collection within foreach loop, but you can do it in for loop:
for (int i = questions.Count - 1; i >= 0; --i) {
List<string> q = questions[i];
if (!groupOfQuestions.Except(q).Any()) //I also tried without '!'
questions.Add(groupOfQuestions);
}
Another possibility is to loop on collection's copy:
// Note ".ToList()"
foreach (List<string> q in questions.ToList())
if (!groupOfQuestions.Except(q).Any()) //I also tried without '!'
questions.Add(groupOfQuestions);
Your problem is on this line:
questions.Add(groupOfQuestions);
You cannot modify a collection while you are iterating over it.
You're going to need to create a new List<List<string>> that you can add the matches to. For example:
var questions = new List<List<string>> {
new List<string>{"aaa", "bbb", "ccc"},
new List<string>{"aaa", "bbb", "ddd"},
};
var groupOfQuestions = new List<string>() { "ddd" };
var questionMatches = new List<List<string>>();
foreach (List<string> q in questions)
{
if (!groupOfQuestions.Except(q).Any()) //I also tried without '!'
{
questionMatches.Add(groupOfQuestions);
}
}
questions can not be modified when iterating over it in a foreach. Build a new list and run AddRange at the end:
var listsToAdd = new List<List<string>>();
foreach (List<string> q in questions)
{
if (!groupOfQuestions.Except(q).Any())
{
questions.Add(groupOfQuestions);
}
}
questions.AddRange(listsToAdd);

Create new list in nested list C#

Anyone know how to add new list into another list, the new list cannot be predefined.
For example, after get user input, I will do the following
List<List<string>> ListA = new List<List<string>>();
foreach (List<string> subList in ListA)
{
foreach (var value in subList)
{
if(value != INPUT)
{
// ListA needs to creates a new list with value INPUT
}
}
}
Since you cannot modify collection that you're currently enumerating use eg. ToArray():
foreach (List<string> subList in ListA.ToArray())
{
foreach (var value in subList)
{
if(value != INPUT)
(
ListA.Add(new List<string>() { INPUT });
}
}
}
You could use linq to do this:
include the namespace:
using System.Linq;
And try this:
var result = ListA.Select(x => x.Where(k => k != INPUT).ToList()).ToList();
List<List<string>> ListA = new List<List<string>>();
List<List<string>> ListsToAdd = new List<List<string>>();
foreach (List<string> subList in ListA)
{
foreach (var value in subList)
{
if(value != INPUT)
(
List<string> list = new List<string>();
list.Add(INPUT);
ListsToAdd.Add(list);
}
}
}
ListA.AddRange(ListsToAdd);

Using Linq to filter out certain Keys from a Dictionary and return a new dictionary2

Related to this question: Using Linq to filter out certain Keys from a Dictionary and return a new dictionary
I got a control for a auto-complete that uses dictionary. Scenario was every word in my RichTextBox (to serve as code-editor) will automatically add in my list of autocomplete. Like if I type the word "asdasdasd" in RichTextBox , the word "asdasdasd" will automatically be added in my auto-complete .
using this code:
private IEnumerable<AutocompleteItem> BuildList()
{
//find all words of the text
var words = new Dictionary<string, string>();
var keysToBeFiltered = new HashSet<string> { "Do", "Not" };
var filter = words.Where(p => !keysToBeFiltered.Contains(p.Key))
.ToDictionary(p => p.Key, p => p.Value);
foreach (Match m in Regex.Matches(rtb_JS.Text, #"\b\w+\b"))
filter[m.Value] = m.Value;
//foreach (Match m in Regex.Matches(rtb_JS.Text, #"^(\w+)([=<>!:]+)(\w+)$"))
//filter[m.Value] = m.Value;
foreach (var word in filter.Keys)
{
yield return new AutocompleteItem(word);
}
}
Now the word "Do" and "Not" are still included to auto-complete using the code above. Also when my form loads, a specific default script appears that must be there all the time. So i can't change it.
Two possible solutions I have do to fix this:
1. don't allow those default words used in default script add in my autocomplete when form loads.(make list of words that prevent from adding into my list)
2. detect the line that has commented "//" or "/*" and prevent words from it to add in my dictionary.
Hope you can help me. Please tell me if I need to revise my question and I'll revise/update it ASAP.
main_Q:
how to prevent adding commented words from richtextbox (line that starts with // or /*) into autocomplete
I found your problem in the following line:
foreach (Match m in Regex.Matches(rtb_JS.Text, #"\b\w+\b"))
filter[m.Value] = m.Value;
With "\b\w+\b" regex, you add all words in your RichTextBox control to your filter variable.
So, you must change your code in that line for prevent from adding your unwanted keywords. Please check the following:
private IEnumerable<AutocompleteItem> BuildList()
{
//find all words of the text
bool bolFindMatch = false;
var words = new Dictionary<string, string>();
var keysToBeFiltered = new HashSet<string> { "Do", "Not" };
var filter = words.Where(p => !keysToBeFiltered.Contains(p.Key))
.ToDictionary(p => p.Key, p => p.Value);
foreach (Match m in Regex.Matches(rtb1.Text, #"\b\w+\b"))
{
foreach (string hs in keysToBeFiltered)
{
if (Regex.Matches(m.Value, #"\b" + hs + #"\b").Count > 0)
{
bolFindMatch = true;
break;
}
}
if (!bolFindMatch)
{
filter[m.Value] = m.Value;
}
else
{
bolFindMatch = false;
}
}
//foreach (Match m in Regex.Matches(rtb_JS.Text, #"^(\w+)([=<>!:]+)(\w+)$"))
//filter[m.Value] = m.Value;
foreach (var word in filter.Keys)
{
yield return new AutocompleteItem(word);
}
}
why do not you just check weather the string starts with "//" or "/*" before you do your processing
string notAllowed1 = #"//";
string notAllowed2 = #"/*";
var contains = false;
foreach(string line in rtb_JS.Lines)
{
if (line.StartsWith(notAllowed2) || !line.StartsWith(notAllowed1))
{
contains = true;
break
}
}
//else do nothing
update 2 with Linq
var contains = rtb_JS.Lines.ToList()
.Count( line => line.TrimStart().StartsWith(notAllowed2) ||
line.TrimStart().StartsWith(notAllowed1)) > 0 ;
if(!contains)
{
//do your logic
}
I think this is what you want:
var filter = Regex.Matches(rtb_JS.Text, #"\b\w+\b")
.OfType<Match>()
.Where(m=>!keysToBeFiltered.Any(x=>x == m.Value))
.ToDictionary(m=>m.Value,m=>m.Value);
It's strange that your Dictionary has Keyand Value as the same values in an entry?

dictionary of lists in c#(cant seem to get my lists)

Here i used linq to filter my result in an array and pass into a list and from that list into a dictionary as you can see below
//array is a multidimensional array with string data in it
var datumn = array;
var list = new List<string>();
var stringcounts = new Dictionary<int,List<string>>();
var listtemp = new List<string>();
//linq
var arrayresult = from string a in datumn where a != "FREE" select a;
//adding result from arrayresult to list
foreach (var listing in arrayresult)
{
list.Add(listing);
}
//using linq again i filter my list then add to dictionary
for (int count = 3; count > 0; count-- )
{
var duplicateItems = from x in list
group x by x into grouped
where grouped.Count() == count
select grouped.Key;
foreach (var replace in duplicateItems)
{
listtemp.Add(replace.ToString());
}
stringcounts.Add(count, lists);
//clearing the list to avoid duplicating data in my dictionary
listtemp.Clear();
}
for (int key = stringcounts.Count; key > 0; --key)
{
var holding = stringcounts[key];
foreach (var li in holding)
{
MessageBox.Show(li.ToString());
//just view what i have to check if the data is correct
}
}
`
the program skips iterator over of the lists and ends can some one help with this
and i have tried everything including linq and other collections like hashtable
and maps but nothing works and it is not a console application
This line is wrong:
var dict = new Dictionary<int, List<string>>();
Remove the ";".
Result:
indigo silver violet purple green pink red brown yellow
Edit: full code for comparison:
var dict = new Dictionary<int, List<string>>()
{
{1, new List<string>{"red", "brown", "yellow"}},
{2, new List<string>{"purple", "green", "pink"}},
{3, new List<string>{"indigo", "silver", "violet"}}
};
// now i want to get my values from the lists in the dictionary
for (int count = 3; count > 0; count--)
{
var l = dict[count];
foreach (var li in l)
{
li.Dump();
}
}
foreach (var item in dict)
{
var list = item.Value;
foreach (var str in list)
{
MessageBox.Show(str);
}
}
The listtemp.Clear() is a bad syntax so therefore it should be removed and the listtemp should be declared in the for loop therefore removing redundancy and the initial problem

Using more than one object to iterate though in foreach loops

I have a multidimensional list:
List<string>[] list = new List<string>[2];
list[0] = new List<string>();
list[1] = new List<string>();
And I iterate though the list as follows - but this only iterates through one list:
foreach (String str in dbConnectObject.Select()[0])
{
Console.WriteLine(str);
}
Although I want to be able to do something like:
foreach (String str in dbConnectObject.Select()[0] & String str2 in dbConnectObject.Select()[1])
{
Console.WriteLine(str + str2);
}
If the lists are of the same size, you can use Enumerable.Zip:
foreach (var p in dbConnectObject.Select()[0].Zip(dbConnectObject.Select()[1], (a,b) => new {First = a, Second = b})) {
Console.Writeline("{0} {1}", p.First, p.Second);
}
If you want to sequentially iterate through the two lists, you can use IEnumerable<T>.Union(IEnumerable<T>) extension method :
IEnumerable<string> union = list[0].Union(list[1]);
foreach(string str int union)
{
DoSomething(str);
}
If you want a matrix of combination, you can join the lists :
var joined = from str1 in list[0]
from str2 in list[1]
select new { str1, str2};
foreach(var combination in joined)
{
//DoSomethingWith(combination.str1, combination.str2);
Console.WriteLine(str + str2);
}
You can try the following code. It also works if the sizes differ.
for (int i = 0; i < list[0].Count; i++)
{
var str0 = list[0][i];
Console.WriteLine(str0);
if (list[1].Count > i)
{
var str1 = list[1][i];
Console.WriteLine(str1);
}
}
Use LINQ instead:
foreach (var s in list.SelectMany(l => l))
{
Console.WriteLine(s);
}

Categories

Resources