Related
Let's say I have strings (Java version strings) like this:
"1.8.0_101"
"1.8.0_91"
Is there an easy way to determine which version is higher in c/c++?
I have used strcmp() so far. It compares version strings like "1.7.0" with "1.8.0" correctly, but when underscore character is involved it does not work anymore (because underscore has higher value in ASCII table than all numbers)
Disclaimer: this should be just an example to give you an idea of implementation. Your question lacks various points in order to make it a good question, anyway I'm going to try to help and give you a runnable example. The code could be improved and make it more robust.
struct Version {
int m_major;
int m_minor;
int m_release;
int m_minor_release;
Version() : m_major(-1),
m_minor(-1),
m_release(-1),
m_minor_release(-1) { }
// Convert a str to a version object
explicit Version(const std::string& str_version) : Version() {
size_t offset = 0;
size_t finder;
// Cycle until find a character as '.' '-' '_'
while (finder = str_version.find_first_of(".-_", offset),
finder != std::string::npos) {
int* p = nullptr;
if (m_major == -1) {
p = &m_major;
} else if (m_minor == -1) {
p = &m_minor;
} else if (m_release == -1) {
p = &m_release;
} else {
p = &m_minor_release;
}
*p = std::stoi(str_version.substr(offset, finder - offset));
offset = finder + 1;
}
if (offset != str_version.size() && m_minor_release == -1) {
m_minor_release = std::stoi(
str_version.substr(offset,std::string::npos));
}
}
bool operator<(const Version& oth) const {
// compare this version with another one
}
};
I hope you can find this little example useful for your problem implementation.
What would be the most efficient yet simplest way to convert a char to its unsigned binary representation (bytes where the MSB is 0)? I have a method set up like this:
string AsciiToBinary(char value) {
string binary = "";
int code = value;
while (code > 0) {
if ((code % 2) == 1) {
binary.append("1", 1);
} else {
binary.append("0", 1);
}
code = code / 2;
}
return binary;
}
I am assuming that setting an int to a char sets the char's ASCII value to the int. However, my results do not match the ASCII table. I am implementing this function as follows:
char head = stack.pop();
int code = head; // do not need to parse
string binary;
while (!stack.isEmpty()) {
binary = AsciiToBinary(code);
outfile << binary << endl;
binary.clear();
head = stack.pop();
code = head;
}
I have stored all of the chars in a stack.
Thank you for info and direction.
std::string::append() adds the character onto the end of the string. So you are putting the bits on in the reverse order: the LSB is the first character and vice versa. Try this: binary.insert (0, 1, (code % 2 == 1) ? '1' : '0');
This method works well and is editable for all those interested and learning C++:
using namespace std; // bad for updates
#include <string>
string AsciiToBinary(char value) {
string binary = "";
unsigned int code = value;
unsigned int chk = value;
while (code > 0) {
if ((code & 1) == 1) {
binary.append("1", 1);
} else {
binary.append("0", 1);
}
code = code / 2;
}
reverse(binary.begin(), binary.end());
if (chk < 64) {
binary.insert(0, "00");
} else {
binary.insert(0, "0");
}
return binary;
}
Recently I was asked in an interview to convert the string "aabbbccccddddd" to "a2b3c4d5". The goal is to replace each repeated character with a single occurrence and a repeat count. Here 'a' is repeated twice in the input, so we have to write it as 'a2' in the output. Also I need to write a function to reverse the format back to the original one (e.g. from the string "a2b3c4d5" to "aabbbccccddddd"). I was free to use either C or C++. I wrote the below code, but the interviewer seemed to be not very happy with this. He asked me to try a smarter way than this.
In the below code, I used formatstring() to eliminate repeated chars by just adding the repeated count and used reverseformatstring() to convert back to the original string.
void formatstring(char* target, const char* source) {
int charRepeatCount = 1;
bool isFirstChar = true;
while (*source != '\0') {
if (isFirstChar) {
// Always add the first character to the target
isFirstChar = false;
*target = *source;
source++; target++;
} else {
// Compare the current char with previous one,
// increment repeat count
if (*source == *(source-1)) {
charRepeatCount++;
source++;
} else {
if (charRepeatCount > 1) {
// Convert repeat count to string, append to the target
char repeatStr[10];
_snprintf(repeatStr, 10, "%i", charRepeatCount);
int repeatCount = strlen(repeatStr);
for (int i = 0; i < repeatCount; i++) {
*target = repeatStr[i];
target++;
}
charRepeatCount = 1; // Reset repeat count
}
*target = *source;
source++; target++;
}
}
}
if (charRepeatCount > 1) {
// Convert repeat count to string, append it to the target
char repeatStr[10];
_snprintf(repeatStr, 10, "%i", charRepeatCount);
int repeatCount = strlen(repeatStr);
for (int i = 0; i < repeatCount; i++) {
*target = repeatStr[i];
target++;
}
}
*target = '\0';
}
void reverseformatstring(char* target, const char* source) {
int charRepeatCount = 0;
bool isFirstChar = true;
while (*source != '\0') {
if (isFirstChar) {
// Always add the first character to the target
isFirstChar = false;
*target = *source;
source++; target++;
} else {
// If current char is alpha, add it to the target
if (isalpha(*source)) {
*target = *source;
target++; source++;
} else {
// Get repeat count of previous character
while (isdigit(*source)) {
int currentDigit = (*source) - '0';
charRepeatCount = (charRepeatCount == 0) ?
currentDigit : (charRepeatCount * 10 + currentDigit);
source++;
}
// Decrement repeat count as we have already written
// the first unique char to the target
charRepeatCount--;
// Repeat the last char for this count
while (charRepeatCount > 0) {
*target = *(target - 1);
target++;
charRepeatCount--;
}
}
}
}
*target = '\0';
}
I didn't find any issues with above code. Is there any other better way of doing this?
The approach/algorithm is fine, perhaps you could refine and shrink the code a bit (by doing something simpler, there's no need to solve this in an overly complex way). And choose an indentation style that actually makes sense.
A C solution:
void print_transform(const char *input)
{
for (const char *s = input; *s;) {
char current = *s;
size_t count = 1;
while (*++s == current) {
count++;
}
if (count > 1) {
printf("%c%zu", current, count);
} else {
putc(current, stdout);
}
}
putc('\n', stdout);
}
(This can be easily modified so that it returns the transformed string instead, or writes it to a long enough buffer.)
A C++ solution:
std::string transform(const std::string &input)
{
std::stringstream ss;
std::string::const_iterator it = input.begin();
while (it != input.end()) {
char current = *it;
std::size_t count = 1;
while (++it != input.end() && *it == current) {
count++;
}
if (count > 1) {
ss << current << count;
} else {
ss << current;
}
}
return ss.str();
}
Since several others have suggested very reasonable alternatives, I'd like to offer some opinions on what I think is your underlying question: "He asked me to try a smarter way than this.... Is there any other better way of doing this?"
When I interview a developer, I'm looking for signals that tell me how she approaches a problem:
Most important, as H2CO3 noted, is correctness: will the code work? I'm usually happy to overlook small syntax errors (forgotten semicolons, mismatched parens or braces, and so on) if the algorithm is sensible.
Proper use of the language, especially if the candidate claims expertise or has had extensive experience. Does he understand and use idioms appropriately to write straightforward, uncomplicated code?
Can she explain her train of thought as she formulates her solution? Is it logical and coherent, or is it a shotgun approach? Is she able and willing to communicate well?
Does he account for edge cases? And if so, does the intrinsic algorithm handle them, or is everything a special case? Although I'm happiest if the initial algorithm "just works" for all cases, I think it's perfectly acceptable to start with a verbose approach that covers all cases (or simply to add a "TODO" comment, noting that more work needs to be done), and then simplifying later, when it may be easier to notice patterns or duplicated code.
Does she consider error-handling? Usually, if a candidate starts by asking whether she can assume the input is valid, or with a comment like, "If this were production code, I'd check for x, y, and z problems," I'll ask what she would do, then suggest she focus on a working algorithm for now and (maybe) come back to that later. But I'm disappointed if a candidate doesn't mention it.
Testing, testing, testing! How will the candidate verify his code works? Does he walk through the code and suggest test cases, or do I need to remind him? Are the test cases sensible? Will they cover the edge cases?
Optimization: as a final step, after everything works and has been validated, I'll sometimes ask the candidate if she can improve her code. Bonus points if she suggests it without my prodding; negative points if she spends a lot of effort worrying about it before the code even works.
Applying these ideas to the code you wrote, I'd make these observations:
Using const appropriately is a plus, as it shows familiarity with the language. During an interview I'd probably ask a question or two about why/when to use it.
The proper use of char pointers throughout the code is a good sign. I tend to be pedantic about making the data types explicit within comparisons, particularly during interviews, so I'm happy to see, e.g.
while (*source != '\0') rather than the (common, correct, but IMO less careful) while(*source).
isFirstChar is a bit of a red flag, based on my "edge cases" point. When you declare a boolean to keep track of the code's state, there's often a way of re-framing the problem to handle the condition intrinsically. In this case, you can use charRepeatCount to decide if this is the first character in a possible series, so you won't need to test explicitly for the first character in the string.
By the same token, repeated code can also be a sign that an algorithm can be simplified. One improvement would be to move the conversion of charRepeatCount to a separate function. See below for an even better solution.
It's funny, but I've found that candidates rarely add comments to their code during interviews. Kudos for helpful ones, negative points for those of the ilk "Increment the counter" that add verbosity without information. It's generally accepted that, unless you're doing something weird (in which case you should reconsider what you've written), you should assume the person who reads your code is familiar with the programming language. So comments should explain your thought process, not translate the code back to English.
Excessive levels of nested conditionals or loops can also be a warning. You can eliminate one level of nesting by comparing each character to the next one instead of the previous one. This works even for the last character in the string, because it will be compared to the terminating null character, which won't match and can be treated like any other character.
There are simpler ways to convert charRepeatCount from an int to a string. For example, _snprintf() returns the number of bytes it "prints" to the string, so you can use
target += _snprintf(target, 10, "%i", charRepeatCount);
In the reversing function, you've used the ternary operator perfectly ... but it's not necessary to special-case the zero value: the math is the same regardless of its value. Again, there are also standard utility functions like atoi() that will convert the leading digits of a string into an integer for you.
Experienced developers will often include the increment or decrement operation as part of the condition in a loop, rather than as a separate statement at the bottom: while(charRepeatCount-- > 0). I'd raise an eyebrow but give you a point or two for humor and personality if you wrote this using the slide operator: while (charRepeatCount --> 0). But only if you'd promise not to use it in production.
Good luck with your interviewing!
I think your code is too complex for the task. Here's my approach (using C):
#include <ctype.h>
#include <stdio.h>
void format_str(char *target, char *source) {
int count;
char last;
while (*source != '\0') {
*target = *source;
last = *target;
target++;
source++;
for (count = 1; *source == last; source++, count++)
; /* Intentionally left blank */
if (count > 1)
target += sprintf(target, "%d", count);
}
*target = '\0';
}
void convert_back(char *target, char *source) {
char last;
int val;
while (*source != '\0') {
if (!isdigit((unsigned char) *source)) {
last = *source;
*target = last;
target++;
source++;
}
else {
for (val = 0; isdigit((unsigned char) *source); val = val*10 + *source - '0', source++)
; /* Intentionally left blank */
while (--val) {
*target = last;
target++;
}
}
}
*target = '\0';
}
format_str compresses the string, and convert_back uncompresses it.
Your code "works", but it doesn't adhere to some common patterns used in C++. You should have:
used std::string instead of plain char* array(s)
pass that string as const reference to avoid modification, since you write the result somewhere else;
use C++11 features such as ranged based for loops and lambdas as well.
I think the interviewer's purpose was to test your ability to deal with the C++11 standard, since the algorithm itself was pretty trivial.
Perhaps the interviewer wanted to test your knowledge of existing standard library tools. Here's how my take could look in C++:
#include <string>
#include <sstream>
#include <algorithm>
#include <iostream>
typedef std::string::const_iterator Iter;
std::string foo(Iter first, Iter last)
{
Iter it = first;
std::ostringstream result;
while (it != last) {
it = std::find_if(it, last, [=](char c){ return c != *it; });
result << *first << (it - first);
first = it;
}
return result.str();
}
int main()
{
std::string s = "aaabbbbbbccddde";
std::cout << foo(s.begin(), s.end());
}
An extra check is needed for empty input.
try this
std::string str="aabbbccccddddd";
for(int i=0;i<255;i++)
{
int c=0;
for(int j=0;j<str.length();j++)
{
if(str[j] == i)
c++;
}
if(c>0)
printf("%c%d",i,c);
}
My naive approach:
void pack( char const * SrcStr, char * DstBuf ) {
char const * Src_Ptr = SrcStr;
char * Dst_Ptr = DstBuf;
char c = 0;
int RepeatCount = 1;
while( '\0' != *Src_Ptr ) {
c = *Dst_Ptr = *Src_Ptr;
++Src_Ptr; ++Dst_Ptr;
for( RepeatCount = 1; *Src_Ptr == c; ++RepeatCount ) {
++Src_Ptr;
}
if( RepeatCount > 1 ) {
Dst_Ptr += sprintf( Dst_Ptr, "%i", RepeatCount );
RepeatCount = 1;
}
}
*Dst_Ptr = '\0';
};
void unpack( char const * SrcStr, char * DstBuf ) {
char const * Src_Ptr = SrcStr;
char * Dst_Ptr = DstBuf;
char c = 0;
while( '\0' != *Src_Ptr ) {
if( !isdigit( *Src_Ptr ) ) {
c = *Dst_Ptr = *Src_Ptr;
++Src_Ptr; ++Dst_Ptr;
} else {
int repeat_count = strtol( Src_Ptr, (char**)&Src_Ptr, 10 );
memset( Dst_Ptr, c, repeat_count - 1 );
Dst_Ptr += repeat_count - 1;
}
}
*Dst_Ptr = '\0';
};
But if interviewer asks for error-handling than solution turns to be much more complex (and ugly). My portable approach:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
// for MSVC
#ifdef _WIN32
#define snprintf sprintf_s
#endif
int pack( char const * SrcStr, char * DstBuf, size_t DstBuf_Size ) {
int Err = 0;
char const * Src_Ptr = SrcStr;
char * Dst_Ptr = DstBuf;
size_t SrcBuf_Size = strlen( SrcStr ) + 1;
char const * SrcBuf_End = SrcStr + SrcBuf_Size;
char const * DstBuf_End = DstBuf + DstBuf_Size;
char c = 0;
int RepeatCount = 1;
// don't forget about buffers intercrossing
if( !SrcStr || !DstBuf || 0 == DstBuf_Size \
|| (DstBuf < SrcBuf_End && DstBuf_End > SrcStr) ) {
return 1;
}
// source string must contain no digits
// check for destination buffer overflow
while( '\0' != *Src_Ptr && Dst_Ptr < DstBuf_End - 1 \
&& !isdigit( *Src_Ptr ) && !Err ) {
c = *Dst_Ptr = *Src_Ptr;
++Src_Ptr; ++Dst_Ptr;
for( RepeatCount = 1; *Src_Ptr == c; ++RepeatCount ) {
++Src_Ptr;
}
if( RepeatCount > 1 ) {
int res = snprintf( Dst_Ptr, DstBuf_End - Dst_Ptr - 1, "%i" \
, RepeatCount );
if( res < 0 ) {
Err = 1;
} else {
Dst_Ptr += res;
RepeatCount = 1;
}
}
}
*Dst_Ptr = '\0';
return Err;
};
int unpack( char const * SrcStr, char * DstBuf, size_t DstBuf_Size ) {
int Err = 0;
char const * Src_Ptr = SrcStr;
char * Dst_Ptr = DstBuf;
size_t SrcBuf_Size = strlen( SrcStr ) + 1;
char const * SrcBuf_End = SrcStr + SrcBuf_Size;
char const * DstBuf_End = DstBuf + DstBuf_Size;
char c = 0;
// don't forget about buffers intercrossing
// first character of source string must be non-digit
if( !SrcStr || !DstBuf || 0 == DstBuf_Size \
|| (DstBuf < SrcBuf_End && DstBuf_End > SrcStr) || isdigit( SrcStr[0] ) ) {
return 1;
}
// check for destination buffer overflow
while( '\0' != *Src_Ptr && Dst_Ptr < DstBuf_End - 1 && !Err ) {
if( !isdigit( *Src_Ptr ) ) {
c = *Dst_Ptr = *Src_Ptr;
++Src_Ptr; ++Dst_Ptr;
} else {
int repeat_count = strtol( Src_Ptr, (char**)&Src_Ptr, 10 );
if( !repeat_count || repeat_count - 1 > DstBuf_End - Dst_Ptr - 1 ) {
Err = 1;
} else {
memset( Dst_Ptr, c, repeat_count - 1 );
Dst_Ptr += repeat_count - 1;
}
}
}
*Dst_Ptr = '\0';
return Err;
};
int main() {
char str[] = "aabbbccccddddd";
char buf1[128] = {0};
char buf2[128] = {0};
pack( str, buf1, 128 );
printf( "pack: %s -> %s\n", str, buf1 );
unpack( buf1, buf2, 128 );
printf( "unpack: %s -> %s\n", buf1, buf2 );
return 0;
}
Test: http://ideone.com/Y7FNE3. Also works in MSVC.
Try to make do with less boilerplate:
#include <iostream>
#include <iterator>
#include <sstream>
using namespace std;
template<typename in_iter,class ostream>
void torle(in_iter i, ostream &&o)
{
while (char c = *i++) {
size_t n = 1;
while ( *i == c )
++n, ++i;
o<<c<<n;
}
}
template<class istream, typename out_iter>
void fromrle(istream &&i, out_iter o)
{
char c; size_t n;
while (i>>c>>n)
while (n--) *o++=c;
}
int main()
{
typedef ostream_iterator<char> to;
string line; stringstream converted;
while (getline(cin,line)) {
torle(begin(line),converted);
cout<<converted.str()<<'\n';
fromrle(converted,ostream_iterator<char>(cout));
cout<<'\n';
}
}
On a test data set the following code works, but when I change to a second test set with a similar size it overflows.
To change a string of tokens into an associated new string of tokens I use this vector lookup function
//looks for input string in vector and returns output, 'c' is check row, 'r' is return row
string vectorSearch(string &check, int &direction, int n, int c, int r, int level)
{
if ((direction == 1 && check.length() <= 1) || n == list.size()-1 ||(direction == 0 && check.length() > 1)) { //if reading and string is 1 char then pass over
if (direction == 1){ //convert '???' into '?'
string temp = "";
bool wildToken = false;
for (unsigned int i = 0; i < check.length(); i++) {
temp+='?';
if (check.compare(temp) == 0) { check = '?'; wildToken = false; } //done,'???" case, return '?' token
else if (check[i] == '?') wildToken = true; //not done searching
}
}
return check;
} else {
if (list[n][c] == check || list[n][c] == ('0'+check)) //add dummy '0'
return list[n][r];
else
return vectorSearch (check, direction, n+1, c, r, level);
}
}
After working fine for a dozen conversions the stack overflows
vectorSearch is called from this function
//this function takes an ontology and direction==1 (default) changes from string
//to single char or if direction==0 takes single char and converts to string representation
string Lexicon::convertOntology(string input, int level, int direction, string out, string temp)
{
if (input == "" && temp == "")
return out; //check for completed conversion
else {
if (direction == 0 || input[0] == '.' || input[0] == '-' || input == "" ) { //found deliniator or end
if (temp == "") temp = input[0]; //condition for reverse w/o deleniators
if (input != "") return convertOntology(input.substr(1), level+1, direction,
out+=vectorSearch(temp, direction, 0, direction, 1-direction, level));
else {
string empty = "";
return convertOntology(empty, level+1, direction, out+=vectorSearch(temp, direction, 0, direction, 1-direction, level));
}
} else
return convertOntology(input.substr(1), level, direction, out, temp+=input[0]); //increment and check
}
}
The call stack is a finite resource and can be exhausted like any other. The larger your function is (with respect to creation of local variables you create inside it) the larger the amount of space each call uses on the stack. It is something that is unavoidable with recursion unless you can restrict the number of recursive calls in some way.
You can only go so deep with recursion before running out of stack space. Luckily, any recursive function can be re-written to be iterative. I believe the below is a correct iterative implementation of your vectorSearch, I'll leave the latter one to you.
string vectorSearch(string &check, int &direction, int n, int c, int r, int level)
{
while(true)
{
if ((direction == 1 && check.length() <= 1) || n == list.size()-1 ||(direction == 0 && check.length() > 1)) { //if reading and string is 1 char then pass over
if (direction == 1){ //convert '???' into '?'
string temp = "";
bool wildToken = false;
for (unsigned int i = 0; i < check.length(); i++) {
temp+='?';
if (check.compare(temp) == 0) { check = '?'; wildToken = false; } //done,'???" case, return '?' token
else if (check[i] == '?') wildToken = true; //not done searching
}
}
return check;
} else if (list[n][c] == check || list[n][c] == ('0'+check)) {//add dummy '0'
return list[n][r];
}
n++;
}
}
thank you to the reviews and comments.
The functions are fine - this recursive function bundle requires that the string exists in the database it acts an, and the string checks prior to these incorrectly recognized a special condition and inserted a dummy char. There is the recursive function that precedes these two - I did not correctly see that I had written a bundle of three recursive functions - and that one was searching within parameters for a string longer than what exists in the database; apparently the parameters were wider than the stack. Checked into the parameters and one was not updated and was not controlling.
I fixed the special condition, the strings are now the same length and the search parameters are fixed.
the functions posted are not too complex.
I made this program just out of interest and wanted to make it better. My problem is that I want to make a nested for-loop to carry out the iterations but I can't get my head around it, I have tried many times but my head is melting. Any help would be greatly appreciated. Also for some reason on windows and openSuse (from what I have seen) the program prints out some random characters after the expected output, a solution to this would be a great bonus. Thanks !
Sorry I didn't make it clearer, the point of the code is to be able to theoretically generate every combination of letters from AAAAAAAA to ZZZZZZZZ.
1) No it's not homework
#include <iostream>
using namespace std;
int main()
{
char pass [] = {'A','A','A','A','A','A','A','A'};
while(pass[0] != '[')
{
pass[7]++;
if(pass[7]=='[')
{
pass[6]++;
pass[7] = 'A';
}
if(pass[6] == '[')
{
pass[6] = 'A';
pass[5]++;
}
if(pass[5] == '[')
{
pass[5] = 'A';
pass[4]++;
}
if(pass[4] == '[')
{
pass[4] = 'A';
pass[3]++;
}
if(pass[3] == '[')
{
pass[3] = 'A';
pass[2]++;
}
if(pass[2] == '[')
{
pass[2] = 'A';
pass[1]++;
}
if(pass[1] == '[')
{
pass[1] = 'A';
pass[0]++;
}
cout << pass << endl;
}
return 0;
}
Maybe like this:
const char char_first = 'A';
const char char_last = '[';
const unsigned int passlen = 8;
while (pass[0] != char_last)
{
++pass[passlen - 1];
for (unsigned int i = passlen - 1; i != 0; --i)
{
if (pass[i] == char_last)
{
++pass[i - 1]; // OK, i is always > 0
pass[i] = char_first;
}
}
}
For printing, include <string> and say:
std::cout << std::string(pass, passlen) << std::endl;
I took the liberty of making a few of the magic numbers into constants. If you're ever going to refactor this into a separate function, you'll see the merit of this.
Since (to output it) you use pass as a C string, it should be null terminated. Since it is not, garbage is printed. So you could define it as:
char pass [] = {'A','A','A','A','A','A','A','A','\0'};
or simpler
char pass[] = "AAAAAAAAA";
I'd forget about carrying on my own and just convert to/from numbers. What you're doing here is basically printing a numbers whose digits range from 'A' to ']', mappable to 0-28 via the magic of ASCII (why no ^ in passwords?)
Printing the number of anything then really boils down to
#include <iostream>
#include <cmath>
using namespace std;
std::string format(long num, int ndigits) {
if(ndigits == 0) {
return "";
} else {
char digit = 'A' + num % 28;
return format(num / 28, ndigits - 1) + digit;
}
}
int main()
{
for(int i = 0 ; i < powl(28,8) ; ++i) {
cout << format(i, 8) << endl;
}
}
You may still want to work in a char array instead of producing a billion temporary strings if you're serious about the loop, but the principle stays the same.
First try to find the common parts in the expressions looking like
if(pass[7]=='[')
{
pass[6]++;
pass[7] = 'A';
}
You should think along a line like "There's always the same number here, and a one-lower number there". Then, you replace that notion of a number with a variable and find out which range the variable has. KerrekSB gave you a solution, try to arrive at similar code from your own reasoning.
You just have to play a bit with your while and make it fit a for-loop.
while(pass[0] != '[') becomes for (i=0; pass[0] != '['; i++)
then you can replace all ifs with only one:
if(pass[i+1] == '[')
{
pass[i+1] = 'A';
pass[i]++;
}
How did we come to that conclusion? Well if you check all your if-statements all that changes between them is the indices. You can see clearly that pattern so you just replace the indices with a variable.
For starters, this is definitely not a case for a nested loop. In fact,
your entire code boils down to:
pass = initialPattern();
while ( isValidPattern( pass ) ) {
nextPattern( pass );
std::cout << pass << std::endl;
}
(But I wonder if you don't really mean to do the output before the
increment.)
Now all you have to do is define the type of pass and relevant
functions; you might even consider
putting everything in a class, since all of the functions operate on the
same data instance.
Judging from your code, pass should be an std::string with 8
characters; the initialization could be written:
std::string pass( 8, 'A' );
isValidPattern apparently only looks at the first character. (I'm not
sure that's correct, but that's what your code does.) Something like:
bool
isValidPattern( std::string const& pattern )
{
return pattern[0] != '[';
}
according to your code, but something like:
struct NotIsUpper
{
bool operator()( char ch ) const
{
return ! ::isupper( static_cast<unsigned char>( ch ) );
}
};
bool
isValidPattern( std::string const& pattern )
{
return pattern.size() == 8
&& std::find_if( pattern.begin(), pattern.end(), NotIsUpper() )
== pattern.end();
}
would seem more appropriate. (Of course, if you're doing any sort of
coding with text, you'd already have NotIsUpper and its siblings in
your tool kit.)
Finally, nextPattern seems to be nothing more than a multi-digit
increment, where the data is stored in big-endian order. So the
following (classical) algorithm would seem appropriate:
void
nextPattern( std::string& pattern )
{
static char const firstDigit = 'A';
static char const lastDigit = 'Z';
static std::string const invalidPattern( 1, '[' );
std::string::reverse_iterator current = pattern.rbegin();
std::string::reverse_iterator end = pattern.rend();
while ( current != end && *current == lastDigit ) {
*current = firstDigit;
++ current;
}
if ( current != end ) {
++ *current;
} else {
pattern = invalidPattern;
}
}
Formally, there is no guarantee in the standard that the letters will
be encoded in sequential ascending order, so for maximum portability,
you probably should in fact use an std::vector<int> with values in the
range [0, 26), and map those to letters just befor output. This
would be trivial if you put all of these operations in a class, since
the internal representation wouldn't be visible to the client code.
Something like:
class PatternGenerator
{
std::vector<int> myData;
public:
explicit PatternGenerator()
: myData( 8, 0 )
{
}
void next()
{
static int const lastDigit = 26;
std::vector<int>::reverse_iterator current = pattern.rbegin();
std::vector<int>::reverse_iterator end = pattern.rend();
while ( current != end && *current == lastDigit - 1 ) {
*current = 0;
++ current;
}
if ( current != end ) {
++ *current;
} else {
myData.front() = lastDigit;
}
}
bool isValid() const
{
return myData.front() < lastDigit;
}
friend std::ostream& operator<<(
std::ostream& dest, PatternGenerator const& obj )
{
static char const characterMap[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for ( std::vector<int>::iterator current = obj.myData.current();
current != obj.myData.end():
++ current ) {
dest << characterMap[*current];
}
return dest;
}
};
(Note that things like isValid become simpler, because they can depend on the class invariants.)
Given this, all you have to write is:
int
main()
{
PatternGenerator pass;
while ( pass.isValid() ) {
std::cout << pass << std::endl;
pass.next();
}
return 0;
}
To do nested loops, you need to turn it inside-out.
You've written the code thinking as follows: go through all the possibilities for the last symbol, then change the second-last once and go back, etc. That's like counting up from 1, getting to 10 and putting a 1 in the tens column, etc.
Nested loops work the other way: go through the possibilities for the first symbol, allowing the inner loops to take care of possibilities for the other symbols each time. i.e., "list all those numbers, in order, that start with 0 in the millions place, then the ones that start with 1 etc.". In the outermost loop, you just set that value for the first digit, and the nested loops take care of the rest.