Having learned the basics/fundamentals of the C# programming language, I am now trying to tackle my first real-world problem: Write a program that, given a string, finds its longest sub-string that contains at least one upper-case letter but no digits (and then displays the length of this longest sub-string). This could be two qualifying conditions for an acceptable password, for example...
I have written the code below all by myself, which means there is probably performance issues, but that is for later consideration. I am stuck at the point where I have to make sure there is no digit in the sub-string. The comments in my code show my thinking while writing the program...
I thought first I should check to see if there is an upper-case letter in an extracted sub-string, and if there was, then I can store that qualifying sub-string in a list and then break out of the loop. But now I wonder how to check the no-digit condition at the same time in the same sub-string?
I am trying to keep it neat and simple (as I said I have only just started writing programs longer than a few lines!) so I thought doing a nested loop to check every character against !char.IsNumber(letter) might not be optimal. Or should I first check to see if there is no digit, then see if there is at least a capital character?
I feel confused how to achieve both restrictions, so I would appreciate some help in resolving this issue. I would also appreciate any observations or suggestions you might have. For example, is it OK to store my sub-strings in a list? Should I make a dictionary of some sort? Is my all-possible-sub-string extraction nested-loop optimal?
p.s. Some bits are still unfinished; for example I am still to implement the last step to find the longest sub-string and display to the user its length...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PasswordRestriction
{
class Program /// Write a program that, given a string, finds the longest substring that is a valid password and returns its length.
{
static void Main(string[] args)
{
// Ask the user for an alphanumeric string.
Console.WriteLine("Enter a string of alphanumeric values:");
// Receive the user input as string.
string password = Console.ReadLine();
// Print the length of the longest qualifying substring of the user string.
Console.WriteLine("Length of the longest qualifying substring:\n" + Solution(password).Length );
// Prevent the console window from closing.
Console.ReadLine();
}
/// The method that exracts the longest substring that is a valid password.
/// Note that a substring is a 'contiguous' segment of a string.
public static string Solution(string str)
{
// Only allow non-empty strings.
if ( String.IsNullOrEmpty(str) )
{
return "";
}
else
{
// Only allow letters and digits.
if ( str.All(char.IsLetterOrDigit) )
{
// A list for containing qualifying substrings.
List<string> passwordList = new List<string>();
// Generate all possible substrings. Note that
// string itself is not a substring of itself!
for (int i = 1; i < str.Length; i++)
{
for (int j = 0; j <= (str.Length-i); j++)
{
string subStr = str.Substring(j, i);
Console.WriteLine(subStr);
bool containsNum = false;
bool containsUpper = false;
// Convert the substrings to arrays of characters with ToCharArray.
// This method is called on a string and returns a new character array.
// You can manipulate this array in-place, which you cannot do with a string.
char[] subStrArray = subStr.ToCharArray();
// Go through every character in each substring.
// If there is at least one uppercase letter and
// no digits, put the qualifying substring in a list.
for (int k = 0; k < subStrArray.Length; k++)
{
char letter = subStrArray[k];
if ( char.IsNumber(letter) )
{
containsNum = true;
break;
}
if ( char.IsUpper(letter) )
{
containsUpper = true;
}
if ( containsUpper && (containsNum == false) && (k == subStrArray.Length - 1) )
{
Console.WriteLine("Found the above legit password!");
passwordList.Add(subStr);
}
}
}
}
//Find the longest stored string in the list.
//if (passwordList.Count != 0)
//{
string maxLength = passwordList[0];
foreach (string s in passwordList)
{
if (s.Length > maxLength.Length)
{
maxLength = s;
}
}
//}
// Return the qualifying substring.
return maxLength;
}
else
{
return "aaaaaaaaaa";
}
}
}
}
}
A good problem for Linq
contains no digits - Split on digits
at least one upper-case letter - Where + Any
longest (not shortest) OrderByDescending
longest (just one) - FirstOrDefault
Implementation
string source = ....
var result = source
.Split('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
.Where(line => line.Any(c => c >= 'A' && c <= 'Z')) // or char.IsUpper(c)
.OrderByDescending(line => line.Length)
.FirstOrDefault(); // null if there're no such substrings at all
As an alternative to the Linq answer, and if I understand you correctly, this is what I'd do, replacing the content of the str.All condition:
string qualifier;
string tempQualifier;
bool containsUpper = false;
for (int i = 0; i < str.Length(); i++) {
tempQualifier += str[i];
if (char.IsNumber(str[i])) {
if (containsUpper) {
if (tempQualifier.Length > qualifier.Length && tempQualifier.Length != str.Length) {
qualifier = tempQualifier;
}
containsUpper = false;
}
tempQualifier = "";
} else if (char.IsUpper(str[i])) {
containsUpper = true;
}
}
return qualifier;
This would go through the string, building up the substring until it comes across a number. If the substring contains an uppercase letter and is longer than any previous qualifier, it is stored as the new qualifier (also assuming that it isn't the length of the string provided). Apologies if I've made any mistakes (I'm not well versed in C#).
It's much longer than the Linq answer, but I thought it'd be handy for you to see the process broken down so you can understand it better.
Related
I have to find where a * is at when it could be none at all , 1st position | 2nd position | 3rd position.
The positions are separated by pipes |
Thus
No * wildcard would be
`ABC|DEF|GHI`
However, while that could be 1 scenario, the other 3 are
string testPosition1 = "*|DEF|GHI";
string testPosition2 = "ABC|*|GHI";
string testPosition3 = "ABC|DEF|*";
I gather than I should use IndexOf , but it seems like I should incorporate | (pipe) to know the position ( not just the length as the values could be long or short in each of the 3 places. So I just want to end up knowing if * is in first, second or third position ( or not at all )
Thus I was doing this but i'm not going to know about if it is before 1st or 2nd pipe
if(testPosition1.IndexOf("*") > 0)
{
// Look for pipes?
}
There are lots of ways you could approach this. The most readable might actually just be to do it the hard way (i.e. scan the string to find the first '*' character, keeping track of how many '|' characters you see along the way).
That said, this could be a similarly readable and more concise:
int wildcardPosition = Array.IndexOf(testPosition1.Split('|'), "*");
Returns -1 if not found, otherwise 0-based index for which segment of the '|' delimited string contains the wildcard string.
This only works if the wildcard is exactly the one-character string "*". If you need to support other variations on that, you will still want to split the string, but then you can loop over the array looking for whatever criteria you need.
You can try with linq splitting the string at the pipe character and then getting the index of the element that contains just a *
var x = testPosition2.Split('|').Select((k, i) => new { text = k, index = i}).FirstOrDefault(p => p.text == "*" );
if(x != null) Console.WriteLine(x.index);
So the first line starts splitting the string at the pipe creating an array of strings. This sequence is passed to the Select extension that enumerates the sequence passing the string text (k) and the index (i). With these two parameters we build a sequences of anonymous objects with two properties (text and index). FirstOrDefault extract from this sequence the object with text equals to * and we can print the property index of that object.
The other answers are fine (and likely better), however here is another approach, the good old fashioned for loop and the try-get pattern
public bool TryGetStar(string input, out int index)
{
var split = input.Split('|');
for (index = 0; index < split.Length; index++)
if (split[index] == "*")
return true;
return false;
}
Or if you were dealing with large strings and trying to save allocations. You could remove the Split entirely and use a single parse O(n)
public bool TryGetStar(string input, out int index)
{
index = 0;
for (var i = 0; i < input.Length; i++)
if (input[i] == '|') index++;
else if (input[i] == '*') return true;
return false;
}
Note : if performance was a consideration, you could also use unsafe and pointers, or Span<Char> which would afford a small amount of efficiency.
Try DotNETFiddle:
testPosition.IndexOf("*") - testPosition.Replace("|","").IndexOf("*")
Find the index of the wildcard ("*") and see how far it moves if you remove the pipe ("|") characters. The result is a zero-based index.
From the question you have the following code segment:
if(testPosition1.IndexOf("*") > 0)
{
}
If you're now inside the if statement, you're sure the asterisk exists.
From that point, an efficient solution could be to check the first two chars, and the last two chars.
if (testPosition1.IndexOf("*") > 0)
{
if (testPosition1[0] == '*' && testPosition[1] == '|')
{
// First position.
}
else if (testPosition1[testPosition.Length - 1] == '*' && testPosition1[testPosition.Length - 2] == '|')
{
// Third (last) position.
}
else
{
// Second position.
}
}
This assumes that no more than one * can exist, and also assumes that if an * exist, it can only be surrounded by pipes. For example, I assume an input like ABC|DEF|G*H is invalid.
If you want to remove this assumptions, you could do a one-pass loop over the string and keeping track with the necessary information.
This question already has answers here:
What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?
(5 answers)
Closed 3 years ago.
So the task is that I have to code a program that lets the user enter text. After that, I have to filter the text for non-capital letters only and put them in a list. Every letter should only be in the list once. My code's problem is that when I enter a word like "even", the method selects out e and v but the method doesn't skip the second "e" and ends there.
for (int i = 0; i < text.Length; i++)
{
if (letters.Contains(text[i]) == false && (text[i] >= 'a' && text[i] <= 'z'))
{
letters.Add(text[i]);
Console.WriteLine($"{letters[i]}");
}
}
I get an index out of range error message.
It seems that the Exception is being caused by this line:
Console.WriteLine($"{letters[i]}");
can you replace it by
Console.WriteLine($"{text[i]}");
or you can use a foreach and another variable caller letter:
foreach (var letter in text)
{
if (letters.Contains(letter) == false && (letter >= 'a' && letter <= 'z'))
{
letters.Add(letter);
Console.WriteLine($"{letter}");
}
}
The cause for IndexOutOfRangeException lies in the following lines.
letters.Add(text[i]);
Console.WriteLine($"{letters[i]}");
When i= 3, you are attempting to process character 'n' (assuming your input is 'even'), which doesn't exist in the letters List. You then add it to letters List and print letters[3]. However, at this point, letters only have 2 items('e' and 'v') in it. This is the reason, IndexOutOfRangeException is raised.
What you could do to print the last element added to letters is
Console.WriteLine($"{letters.Last()}");
Or
Console.WriteLine($"{letters[letters.Count - 1]}");
Meanwhile, as juharr pointed out, even printing the text[i] would produce the same result as it is the same character.
Console.WriteLine($"{text[i]}");
Char class already has extensions that verifies the characters, you can use char.IsLetter(character) and char.IsLower(character) to get a lower case letters only.
here is an example of your code :
string input = "Even if I'm a string, I still like odd numbers like 1, 3, 5 ..etc.";
var text = input.ToCharArray();
List<char> letters = new List<char>();
for(int x =0; x < text.Length; x++)
{
if (char.IsLetter(text[x]) && char.IsLower(text[x]) && !letters.Contains(text[x]))
{
letters.Add(text[x]);
Console.WriteLine($"{text[x]}");
}
}
There are few things that could help you here:
HashSet
Char.IsLetter
Short example:
HashSet<char> characters = new HashSet<char>();
foreach (char c in text)
{
if (Char.IsLetter(c))
{
// This will add the character ONLY if it is not there
characters.Add(c);
}
}
Full console app. If you want to try it create a new console app and replace all the Programs.cs by this:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string text = "evEn There";
// HashSet is like a list, but without duplicates
HashSet<char> characters = new HashSet<char>();
// First make all characters lower case
text = text.ToLower();
foreach (char c in text)
{
if (Char.IsLetter(c))
{
// This will add the character ONLY if it is not there, if not it returns false
bool couldBeInserted = characters.Add(c);
}
}
string allCharacters = new String(characters.ToArray());
//This will print: "evnthr"
Console.WriteLine(allCharacters);
}
}
}
You can do it easily with LINQ Where method and ToHashSet method for unique items in array:
var text = "Avene";
var letters = text.Where(ch => char.IsLower(ch)).ToHashSet();
// leters contain now 'e', 'v' and 'n'
To get rid out of that exception you just could do:
Console.WriteLine($"{text[i]}");
which would work just as you want it.
Using C# I'm trying to find a specific word within a char array. Also, I don't want the same letter used more than once i.e. the word is 'hello' and I'm trying to find it within a random array of letters, so if the letter 'l' is used out of the random array of letters, I don't want it to be used again. There should be another 'l' within the array of letters to be used as the second 'l' in "hello". Just trying to be precise. A simple answer would be very helpful. Thank you.
Here is my attempt so far.
public static char [] Note = "hello".ToCharArray();
public static char [] Newspaper = "ahrenlxlpoz".ToCharArray();
static void main(string[] args)
{
Array.Sort(Note);
Array.Sort(Newspaper);
if(Newspaper.Contains<Note>)
{
Console.Write("It should display the letters of Note found within Newspaper");
}
}
I assume by "contains" you mean Newspaper has enough number of letters from each letter to make up Note. For example, you need at least two l's to make up the word "hello". If so, you need to basically count the number of each letter in both strings, and make sure the number of each letter in Note is less than or equal to the number of that letter in Newspaper.
var dictNote = Note.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count());
var dictNews = Newspaper.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count());
bool contains = dictNote.All(x =>
dictNews.ContainsKey(x.Key) && x.Value <= dictNews[x.Key]);
In fact, a string is a char array. And the most "classic" way to do this would be:
string Note = "hello";
char[] Newspaper = "ahrenlxlpoz".ToCharArray();
string res = "";
for (int i = 0; i < Note.Length; i++)
for (int j = 0; j < Newspaper.Length; j++)
if (Note[i] == Newspaper[j])
{
res += Newspaper[j];
Newspaper[j] = ' ';
break;
}
//This prints the remaining characters in Newspaper. I avoid repeating chars.
for (int i = 0; i < Newspaper.Length; i++ )
Console.Write(Newspaper[i]+"\n");
Console.Write("\n\n");
if (Note.Equals(res)) Console.Write("Word found");
else Console.Write("Word NOT found");
Console.Read();
At the end, res will be "hello". Print res in the console. I added the ' ' to avoid repeated characters as someone said in the answer up. So at the end it will compare the result with the word and will tell you if it found the word in the string. Try changing Newspaper to this: "ahrenlxlpaz" and it will tell you the word is NOT found :)
Try this:
public static char[] Note = "hello".ToCharArray();
public static char[] Newspaper = "ahrenlxlpoz".ToCharArray();
foreach (char c in Note) //check each character of Note
{
if (Newspaper.Contains(c))
{
Console.Write(c); //it will display hello
}
}
I need to make a windows form application in c# that provides the user with a textbox, and upon clicking a button it changes the letters in the first textbox to a replacement in the other one.
For example:
if I type "apple" in the first textbox and I have replaced "a" with "b" and "p" with "o" it should spell "baoole".
This process also has to work in reverse. I don't know how to accomplish this.
I tried using .Replace with every pair of letters in the alphabet ex: "a","b"; "c","d";, But it only replaced the first letters so if I typed "c" it would not change to "d". Once I tried to then replace "d","c"; it overlapped and my program wouldn't work. I then tried this:
if (richTextBox1.Text.Contains("a"))
{
richTextBox2.Text=richTextBox1.Text.Replace("a", "b");
}
if (richTextBox1.Text.Contains("b"))
{
richTextBox2.Text=richTextBox1.Text.Replace("b", "a");
}
But it only successfully replaced the first character. I am sorry if I missed anything obvious, I am learning c# and am eager to learn more. Thank you for your time and knowledge.
string x = richTextBox1.Text;
string result = "";
for (int i = 0; i < x.Length; i++)
{
char c = x[i];
if (c % 2 == 0)
{
c--;
}
else
{
c++;
}
result += c;
}
richTextBox2.Text=result;
If the text is "bad" this code will replace it with "abc" and will do the same in the reverse case, I think this is what you are looking for .
Every string is a character array.
So you need to make a loop over that character array (ie., string) and replace each and every character.
string richTextBoxString = richTextBox1.Text;
foreach(char ch in richTextBoxString )
{
if(ch=='a')
Convert.ToString(ch).Replace("a", "b");
//likewise for all characters you need to code
}
richTextBox1.Text=richTextBoxString ;
Any clarification please ask.
It appears you're just trying to make each character be the next character in the alphabet. If that is the case.. I would use a StringBuilder and iterate over each character. Something like this:
private string Encrypt(string text)
{
var content = new StringBuilder(text);
for (int i = 0; i < content.Length; i++) {
if (content[i] == 'z') {
content[i] = 'a';
continue;
}
if (content[i] == 'Z') {
content[i] = 'A';
continue;
}
content[i]++;
}
return content.ToString();
}
Basically, you can iterate over every character in the string and add 1 to it. If you encounter a Z or z.. then just round it out to A or a respectively and move on.
"Decryption" is the reverse of that:
private string Decrypt(string text) {
var content = new StringBuilder(text);
for (int i = 0; i < content.Length; i++) {
if (content[i] == 'a') {
content[i] = 'z';
continue;
}
if (content[i] == 'A') {
content[i] = 'Z';
continue;
}
content[i]--;
}
return content.ToString();
}
That is, if you encounter an A or a, change it to Z or z respectively. Using the above, you can call it like this:
richTextBox2.Text = Encrypt(richTextBox1.Text); // Encrypt it
richTextBox2.Text = Decrypt(richTextBox2.Text); // Decrypt it
Click here to see a live sample of it running
If I understand well what you would like to do:
You have "abba", and would like to do the following replacements { 'a' -> 'b', 'b' -> 'c' }.
If you would use consecutive replaces this would give "cccc", but you would like to get "bccb".
If this is the case, i do not know any out of the box solition in the .net library, but you should write your own class to do this.
I'd do it the following way:
The class gets a string (in the constructur), and splits it to a character array.
The class has a method to add transliteration pairs to its lisf of these steps.
The class has a method to do the transliteration.
This method creates a bitmap, to track which character has been replaced.
It scans the array and saves each transliteraated chracter to a target array.
It flags that position as 'done'.
When doing transliterations it skips the 'done' fields.
finally it fills the non-transliterated fields, and returns a string constructed from the result character array.
This architecture lets you make the translitation easily reversible.
As you stated you are learning C# (and I guess programming as well), thus I only wrote this guide to a possible implementation.
Try it:
var replacements = new Dictionary<char, string>();
replacements.Add('a', "b");
replacements.Add('b', "a");
var inputString = "abc";
var etalonString = "bac";
var resSB= new StringBuilder();
foreach(var letter in inputString)
{
if(replacements.ContainsKey(letter))
resSB.Append(replacements[letter]);
else
resSB.Append(letter);
}
var resString = resSB.ToString();
I would like to automatically parse a range of numbered sequences from an already sorted List<FileData> of filenames by checking which part of the filename changes.
Here is an example (file extension has already been removed):
First filename: IMG_0000
Last filename: IMG_1000
Numbered Range I need: 0000 and 1000
Except I need to deal with every possible type of file naming convention such as:
0000 ... 9999
20080312_0000 ... 20080312_9999
IMG_0000 - Copy ... IMG_9999 - Copy
8er_green3_00001 .. 8er_green3_09999
etc.
I would like the entire 0-padded range e.g. 0001 not just 1
The sequence number is 0-padded e.g. 0001
The sequence number can be located anywhere e.g. IMG_0000 - Copy
The range can start and end with anything i.e. doesn't have to start with 1 and end with 9999
Numbers may appear multiple times in the filename of the sequence e.g. 20080312_0000
Whenever I get something working for 8 random test cases, the 9th test breaks everything and I end up re-starting from scratch.
I've currently been comparing only the first and last filenames (as opposed to iterating through all filenames):
void FindRange(List<FileData> files, out string startRange, out string endRange)
{
string firstFile = files.First().ShortName;
string lastFile = files.Last().ShortName;
...
}
Does anyone have any clever ideas? Perhaps something with Regex?
If you're guaranteed to know the files end with the number (eg. _\d+), and are sorted, just grab the first and last elements and that's your range. If the filenames are all the same, you can sort the list to get them in order numerically. Unless I'm missing something obvious here -- where's the problem?
Use a regex to parse out the numbers from the filenames:
^.+\w(\d+)[^\d]*$
From these parsed strings, find the maximum length, and left-pad any that are less than the maximum length with zeros.
Sort these padded strings alphabetically. Take the first and last from this sorted list to give you your min and max numbers.
Firstly, I will assume that the numbers are always zero-padded so that they are the same length. If not then bigger headaches lie ahead.
Secondly, assume that the file names are exactly the same apart from the increment number component.
If these assumptions are true then the algorithm should be to look at each character in the first and last filenames to determine which same-positioned characters do not match.
var start = String.Empty;
var end = String.Empty;
for (var index = 0; index < firstFile.Length; index++)
{
char c = firstFile[index];
if (filenames.Any(filename => filename[index] != c))
{
start += firstFile[index];
end += lastFile[index];
}
}
// convert to int if required
edit: Changed to check every filename until a difference is found. Not as efficient as it could be but very simple and straightforward.
Here is my solution. It works with all of the examples that you have provided and it assumes the input array to be sorted.
Note that it doesn't look exclusively for numbers; it looks for a consistent sequence of characters that might differ across all of the strings. So if you provide it with {"0000", "0001", "0002"} it will hand back "0" and "2" as the start and end strings, since that's the only part of the strings that differ. If you give it {"0000", "0010", "0100"}, it will give you back "00" and "10".
But if you give it {"0000", "0101"}, it will whine since the differing parts of the string are not contiguous. If you would like this behavior modified so it will return everything from the first differing character to the last, that's fine; I can make that change. But if you are feeding it a ton of filenames that will have sequential changes to the number region, this should not be a problem.
public static class RangeFinder
{
public static void FindRange(IEnumerable<string> strings,
out string startRange, out string endRange)
{
using (var e = strings.GetEnumerator()) {
if (!e.MoveNext())
throw new ArgumentException("strings", "No elements.");
if (e.Current == null)
throw new ArgumentException("strings",
"Null element encountered at index 0.");
var template = e.Current;
// If an element in here is true, it means that index differs.
var matchMatrix = new bool[template.Length];
int index = 1;
string last = null;
while (e.MoveNext()) {
if (e.Current == null)
throw new ArgumentException("strings",
"Null element encountered at index " + index + ".");
last = e.Current;
if (last.Length != template.Length)
throw new ArgumentException("strings",
"Element at index " + index + " has incorrect length.");
for (int i = 0; i < template.Length; i++)
if (last[i] != template[i])
matchMatrix[i] = true;
}
// Verify the matrix:
// * There must be at least one true value.
// * All true values must be consecutive.
int start = -1;
int end = -1;
for (int i = 0; i < matchMatrix.Length; i++) {
if (matchMatrix[i]) {
if (end != -1)
throw new ArgumentException("strings",
"Inconsistent match matrix; no usable pattern discovered.");
if (start == -1)
start = i;
} else {
if (start != -1 && end == -1)
end = i;
}
}
if (start == -1)
throw new ArgumentException("strings",
"Strings did not vary; no usable pattern discovered.");
if (end == -1)
end = matchMatrix.Length;
startRange = template.Substring(start, end - start);
endRange = last.Substring(start, end - start);
}
}
}