I want to be able to check if the string contains all the values held in the list;
So it will only give you a 'correct answer' if you have all the 'key words' from the list in your answer.
Heres something i tired which half fails;(Doesn't check for all the arrays, will accept just one).
Code i tired:
foreach (String s in KeyWords)
{
if (textBox1.Text.Contains(s))
{
correct += 1;
MessageBox.Show("Correct!");
LoadUp();
}
else
{
incorrect += 1;
MessageBox.Show("Incorrect.");
LoadUp();
}
}
Essentially what i want to do is:
Question: What is the definition of Psychology?
Key words in arraylist: study,mental process,behaviour,humans
Answer: Psychology is the study of mental process and behaviour of humans
Now if and ONLY if the answer above contains all key words will my code accept the answer.
I hope i have been clear with this.
Edit: Thank you all for your help. All answers have been voted up and i thank everyone for quick answers. I voted up the answer that can be easily adapted to any code. :)
Using LINQ:
// case insensitive check to eliminate user input case differences
var invariantText = textBox1.Text.ToUpperInvariant();
bool matches = KeyWords.All(kw => invariantText.Contains(kw.ToUpperInvariant()));
This should help:
string text = "Psychology is the study of mental process and behaviour of humans";
bool containsAllKeyWords = KeyWords.All(text.Contains);
You can use some of the LINQ methods like:
if(Keywords.All(k => textBox1.Text.Contains(k))) {
correct += 1;
MessageBox.Show("Correct");
} else {
incorrect -= 1;
MessageBox.Show("Incorrect");
}
The All method returns true when the function returns true for all of the items in the list.
Related
I am aware this question as been asked. And I am not really looking for a function to do so. I was hoping to get some tips on making a little method I made better. Basically, take a long string, and search for a smaller string inside of it. I am aware that there is literally always a million ways to do things better, and that is what brought me here.
Please take a look at the code snippet, and let me know what you think. No, its not very complex, yes it does work for my needs, but I am more interested in learning where the pain points would be using this for something I would assume it would work for, but would not for such and such reason. I hope that makes sense. But to give this question a way to be answered for SO, is this a strong way to perform this task (I somewhat know the answer :) )
Super interested in constructive criticism, not just in "that's bad". I implore you do elaborate on such a thought so I can get the most out of the responses.
public static Boolean FindTextInString(string strTextToSearch, string strTextToLookFor)
{
//put the string to search into lower case
string strTextToSearchLower = strTextToSearch.ToLower();
//put the text to look for to lower case
string strTextToLookForLower = strTextToLookFor.ToLower();
//get the length of both of the strings
int intTextToLookForLength = strTextToLookForLower.Length;
int intTextToSearch = strTextToSearchLower.Length;
//loop through the division amount so we can check each part of the search text
for(int i = 0; i < intTextToSearch; i++)
{
//substring at multiple positions and see if it can be found
if (strTextToSearchLower.Substring(i,intTextToLookForLength) == strTextToLookForLower)
{
//return true if we found a matching string within the search in text
return true;
}
}
//otherwise we will return false
return false;
}
If you only care about finding a substring inside a string, just use String.Contains()
Example:
string string_to_search = "the cat jumped onto the table";
string string_to_find = "jumped onto";
return string_to_search.ToLower().Contains(string_to_find.ToLower());
You can reuse VB's Like operator this way:
1) Make a reference to Microsoft.VisualBasic.dll library.
2) Use the following code.
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
if (LikeOperator.LikeString(Source: "11", Pattern: "11*", CompareOption: CompareMethod.Text)
{
// Your code here...
}
To implement your function in a case-insensitive way, it may be more appropriate to use IndexOf instead of the combination of two ToLower() calls with Contains. This is both because ToLower() will generate a new string, and because of the Turkish İ Problem.
Something like the following should do the trick, where it returns False if either term is null, otherwise uses a case-insensitive IndexOf call to determine if the search term exists in the source string:
public static bool SourceContainsSearch(string source, string search)
{
return search != null &&
source?.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1;
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
So, I am creating a word filter for a game server in C# and basically I am trying to scourer the sentence for banned words and replace them with clean words. I've already done so, but now I'm up to the part where I want to scan the sentence for a list of sentence banned words. I'm hopeless at this bit, and I can't seem to wrap my head around it.
Basically I am CheckSentence(Message) in the ChatManager, and need the following code to count and return continue; if the value is more than 5. So far I have:
public bool CheckSentence(string Message)
{
foreach (WordFilter Filter in this._filteredWords.ToList())
{
if (Message.ToLower().Contains(Filter.Word) && Filter.IsSentence)
{
// count Message, if message contains >5
// from (Message.Contains(Filter.Word))
// continue; else (ignore)
}
}
return false;
}
I'm not too sure if that makes much sense, but I want it to continue; if there are more than 5 Message.Contains(Filter.Word)
public bool CheckSentence(string rawMessage)
{
var lower = rawMessage.ToLower();
var count = 0;
foreach (WordFilter Filter in this._filteredWords.ToList())
{
if (lower.Contains(Filter.Word) && Filter.IsSentence)
{
count++;
}
}
return count >= 5;
}
If this becomes too slow, you may be better of caching the list of filtered words in a HashSet, and iterating over each word in the message, checking if it exists in the HashSet, which would give you O(n) speed, where N is the number of words.
LINQ Version
public bool CheckSentenceLinq(string rawMessage)
{
var lower = rawMessage.ToLower();
return _filteredWords
.Where(x => x.IsSentence)
.Count(x => lower.Contains(x.Word)) >= 5;
}
EDIT 2: LINQ Updated As per #S.C. Comment
By #S.C.
For the linq version, there's no need to count past the first five. return _filteredWords.Where(x => x.IsSentence && lower.Contains(x.Word)).Skip(5).Any();
public bool CheckSentenceLinq(string rawMessage)
{
var lower = rawMessage.ToLower();
return _filteredWords
.Where(x => x.IsSentence)
.Where(x => lower.Contains(x.Word))
.Skip(5)
.Any();
}
ToUpper vs ToLower
As #DevEstacion mentioned and per Microsoft best practices for using string recommendations here it is best to use ToUpperInvariant() for string comparisons rather than ToLowerInvariant().
EDIT:Using Continue
public bool CheckSentenceWithContinue(string rawMessage)
{
var lower = rawMessage.ToLower();
var count = 0;
foreach (WordFilter Filter in this._filteredWords.ToList())
{
if (!Filter.IsSentence)
continue; // Move on to the next filter, as this is not a senetece word filter
if (!lower.Contains(Filter.Word))
continue; // Move on to the next filter, as the message does not contain this word
// If you are here it means filter is a Sentence filter, and the message contains the word, so increment the counter
count++;
}
return count >= 5;
}
I believe someone already posted a correct answer, I'm just here to provide an alternative.
So instead of doing a forloop or foreach, I'll be providing you with Regex solution.
public bool CheckSentence(string rawMessage)
{
/*
The string.Join("|", _filteredWords) will create the pattern for the Regex
the '|' means or so from the list of filtered words, it will look it up on
the raw message and get all matches
*/
return new Regex(string.Join("|", _filteredWords.Where(x => x.IsSentence)),
RegexOptions.IgnoreCase | RegexOptions.Compiled).Match(rawMessage).Length >= 5;
}
Benefits? much shorter, prevents loop and could be faster :)
Don't forget to add these two lines of using declaration on top of the .cs file
using System.Linq;
using System.Text.RegularExpressions;
I have a equation string and when I split it with a my pattern I get the folowing string array.
string[] equationList = {"code1","+","code2","-","code3"};
Then from this I create a list which only contains the codes.
List<string> codeList = {"code1","code2","code3"};
Then existing code loop through the codeList and retrieve the value of each code and replaces the value in the equationList with the below code.
foreach (var code in codeList ){
var codeVal = GetCodeValue(code);
for (var i = 0; i < equationList.Length; i++){
if (!equationList[i].Equals(code,StringComparison.InvariantCultureIgnoreCase)) continue;
equationList[i] = codeVal;
break;
}
}
I am trying to improve the efficiency and I believe I can get rid of the for loop within the foreach by using linq.
My question is would it be any better if I do in terms of speeding up the process?
If yes then can you please help with the linq statement?
Before jumping to LINQ... which doesn't solve any problems you've described, let's look at the logic you have here.
We split a string with a 'pattern'. How?
We then create a new list of codes. How?
We then loop through those codes and decode them. How?
But since we forgot to keep track of where those code came from, we now loop through the equationList (which is an array, not a List<T>) to substitute the results.
Seems a little convoluted to me.
Maybe a simpler solution would be:
Take in a string, and return IEnumerable<string> of words (similar to what you do now).
Take in a IEnumerable<string> of words, and return a IEnumerable<?> of values.
That is to say with this second step iterate over the strings, and simply return the value you want to return - rather than trying to extract certain values out, parsing them, and then inserting them back into a collection.
//Ideally we return something more specific eg, IEnumerable<Tokens>
public IEnumerable<string> ParseEquation(IEnumerable<string> words)
{
foreach (var word in words)
{
if (IsOperator(word)) yield return ToOperator(word);
else if (IsCode(word)) yield return ToCode(word);
else ...;
}
}
This is quite similar to the LINQ Select Statement... if one insisted I would suggest writing something like so:
var tokens = equationList.Select(ToToken);
...
public Token ToToken(string word)
{
if (IsOperator(word)) return ToOperator(word);
else if (IsCode(word)) return ToCode(word);
else ...;
}
If GetCodeValue(code) doesn't already, I suggest it probably could use some sort of caching/dictionary in its implementation - though the specifics dictate this.
The benefits of this approach is that it is flexible (we can easily add more processing steps), simple to follow (we put in these values and get these as a result, no mutating state) and easy to write. It also breaks the problem down into nice little chunks that solve their own task, which will help immensely when trying to refactor, or find niggly bugs/performance issues.
If your array is always alternating codex then operator this LINQ should do what you want:
string[] equationList = { "code1", "+", "code2", "-", "code3" };
var processedList = equationList.Select((s,j) => (j % 2 == 1) ? s :GetCodeValue(s)).ToArray();
You will need to check if it is faster
I think the fastest solution will be this:
var codeCache = new Dictionary<string, string>();
for (var i = equationList.Length - 1; i >= 0; --i)
{
var item = equationList[i];
if (! < item is valid >) // you know this because you created the codeList
continue;
string codeVal;
if (!codeCache.TryGetValue(item, out codeVal))
{
codeVal = GetCodeValue(item);
codeCache.Add(item, codeVal);
}
equationList[i] = codeVal;
}
You don't need a codeList. If every code is unique you can remove the codeCace.
If anyone can help out there, I'll be tremendously grateful.
Essentially I am working on a homework project where, for part of it, I need to search an array. The array, I currently have as a String type, but is essentially a collection of dates. (In the format 05/06/2014)
I am just about at my wits end trying to find a way to allow the user to search this array, in particular that doesn't use built in methods like array.binarysearch etc.
I tried to implement a binary search but that didn't seem to work, I can provide code if you wish to see where I'm probably going wrong. But is there perhaps a better search I should use for this string type, or should I be converting the string array into a different type?
If anyone can help I would greatly appreciated, I'm not necessarily asking for anyone to do my work for me I'd just be thrilled if someone could bump me in the right direction, as this problem has been doing my nut in. Thanks!
Current Binary Search Code:
public static void BinarySearch(string[] dateArray, string searchTerm)
{
int first = 0;
int last = dateArray.Length - 1;
int position = -1;
bool found = false;
int compCount = 0;
while (found != true && first <= last)
{
int middle = (first + last) / 2;
int comparisonSTR = string.Compare(dateArray[middle], searchTerm);
if (dateArray[middle] == searchTerm)
{
found = true;
position = middle;
compCount++;
Console.WriteLine("Your search has been found after " + compCount + "comparisons.");
}
else if (comparisonSTR > 0)
{
last = middle;
compCount++;
}
else
{
first = middle;
compCount++;
}
}
}
For an educational response, your binary search is correct*, if not very clean - like #Alex said, you only have to make sure you're comparing them as DateTimes. The problem is with the line
int comparisonSTR = string.Compare(dateArray[middle], searchTerm);
because the "string class" doesn't know what a date is so it can't really give you a datetime comparison when you're trying to search for dates. It can only give you a comparison on if one term comes alphabetically before, equal, or after another term.
Instead, if you convert them to DateTimes and use the comparer specific for DateTimes, then you should get back a comparison that you can use for binary search. You can either convert them to DateTime in-line
int comparisonSTR = DateTime.Compare(Convert.ToDateTime(searchTerm), Convert.ToDateTime(dateArray[middle]));
or convert them outside of the loop as the first thing you do in your method to make it a little easier to read
DateTime[] dates = Array.ConvertAll(dateArray, Convert.ToDateTime);
DateTime searchDate = Convert.ToDateTime(searchTerm);
while (found != true && first <= last)
{
int middle = (first + last) / 2;
int comparison = DateTime.Compare(searchDate, dates[middle]);
Other than that, you're pretty much set. You might have already solved this by now, so in that case I'm just posting it in part to explain why the string.Compare didn't work for you to convert dates in this case.
Edit: Make sure to test your edge cases (e.g. searching for not only the middle, but also the first and last elements for varying array sizes), because I suspect that your binary search may not be entirely correct on second review.
I am in the process of learning more about LINQ and Lambda expressions but at this stage, I simply don't "Get" Lambda expressions.
Yes ... I am a newbie to these new concepts.
I mean, every example I see illustrates how to add or subtract to parameters.
What about something a little more complex?
To help me gain a better understanding I have posted a small challenge for anyone who wishes to participate. I have the following method which will take any string and will put spaces in between any upper case characters and their preceding neighbour (as shown below).
i.e.
"SampleText" = "Sample Text"
"DoesNotMatterHowManyWords" = "Does Not Matter How Many Words"
Here is the code;
public static string ProperSpace(string text)
{
var sb = new StringBuilder();
var lowered = text.ToLower();
for (var i = 0; i < text.Length; i++)
{
var a = text.Substring(i, 1);
var b = lowered.Substring(i, 1);
if (a != b) sb.Append(" ");
sb.Append(a);
}
return sb.ToString().Trim();
}
I am sure that the method above can be re-written to use with LINQ or a Lambda expression. I am hoping that this exercise will help open my eyes to these new concepts.
Also, if you have any good links to LINQ or Lambda tutorials, please provide.
EDIT
Thanks to everyone who has contributed. Although the current method does do the job, I am happy to see it can be modified to utilize a lambda expression. I also acknowledge that this is perhaps not the best example for LINQ.
Here is the newly updated method using a Lambda expression (tested to work);
public static string ProperSpace(string text)
{
return text.Aggregate(new StringBuilder(), (sb, c) =>
{
if (Char.IsUpper(c)) sb.Append(" ");
sb.Append(c);
return sb;
}).ToString().Trim();
}
I also appreciate the many links to other (similar) topics.
In particular this topic which is so true.
This is doing the same as the original code and even avoids the generation of the second (lower case) string.
var result = text.Aggregate(new StringBuilder(),
(sb, c) => (Char.IsUpper(c) ? sb.Append(' ') : sb).Append(c));
Personally, I think your method is simple and clear, and I would stick with it (I think I might have even written the exact same code somewhere along the lines).
UPDATE:
How about this as a starting point?
public IEnumerable<char> MakeNice(IEnumerable<char> str)
{
foreach (var chr in str)
{
if (char.ToUpper(chr) == chr)
{
yield return ' ';
}
yield return chr;
}
}
public string MakeNiceString(string str)
{
return new string(MakeNice(str)).Trim();
}
Like leppie, I'm not sure this is a good candidate for LINQ. You could force it, of course, but that wouldn't be a useful example. A minor tweak would be to compare text[i] against lowered[i] to avoid some unnecessary strings - and maybe default the sb to new StringBuilder(text.Length) (or a small amount higher):
if (text[i] != lowered[i]) sb.Append(' ');
sb.Append(a);
Other than that - I'd leave it alone;
public static string ProperSpace(string text)
{
return text.Aggregate(new StringBuilder(), (sb, c) =>
{
if (Char.IsUpper(c) && sb.Length > 0)
sb.Append(" ");
sb.Append(c);
return sb;
}).ToString();
}
I would use RegularExpressions for this case.
public static string ProperSpace(string text)
{
var expression = new Regex("[A-Z]");
return expression.Replace(text, " $0");
}
If you want to use a lambda you could use:
public static string ManipulateString(string text, Func<string, string> manipulator)
{
return manipulator(text);
}
// then
var expression = new Regex("[A-Z]");
ManipulateString("DoesNotMatterHowManyWords", s => expression.Replace(text, " $0"));
Which is essentially the same as using an anonyous delegate of
var expression = new Regex("[A-Z]");
ManipulateString("DoesNotMatterHowManyWords", delegate(s) {
return expression.Replace(text, " $0")
});
Here is a way of doing it:
string.Join("", text.Select((c, i) => (i > 0 && char.IsUpper(c)) ? " " + c : c.ToString()).ToArray());
But I don't see where the improvement is. Just check this very recent question...
EDIT : For those who are wondering: yes, I intentionnaly picked an ugly solution.
I've got a Regex solution that's only 8 times slower than your current loop[1], and also harder to read than your solution[2].
return Regex.Replace(text, #"(\P{Lu})(\p{Lu})", "$1 $2");
It matches unicode character groups, in this case non-uppercase followed by an uppercase, and then adds a space between them. This solution works better than other regex-based solutions that only look for [A-Z].
[1] With reservations that my quickly made up test may suck.
[2] Anyone actually know the unicode character groups without googling? ;)
You can use existing LINQ functions to make this work but it's probably not the best approach. The following LINQ expression would work but is inneficient because it generates a lot of extra strings
public static string ProperCase(string text)
{
return text.Aggregate(
string.Empty,
(acc, c) => Char.ToLower(c) != c ? acc + " " + c.ToString() : acc + c.ToString())
.Trim();
}
For usefullness of linq (if you need convincing), you could check out this question.
I think one first step is to get used to the dot syntax, and only then move on to the 'sql' syntax. Otherwise it just hurts your eyes to start with. I do wonder whether Microsoft didn't slow uptake of linq by pushing the sql syntax, which made a lot of people think 'yuck, DB code in my C#'.
As for lambdas, try doing some code with anonymous delegates first, because if you haven't done that, you won't really understand what the fuss is all about.
I'm curious why a simple regular expression replace wouldn't suffice. I wrote one for someone else that does exactly this:
"[AI](?![A-Z]{2,})[a-z]*|[A-Z][a-z]+|[A-Z]{2,}(?=[A-Z]|$)"
I already posted this on another bulleting board here: http://bytes.com/topic/c-sharp/answers/864056-string-manupulation-net-c. There's one bug that requires a post regex trim that I haven't had the opportunity to address yet, but maybe someone else can post a fix for that.
Using the replace pattern: "$0[space]" where you replace [space] with an actual space would cut the code down immensely.
It handles some special cases which might be outside the scope of what you're trying to do but the bulletin board thread will give you the info on those.
Edit: P.S. A great way to start learning some of the applications of LINQ is to check out the GOLF and CODE-GOLF tags and look for the C# posts. There's a bunch of different and more complex uses of LINQ-to-Objects which should help you to recognise some of the more useful(?) and amusing applications of this technology.
Have you ever thought of using the Aggregate function ...
For instance, let’s say I have an array called routes and I want to set all the Active fields to false. This can be done as follow:
routes.Aggregate(false, (value, route) => route.Active = false);
- Routes is the name of the table.
- The first false is simply the seed value and needs to be the same type as the value that is being set. It’s kind of… redundant.
- value is also redundant and is basically the first value.
- route is the aggregate value (each individual element from the sequence)
No more redundant foreach loops…
I don't know Lambda expression all that well either... but i'm sure there is q genius out there somewhere that can abuse this to do that...