_documentContent contains the whole document as html view source.
patternToFind contains text to be searched in _documentContent.
Code snippet below works fine if language is English.
The same code however doesn't works at all when it encounters a language like Korean.
Sample Document
Present Tense
The present tense is just as you have learned. You take the dictionary form of a verb, drop the 다, add the appropriate ending.
먹다 - 먹 + 어요 = 먹어요
마시다 - 마시 + 어요 - 마시어요 - 마셔요.
This tense is used to represent what happens in the present. I eat. I drink. It is a general term for the present.
When I am trying to find 먹 the code belows fails.
can someone please suggest some solution to this
using System;
using System.Collections.Generic;
using System.Text;
namespace MultiByteStringHandling
{
class Program
{
static void Main(string[] args)
{
string _documentContent = #"먹다 - 먹 + 어요 = 먹어요";
byte[] patternToFind = Encoding.UTF8.GetBytes("먹");
byte[] DocumentBytes = Encoding.UTF8.GetBytes(_documentContent);
int intByteOffset = indexOf(DocumentBytes, patternToFind);
Console.WriteLine(intByteOffset.ToString());
}
public int indexOf(byte[] data, byte[] pattern)
{
int[] failure = computeFailure(pattern);
int j = 0;
if (data.Length == 0) return 0;
for (int i = 0; i < data.Length; i++)
{
while (j > 0 && pattern[j] != data[i])
{
j = failure[j - 1];
}
if (pattern[j] == data[i])
{
j++;
}
if (j == pattern.Length)
{
return i - pattern.Length + 1;
}
}
return -1;
}
/**
* Computes the failure function using a boot-strapping process,
* where the pattern is matched against itself.
*/
private int[] computeFailure(byte[] pattern)
{
int[] failure = new int[pattern.Length];
int j = 0;
for (int i = 1; i < pattern.Length; i++)
{
while (j > 0 && pattern[j] != pattern[i])
{
j = failure[j - 1];
}
if (pattern[j] == pattern[i])
{
j++;
}
failure[i] = j;
}
return failure;
}
}
}
Seriously, why not just do the following?
var indexFound = documentContent.IndexOf("data");
Converting strings into byte arrays and then searching those doesn't make much sense to me when you're original data is text. You can always find the byte position after if you wish.
UTF-8 is a variable multi-byte format. Searching for English text in Korean data will never match on a direct pattern match. If you are scanning text you would be much better off using .IndexOf(pattern) [as Noldorin pointed out] or .Contains(pattern).
Related
I have tried this:
using System;
using System.Collections;
using System.Collections.Generic;
public class HelloWorld
{
public static string reverseWords(string str){
ArrayList strArr = new ArrayList();
int start = 0;
string revStr = "";
for(int i = 0; i < str.Length; i++){
if(str[i] == ' '){ // if there's a space,
while(start <= str[i - 1]){ // loop thru the iterated values before space
strArr.Add(str[start]); // add them to the ArrayList
start++; // increment `start` until all iterated values are-
} // stored and also for the next word to loop thru
}
}
for(int j = strArr.Count - 1; j >= 0; j--){
revStr += strArr[j] + " "; // keep appending ArrayList values to the-
} // string from the last to the first value
return revStr;
}
public static void Main(string[] args)
{
Console.WriteLine(reverseWords("Our favorite color is Pink"));
//Expected output : Pink is color favorite Our
}
}
And it's giving this error:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
Please help me understand why this is not working. And also, if there's better way to do this ReverseWord function manually(not using any built-in functions at all).
I'm sorry if this is such a noob question. Any constructive criticism is appreciated. Thanks!
Here is a little improved version of your code that actually works for what you are willing to do.
using System;
using System.Collections;
public class HelloWorld
{
public static string reverseWords(string str){
ArrayList strArr = new ArrayList();
string currentWordString = string.Empty;
string revStr = string.Empty;
for(int i = 0; i < str.Length; i++){
if(str[i] == ' '){ // if there's a space,
strArr.Add(currentWordString); // add the accumulated word to the array
currentWordString = string.Empty; // reset accumulator to be used in next iteration
}else {
currentWordString += str[i]; // accumulate the word
}
}
strArr.Add(currentWordString); // add last word to the array
for(int j = strArr.Count - 1; j >= 0; j--){
revStr += strArr[j] + " "; // keep appending ArrayList values to the-
} // string from the last to the first value
return revStr;
}
public static void Main(string[] args)
{
Console.WriteLine(reverseWords("Our favorite color is Pink"));
//Expected output : Pink is color favorite Our
}
}
I'll let you do the remaining. Like removing the trainling space at the end of the sentence. add seperators other than space (e.g comma, semicolons...)
Try this
"Our favorite color is Pink".Split('\u0020').Reverse().ToList().ForEach(x =>
{
Console.WriteLine(x);
});
This will help
public static string ReverseCharacters(string str)
{
if(str == null)
{
throw new ArgumentNullException(nameof(str));
}
int lastIndex = str.Length - 1;
char[] chars = new char[str.Length];
char temp;
for(int i = 0; i < str.Length/2+1; i++)
{
// Swap. You could refactor this to its own method if needed
temp = str[i];
chars[i] = str[lastIndex - i];
chars[lastIndex - i] = temp;
}
return new string(chars);
}
public static string ReverseWords(string str)
{
if (str == null)
{
throw new ArgumentNullException(nameof(str));
}
if (string.IsNullOrWhiteSpace(str))
{
return str;
}
string space = " ";
StringBuilder reversed = new StringBuilder();
// reverse every characters
var reversedCharacters = ReverseCharacters(str);
// split words (space being word separator here)
var reversedWords = reversedCharacters.Split(space);
// for every revered word characters, reverse it back one more time and append.
foreach(var reversedWord in reversedWords)
{
reversed.Append(ReverseCharacters(reversedWord)).Append(space);
}
// remove last extra space
reversed = reversed.Remove(reversed.Length - 1, 1);
return reversed.ToString();
}
Here is the test result:
I'm working on this task on CodeWars: https://www.codewars.com/kata/5667e8f4e3f572a8f2000039/train/csharp.
My code should turn string like this "ZpglnRxqenU" in something like this "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu".
But I'm getting an error: "Z-Pp-Ggg-Lll-...". It returns three letters "l" instead of four letters.
I tried this code on my PC and the result is the same. But when I use debugger it shows the correct result. How can that be?
Here's my code:
using System;
public class Accumul
{
public static String Accum(string s)
{
string result = "";
for (int i = 0; i < s.Length; i++)
{
result += char.ToUpper(s[i]);
for (int j = 0; j < i; j++)
{
result += char.ToLower(s[i]);
}
result += "-";
}
result = result.Remove(s.Length - 1, 1);
return result;
}
}
You are probably checking result when you debug.
But the error is only at the end result = result.Remove(s.Length - 1, 1);
s.Length - 1 removes the 10th character, you need result.Length -1.
I have a question about iterate through the Alphabet.
I would like to have a loop that begins with "a" and ends with "z". After that, the loop begins "aa" and count to "az". after that begins with "ba" up to "bz" and so on...
Anybody know some solution?
Thanks
EDIT: I forgot that I give a char "a" to the function then the function must return b. if u give "bnc" then the function must return "bnd"
First effort, with just a-z then aa-zz
public static IEnumerable<string> GetExcelColumns()
{
for (char c = 'a'; c <= 'z'; c++)
{
yield return c.ToString();
}
char[] chars = new char[2];
for (char high = 'a'; high <= 'z'; high++)
{
chars[0] = high;
for (char low = 'a'; low <= 'z'; low++)
{
chars[1] = low;
yield return new string(chars);
}
}
}
Note that this will stop at 'zz'. Of course, there's some ugly duplication here in terms of the loops. Fortunately, that's easy to fix - and it can be even more flexible, too:
Second attempt: more flexible alphabet
private const string Alphabet = "abcdefghijklmnopqrstuvwxyz";
public static IEnumerable<string> GetExcelColumns()
{
return GetExcelColumns(Alphabet);
}
public static IEnumerable<string> GetExcelColumns(string alphabet)
{
foreach(char c in alphabet)
{
yield return c.ToString();
}
char[] chars = new char[2];
foreach(char high in alphabet)
{
chars[0] = high;
foreach(char low in alphabet)
{
chars[1] = low;
yield return new string(chars);
}
}
}
Now if you want to generate just a, b, c, d, aa, ab, ac, ad, ba, ... you'd call GetExcelColumns("abcd").
Third attempt (revised further) - infinite sequence
public static IEnumerable<string> GetExcelColumns(string alphabet)
{
int length = 0;
char[] chars = null;
int[] indexes = null;
while (true)
{
int position = length-1;
// Try to increment the least significant
// value.
while (position >= 0)
{
indexes[position]++;
if (indexes[position] == alphabet.Length)
{
for (int i=position; i < length; i++)
{
indexes[i] = 0;
chars[i] = alphabet[0];
}
position--;
}
else
{
chars[position] = alphabet[indexes[position]];
break;
}
}
// If we got all the way to the start of the array,
// we need an extra value
if (position == -1)
{
length++;
chars = new char[length];
indexes = new int[length];
for (int i=0; i < length; i++)
{
chars[i] = alphabet[0];
}
}
yield return new string(chars);
}
}
It's possible that it would be cleaner code using recursion, but it wouldn't be as efficient.
Note that if you want to stop at a certain point, you can just use LINQ:
var query = GetExcelColumns().TakeWhile(x => x != "zzz");
"Restarting" the iterator
To restart the iterator from a given point, you could indeed use SkipWhile as suggested by thesoftwarejedi. That's fairly inefficient, of course. If you're able to keep any state between call, you can just keep the iterator (for either solution):
using (IEnumerator<string> iterator = GetExcelColumns())
{
iterator.MoveNext();
string firstAttempt = iterator.Current;
if (someCondition)
{
iterator.MoveNext();
string secondAttempt = iterator.Current;
// etc
}
}
Alternatively, you may well be able to structure your code to use a foreach anyway, just breaking out on the first value you can actually use.
Edit: Made it do exactly as the OP's latest edit wants
This is the simplest solution, and tested:
static void Main(string[] args)
{
Console.WriteLine(GetNextBase26("a"));
Console.WriteLine(GetNextBase26("bnc"));
}
private static string GetNextBase26(string a)
{
return Base26Sequence().SkipWhile(x => x != a).Skip(1).First();
}
private static IEnumerable<string> Base26Sequence()
{
long i = 0L;
while (true)
yield return Base26Encode(i++);
}
private static char[] base26Chars = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
private static string Base26Encode(Int64 value)
{
string returnValue = null;
do
{
returnValue = base26Chars[value % 26] + returnValue;
value /= 26;
} while (value-- != 0);
return returnValue;
}
The following populates a list with the required strings:
List<string> result = new List<string>();
for (char ch = 'a'; ch <= 'z'; ch++){
result.Add (ch.ToString());
}
for (char i = 'a'; i <= 'z'; i++)
{
for (char j = 'a'; j <= 'z'; j++)
{
result.Add (i.ToString() + j.ToString());
}
}
I know there are plenty of answers here, and one's been accepted, but IMO they all make it harder than it needs to be. I think the following is simpler and cleaner:
static string NextColumn(string column){
char[] c = column.ToCharArray();
for(int i = c.Length - 1; i >= 0; i--){
if(char.ToUpper(c[i]++) < 'Z')
break;
c[i] -= (char)26;
if(i == 0)
return "A" + new string(c);
}
return new string(c);
}
Note that this doesn't do any input validation. If you don't trust your callers, you should add an IsNullOrEmpty check at the beginning, and a c[i] >= 'A' && c[i] <= 'Z' || c[i] >= 'a' && c[i] <= 'z' check at the top of the loop. Or just leave it be and let it be GIGO.
You may also find use for these companion functions:
static string GetColumnName(int index){
StringBuilder txt = new StringBuilder();
txt.Append((char)('A' + index % 26));
//txt.Append((char)('A' + --index % 26));
while((index /= 26) > 0)
txt.Insert(0, (char)('A' + --index % 26));
return txt.ToString();
}
static int GetColumnIndex(string name){
int rtn = 0;
foreach(char c in name)
rtn = rtn * 26 + (char.ToUpper(c) - '#');
return rtn - 1;
//return rtn;
}
These two functions are zero-based. That is, "A" = 0, "Z" = 25, "AA" = 26, etc. To make them one-based (like Excel's COM interface), remove the line above the commented line in each function, and uncomment those lines.
As with the NextColumn function, these functions don't validate their inputs. Both with give you garbage if that's what they get.
Here’s what I came up with.
/// <summary>
/// Return an incremented alphabtical string
/// </summary>
/// <param name="letter">The string to be incremented</param>
/// <returns>the incremented string</returns>
public static string NextLetter(string letter)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (!string.IsNullOrEmpty(letter))
{
char lastLetterInString = letter[letter.Length - 1];
// if the last letter in the string is the last letter of the alphabet
if (alphabet.IndexOf(lastLetterInString) == alphabet.Length - 1)
{
//replace the last letter in the string with the first leter of the alphbat and get the next letter for the rest of the string
return NextLetter(letter.Substring(0, letter.Length - 1)) + alphabet[0];
}
else
{
// replace the last letter in the string with the proceeding letter of the alphabet
return letter.Remove(letter.Length-1).Insert(letter.Length-1, (alphabet[alphabet.IndexOf(letter[letter.Length-1])+1]).ToString() );
}
}
//return the first letter of the alphabet
return alphabet[0].ToString();
}
just curious , why not just
private string alphRecursive(int c) {
var alphabet = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
if (c >= alphabet.Length) {
return alphRecursive(c/alphabet.Length) + alphabet[c%alphabet.Length];
} else {
return "" + alphabet[c%alphabet.Length];
}
}
This is like displaying an int, only using base 26 in stead of base 10. Try the following algorithm to find the nth entry of the array
q = n div 26;
r = n mod 26;
s = '';
while (q > 0 || r > 0) {
s = alphabet[r] + s;
q = q div 26;
r = q mod 26;
}
Of course, if you want the first n entries, this is not the most efficient solution. In this case, try something like daniel's solution.
I gave this a go and came up with this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Alphabetty
{
class Program
{
const string alphabet = "abcdefghijklmnopqrstuvwxyz";
static int cursor = 0;
static int prefixCursor;
static string prefix = string.Empty;
static bool done = false;
static void Main(string[] args)
{
string s = string.Empty;
while (s != "Done")
{
s = GetNextString();
Console.WriteLine(s);
}
Console.ReadKey();
}
static string GetNextString()
{
if (done) return "Done";
char? nextLetter = GetNextLetter(ref cursor);
if (nextLetter == null)
{
char? nextPrefixLetter = GetNextLetter(ref prefixCursor);
if(nextPrefixLetter == null)
{
done = true;
return "Done";
}
prefix = nextPrefixLetter.Value.ToString();
nextLetter = GetNextLetter(ref cursor);
}
return prefix + nextLetter;
}
static char? GetNextLetter(ref int letterCursor)
{
if (letterCursor == alphabet.Length)
{
letterCursor = 0;
return null;
}
char c = alphabet[letterCursor];
letterCursor++;
return c;
}
}
}
Here is something I had cooked up that may be similar. I was experimenting with iteration counts in order to design a numbering schema that was as small as possible, yet gave me enough uniqueness.
I knew that each time a added an Alpha character, it would increase the possibilities 26x but I wasn't sure how many letters, numbers, or the pattern I wanted to use.
That lead me to the code below. Basically you pass it an AlphaNumber string, and every position that has a Letter, would eventually increment to "z\Z" and every position that had a Number, would eventually increment to "9".
So you can call it 1 of two ways..
//This would give you the next Itteration... (H3reIsaStup4dExamplf)
string myNextValue = IncrementAlphaNumericValue("H3reIsaStup4dExample")
//Or Loop it resulting eventually as "Z9zzZzzZzzz9zZzzzzzz"
string myNextValue = "H3reIsaStup4dExample"
while (myNextValue != null)
{
myNextValue = IncrementAlphaNumericValue(myNextValue)
//And of course do something with this like write it out
}
(For me, I was doing something like "1AA000")
public string IncrementAlphaNumericValue(string Value)
{
//We only allow Characters a-b, A-Z, 0-9
if (System.Text.RegularExpressions.Regex.IsMatch(Value, "^[a-zA-Z0-9]+$") == false)
{
throw new Exception("Invalid Character: Must be a-Z or 0-9");
}
//We work with each Character so it's best to convert the string to a char array for incrementing
char[] myCharacterArray = Value.ToCharArray();
//So what we do here is step backwards through the Characters and increment the first one we can.
for (Int32 myCharIndex = myCharacterArray.Length - 1; myCharIndex >= 0; myCharIndex--)
{
//Converts the Character to it's ASCII value
Int32 myCharValue = Convert.ToInt32(myCharacterArray[myCharIndex]);
//We only Increment this Character Position, if it is not already at it's Max value (Z = 90, z = 122, 57 = 9)
if (myCharValue != 57 && myCharValue != 90 && myCharValue != 122)
{
myCharacterArray[myCharIndex]++;
//Now that we have Incremented the Character, we "reset" all the values to the right of it
for (Int32 myResetIndex = myCharIndex + 1; myResetIndex < myCharacterArray.Length; myResetIndex++)
{
myCharValue = Convert.ToInt32(myCharacterArray[myResetIndex]);
if (myCharValue >= 65 && myCharValue <= 90)
{
myCharacterArray[myResetIndex] = 'A';
}
else if (myCharValue >= 97 && myCharValue <= 122)
{
myCharacterArray[myResetIndex] = 'a';
}
else if (myCharValue >= 48 && myCharValue <= 57)
{
myCharacterArray[myResetIndex] = '0';
}
}
//Now we just return an new Value
return new string(myCharacterArray);
}
}
//If we got through the Character Loop and were not able to increment anything, we retun a NULL.
return null;
}
Here's my attempt using recursion:
public static void PrintAlphabet(string alphabet, string prefix)
{
for (int i = 0; i < alphabet.Length; i++) {
Console.WriteLine(prefix + alphabet[i].ToString());
}
if (prefix.Length < alphabet.Length - 1) {
for (int i = 0; i < alphabet.Length; i++) {
PrintAlphabet(alphabet, prefix + alphabet[i]);
}
}
}
Then simply call PrintAlphabet("abcd", "");
While looking at memory-mapped files in C#, there was some difficulty in identifying how to search a file quickly forward and in reverse. My goal is to rewrite the following function in the language, but nothing could be found like the find and rfind methods used below. Is there a way in C# to quickly search a memory-mapped file using a particular substring?
#! /usr/bin/env python3
import mmap
import pathlib
# noinspection PyUnboundLocalVariable
def drop_last_line(path):
with path.open('r+b') as file:
with mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as search:
for next_line in b'\r\n', b'\r', b'\n':
if search.find(next_line) >= 0:
break
else:
raise ValueError('cannot find any line delimiters')
end_1st = search.rfind(next_line)
end_2nd = search.rfind(next_line, 0, end_1st - 1)
file.truncate(0 if end_2nd < 0 else end_2nd + len(next_line))
Is there a way in C# to quickly search a memory-mapped file using a particular substring?
Do you know of any way to memory-map an entire file in C# and then treat it as a byte array?
Yes, it's quite easy to map an entire file into a view then to read it into a single byte array as the following code shows:
static void Main(string[] args)
{
var sourceFile= new FileInfo(#"C:\Users\Micky\Downloads\20180112.zip");
int length = (int) sourceFile.Length; // length of target file
// Create the memory-mapped file.
using (var mmf = MemoryMappedFile.CreateFromFile(sourceFile.FullName,
FileMode.Open,
"ImgA"))
{
var buffer = new byte[length]; // allocate a buffer with the same size as the file
using (var accessor = mmf.CreateViewAccessor())
{
var read=accessor.ReadArray(0, buffer, 0, length); // read the whole thing
}
// let's try searching for a known byte sequence. Change this to suit your file
var target = new byte[] {71, 213, 62, 204,231};
var foundAt = IndexOf(buffer, target);
}
}
I couldn't seem to find any byte searching method in Marshal or Array but you can use this search algorithm courtesy of Social MSDN as a start:
private static int IndexOf2(byte[] input, byte[] pattern)
{
byte firstByte = pattern[0];
int index = -1;
if ((index = Array.IndexOf(input, firstByte)) >= 0)
{
for (int i = 0; i < pattern.Length; i++)
{
if (index + i >= input.Length ||
pattern[i] != input[index + i]) return -1;
}
}
return index;
}
...or even this more verbose example (also courtesy Social MSDN, same link)
public static int IndexOf(byte[] arrayToSearchThrough, byte[] patternToFind)
{
if (patternToFind.Length > arrayToSearchThrough.Length)
return -1;
for (int i = 0; i < arrayToSearchThrough.Length - patternToFind.Length; i++)
{
bool found = true;
for (int j = 0; j < patternToFind.Length; j++)
{
if (arrayToSearchThrough[i + j] != patternToFind[j])
{
found = false;
break;
}
}
if (found)
{
return i;
}
}
return -1;
}
My program needs to check for 3 consecutive letters in a string (and check through the whole string). I could make it check for them in a harcoded manner, like "check for qwe", "check for "wer", check for "ert", but that looks messy and badly done.
static void Main(string[] args)
{
string BadLetters = "qwertyuiopasdfghjklzxcvbnm";
string password = "Blablauio";
for (int i = 1; i <= 30; i++)
{
// This checks if it contains "qwe" but i want it to
// cycle through the rest (such as "wer" or "rty")
if (password.Contains(BadLetters.Substring(0, 3))) {
Console.WriteLine("password contains 3 consequtive letters in BadLetters");
}
}
Console.ReadKey();
}
The problem is that this only checks the first 3 letters of BadLetters (qwe), and it doesn't look for "ert", etc.
It would be better if you loop on the password variable instead, like this:
string badLetters = "qwertyuiopasdfghjklzxcvbnm";
string password = "Blablauio";
for (int i = 0; i < password.Length-2; i++)
{
if (badLetters.Contains(password.Substring(i,3)))
{
Console.WriteLine("password contains 3 consequtive letters in BadLetters");
}
}
Obviously you also have to check that the password is at least 3 characters.
This loop could fail on keyboard row crossing letters, i.e. "opa" or "pas", that should be considered right values, so you could do this instead:
string badLettersR1 = "qwertyuiop";
string badLettersR2 = "asdfghjkl";
string badLettersR3 = "zxcvbnm";
string password = "Blablauio";
for (int i = 0; i < password.Length-2; i++)
{
if (badLettersR1.Contains(password.Substring(i,3)) ||
badLettersR2.Contains(password.Substring(i,3)) ||
badLettersR3.Contains(password.Substring(i,3)))
{
Console.WriteLine("password contains 3 consequtive letters in BadLetters");
}
}
Maybe you can try to edit your string, so the 3 letters are gone and then you do the same job as before for the next ones.
Or you can add to the 2 variables firstletter and secondletter 4 so it skips the first 3 as well and is repeating with the following ones
Sry i understood your string as 3 in a row...
You need to iterate through each character in password, find its index in BadLetters, and check if the next two characters in password match the next two in BadLetters. I also changed the stop condition of the for loop because you only need to iterate through the antepenultimate character in password
string BadLetters = "qwertyuiopasdfghjklzxcvbnm";
string password = "Blablauio";
for (int i = 0; i < password.Length - 2; i++)
{
var j = BadLetters.IndexOf(password[i]);
if (j > -1 && j + 2 < BadLetters.Length &&
password[i + 1] == BadLetters[j + 1] &&
password[i + 2] == BadLetters[j + 2])
{
Console.WriteLine("password contains 3 consequtive letters in BadLetters");
}
}
The simple one I found is
using System;
public class Program
{
public static void Main()
{
string BadLetters = "qwertyuiopasdfghjklzxcvbnm";
string password = "Blablauio";
Console.WriteLine(password.IndexOf(BadLetters.Substring(0,3))>=0?"Present":"Not Present");
Console.ReadLine();
}
}
You may need to check for null condition of both strings
TL; DR
You could just go through array of symbols and compare it's indexes like this:
if (BadLetters.IndexOf(my_word[i]) - BadLetter.IndexOf(my_word[i-1]) == 1) {
Console.WriteLine("Consequent letters detected!");
}
you could just count consequent letters and alert when count more then 3
I provide detailed code with all lines from keyboard. And you could add another lines (i.e. upper case) without any modification of code.
You also have control on N - number of forbidden consequent characters in string.
Also there is Check method which using only for demonstrate results of working:
q - ok
qw - ok
qwe - password contains 3 consequtive letters in BadLetters
abdfsk - ok
ehjk - password contains 3 consequtive letters in BadLetters
bnm - password contains 3 consequtive letters in BadLetters
The code
Code on .net fiddle: https://dotnetfiddle.net/4oILkj
public static String[] KeyboardLines = new [] {
"1234567890",
"qwertyuiop[]",
"asdfghjkl;'\\",
"`zxcvbnm,./"
};
public static Int32 GetLine(char c){
for (int i = 0; i < KeyboardLines.Length; i++) {
if (KeyboardLines[i].IndexOf(c) > -1) {
return i;
};
}
return -1;
}
public static bool HasConsequenceLetters(string str, int n = 3) {
if (str.Length < n) {
return false;
}
char previousLetter = str[0];
int previousLine = GetLine(previousLetter);
int previousLetterIndex = KeyboardLines[previousLine].IndexOf(previousLetter);
Int32 consequentLettersCount = 1;
for (int i = 1; i < str.Length; i++) {
var currentLetter = str[i];
var currentLine = GetLine(currentLetter);
var currentLetterIndex = KeyboardLines[currentLine].IndexOf(currentLetter);
if (currentLine != -1 && currentLine == previousLine) {
if (currentLetterIndex - previousLetterIndex == 1) {
consequentLettersCount += 1;
}
}
else {
consequentLettersCount = 1;
}
if (consequentLettersCount == n) {
return true;
}
previousLetter = currentLetter;
previousLetterIndex = currentLetterIndex;
previousLine = currentLine;
}
return false;
}
Improvements
This approach could be improved if GetLine function will return character index with line number and then compare not just characters but pairs (LetterLine, LetterIndex). But this require from us to using tuples or classes but I don't think you really want this.