Right now, I'm working on a project which requires sequential text key generation. I need to seed the key generator with an integer corresponding to a certain key, which the constructor converts to a key.
My key generator overloads the increment operators so that the string is incremented directly, rather than what I had previously been doing, which is incrementing an index value, then converting the index to a key for every key that I wanted to generate.
My problem is that I have a limited character set I want to use when generating keys. I have to find the character in the key that I want to increment, find out where it is in my character set, find the next character in the set, then replace the character in the key with the next character in the set.
Here is my code:
// Not the full charset
std::string charset = "abcdefghijklmnopqrstuvwxyz0123456789";
std::string key;
key.push_back(charset[0]);
for(unsigned int place = 0; place < key.length(); place++)
{
if(key[place] == charset[charset.length() - 1])
{
// Overflow, reset char at place
key[place] = charset[0];
if((key.length() - 1) < (place + 1))
{
// Carry, no space, insert char
key.insert(key.begin(), charset[0]);
break;
}
else
{
// Space available, increment next char
continue;
}
}
else
{
// Increment char at place
key[place] = charset[charset.find(key[place]) + 1];
break;
}
}
In profiling, I found that the search operation is really slowing things down. Is there any faster way of doing this? I thought of creating a linked list out of the character set, but before I do that, I'd like some input on this.
Rather than doing a find, why don't you have a reverse translation array? The array index would be the character, and the value in the array would be its numeric value (or index into the other array).
key[place] = charset[reverse_charset[key[place]] + 1];
This is another version of the generalized base conversion problem, with n=36.
What you want to do is view your key as an unsigned integer, and view the "string" that you're handing out as a base 36 (a-z + 0-9) representation of that key.
Handing out a key then becomes converting the "next key" value to the base36 string, then increment the next key value.
To convert, do the same thing you'd do to convert any integer to a hex representation, but swap in 36 instead of 16 on the modulo math. I'll leave this as an exercise for the reader. :)
You could store a vector of the same length as your key, where each element in the vector was the index in the charset of the corresponding character in the key.
For example, if key[0] was 'c', then thisVector[0] would be 2, since 'c' is the 3rd character in the character set.
Then all operations would be performed on that integer vector, removing the necessity for a find operation on the string.
I am not sure I understood what you wanted to do exactly but here is a little console program that prints out a sequence of 36*36*36 3-digit keys in base 36 using your charset as the digits. So it starts at aaa and ends at 999.
#include <stdio.h>
typedef int Number;
const size_t N = 3;
size_t B = 36;
Number key[N] = {0};
bool carry = false;
char A[] = "abcdefghifjlmnopqrstuvwxyz0123456789";
void incr(size_t i)
{
if(!carry)
{
return;
}
++key[i];
if(key[i] == B)
{
key[i] = 0;
}
else
{
carry = false;
}
}
void Incr()
{
carry = true;
size_t i = 0;
while(carry)
{
incr(i++);
}
}
void Print()
{
for(int i = N - 1; i >= 0; --i)
{
printf("%c", A[key[i]]);
}
printf("\n");
}
int _tmain(int argc, _TCHAR* argv[])
{
for(int i = 0; i < B * B * B; ++i)
{
Print();
Incr();
}
return 0;
}
Perhaps you would be better off working with indexes into the charset, and then converting them to actual characters when needed?
That would save you the overhead of searching for characters in the charset. And converting a charset index into a character would be a constant-time operation, unlike the inverse.
Store your key as a vector of integers 0 ~ N-1 where N is the length of your charset. Convert those integers to actual characters only when needed, i.e. after the increment.
Related
I have an array of strings, I want to check whether the first characters of all the strings are the same or not.
I know how to retrieve the first character of a string, by this method
char first_letter;
first_letter = (*str)[0];
Initially, I thought to go the brute force way, by checking for the first letter for every strings, using a nested for loop.
int flag = 0
char f1,f2;
for(int i = 0;i < size_arr - 1;i++){
f1 = (*str[i])[0];
for(int j = i + 1;j < size_arr;j++){
f2 = (*str[j])[0];
if(f1 != f2)
flag += 1;
}
}
if(!(flag))
cout<<"All first characters same";
else
cout<<"Different";
But I need an approach to find whether the first letters of all the strings present in an array are the same or not. Is there any efficient way?
You needn't use a nested for loop.Rather modify your code this way
for(int i = 0;i < size_arr - 2;i++){
f1 = (*str[i])[0];
f2 = (*str[i+1])[0];
if( f1!=f2 ){
printf("not same characters at first position");
break;
flag=1;
}
}
if(flag==0)printf("same characters at first position");
I made this C approach for you (it's because you have used character arrays here, not std::string of C++ – so it's convenient to describe using C code):
#include <stdio.h>
#define MAX_LENGTH 128
int main(void) {
char string[][MAX_LENGTH] = {"This is string ONE.", "This one is TWO.",
"This is the third one."};
char first_letter = string[0][0];
int total_strs = sizeof(string) / sizeof(string[0]);
int FLAG = 1;
// Iterate through each letter of each string
for (int i = 0; i < total_strs; i++)
// First letter of the string is equal to first_letter?
if (string[i][0] != first_letter) {
FLAG = 0; // set to 0 as soon as it finds
break; // the initial_letter is NOT equal to the first
} // letter
if (FLAG)
fprintf(stdout, "The strings have the same initial letters.\n");
else
fprintf(stdout, "Not all strings have the same initial letters.\n");
return 0;
}
If you want to convert it to a C++ code, no big issue – just replace stdio.h with iostream, int FLAG = 1 with bool FLAG = true, fprintf() to std::cout statements, that's it.
In case you need to work with std::string for the same job, just simply get the array of those strings, set the flag as true by default, iterate through each string, and match in case the first string's initial letter is equivalent to others, eventually, mark the flag as false in as soon as a defected string is found.
The program will display (if same initial vs. if not):
The strings have the same initial letters.
Not all strings have the same initial letters.
So, I am trying to figure out the best/simplest way to do this. For my algorithms class we are supposed read in a string (containing up to 40 characters) from a file and use the first character of the string (data[1]...we are starting the array at 1 and wanting to use data[0] as something else later) as the number of rotations(up to 26) to rotate letters that follow (it's a Caesar cipher, basically).
An example of what we are trying to do is read in from a file something like : 2ABCD and output CDEF.
I've definitely made attempts, but I am just not sure how to compare the first letter in the array char[] to see which number, up to 26, it is. This is how I had it implemented (not the entire code, just the part that I'm having issues with):
int rotation = 0;
char data[41];
for(int i = 0; i < 41; i++)
{
data[i] = 0;
}
int j = 0;
while(!infile.eof())
{
infile >> data[j+1];
j++;
}
for(int i = 1; i < 27; i++)
{
if( i == data[1])
{
rotation = i;
cout << rotation;
}
}
My output is always 0 for rotation.
I'm sure the problem lies in the fact that I am trying to compare a char to a number and will probably have to convert to ascii? But I just wanted to ask and see if there was a better approach and get some pointers in the right direction, as I am pretty new to C++ syntax.
Thanks, as always.
Instead of formatted input, use unformatted input. Use
data[j+1] = infile.get();
instead of
infile >> data[j+1];
Also, the comparison of i to data[1] needs to be different.
for(int i = 1; i < 27; i++)
{
if( i == data[1]-'0')
// ^^^ need this to get the number 2 from the character '2'.
{
rotation = i;
std::cout << "Rotation: " << rotation << std::endl;
}
}
You can do this using modulo math, since characters can be treated as numbers.
Let's assume only uppercase letters (which makes the concept easier to understand).
Given:
static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const std::string original_text = "MY DOG EATS HOMEWORK";
std::string encrypted_text;
The loop:
for (unsigned int i = 0; i < original_text.size(); ++i)
{
Let's convert the character in the string to a number:
char c = original_text[i];
unsigned int cypher_index = c - 'A';
The cypher_index now contains the alphabetic offset of the letter, e.g. 'A' has index of 0.
Next, we rotate the cypher_index by adding an offset and using modulo arithmetic to "circle around":
cypher_index += (rotation_character - 'A'); // Add in the offset.
cypher_index = cypher_index % sizeof(letters); // Wrap around.
Finally, the new, shifted, letter is created by looking up in the letters array and append to the encrypted string:
encrypted_text += letters[cypher_index];
} // End of for loop.
The modulo operation, using the % operator, is great for when a "wrap around" of indices is needed.
With some more arithmetic and arrays, the process can be expanded to handle all letters and also some symbols.
First of all you have to cast the data chars to int before comparing them, just put (int) before the element of the char array and you will be okay.
Second, keep in mind that the ASCII table doesn't start with letters. There are some funny symbols up until 60-so element. So when you make i to be equal to data[1] you are practically giving it a number way higher than 27 so the loop stops.
The ASCII integer value of uppercase letters ranges from 65 to 90. In C and its descendents, you can just use 'A' through 'Z' in your for loop:
change
for(int i = 1; i < 27; i++)
to
for(int i = 'A'; i <= 'Z'; i++)
and you'll be comparing uppercase values. The statement
cout << rotation;
will print the ASCII values read from infile.
How much of the standard library are you permitted to use? Something like this would likely work better:
#include <iostream>
#include <string>
#include <sstream>
int main()
{
int rotation = 0;
std::string data;
std::stringstream ss( "2ABCD" );
ss >> rotation;
ss >> data;
for ( int i = 0; i < data.length(); i++ ) {
data[i] += rotation;
}
// C++11
// for ( auto& c : data ) {
// c += rotation;
// }
std::cout << data;
}
Live demo
I used a stringstream instead of a file stream for this example, so just replace ss with your infile. Also note that I didn't handle the wrap-around case (i.e., Z += 1 isn't going to give you A; you'll need to do some extra handling here), because I wanted to leave that to you :)
The reason your rotation is always 0 is because i is never == data[1]. ASCII character digits do not have the same underlying numeric value as their integer representations. For example, if data[1] is '5', it's integer value is actually 49. Hint: you'll need to know these values when handle the wrap-around case. Do a quick google for "ANSI character set" and you'll see all the different values.
Your determination of the rotation is also flawed in that you're only checking data[1]. What happens if you have a two-digit number, like 10?
This is a task from school, I am supposed to write a recursive function that will convert a given int to a string, I know I'm close but I can't point the missing thing in my code, hints are welcome.
void intToStr(unsigned int num, char s[])
{
if (num < 10)
{
s[0] = '0' + num;
}
else
{
intToStr(num/10, s);
s[strlen(s)] = '0' + num%10;
}
}
Edit: my problem is that the function only works for pre initialized arrays, but if I let the function work on an uninitialized function it will not work.
Unless your array is zero-initialized, you are forgetting to append a null terminator when you modify it.
Just add it right after the last character:
void intToStr(unsigned int num, char s[])
{
if (num < 10)
{
s[0] = '0' + num;
s[1] = 0;
}
else
{
intToStr(num/10, s);
s[strlen(s)+1] = 0; //you have to do this operation here, before you overwrite the null terminator
s[strlen(s)] = '0' + num%10;
}
}
Also, your function is assuming that s has enough space to hold all the digits, so you better make sure it does (INT_MAX is 10 digits long I think, so you need at least 11 characters).
Andrei Tita already showed you the problem you had with the NULL terminators. I will show you an alternative, so you can compare and contrast different approaches:
int intToStr(unsigned int num, char *s)
{
// We use this index to keep track of where, in the buffer, we
// need to output the current character. By default, we write
// at the first character.
int idx = 0;
// If the number we're printing is larger than 10 we recurse
// and use the returned index when we continue.
if(num > 9)
idx = intToStr(num / 10, s);
// Write our digit at the right position, and increment the
// position by one.
s[idx++] = '0' + (num %10);
// Write a terminating NULL character at the current position
// to ensure the string is always NULL-terminated.
s[idx] = 0;
// And return the current position in the string to whomever
// called us.
return idx;
}
You will notice that my alternative also returns the final length of the string that it output into the buffer.
Good luck with your coursework going forward!
I am trying to perform some operations on an array which the final goal is to do a simple encryption. But anyways my array is 458 characters long which consists of mostly letters and some commas, periods, etc. I am trying to start from last character of array and go to the first character and uppercase all the letters in the array. It reads the last character "" correctly, but then the next step in the for loop is like 4 characters over and skipped a few letters. Is something wrong with my control logic?
void EncryptMessage (ofstream& outFile, char charArray[], int length)
{
int index;
char upperCased;
char current;
for (index = length-1; index <= length; --index)
{
if (charArray[index] >= 'A' && charArray[index] <= 'Z')
{
upperCased = static_cast<char>(charArray[index]);
current = upperCased;
outFile << current;
}
else
{
charArray[index]++;
current = charArray[index];
}
}
}
Change:
for (index = length-1; index <= length; --index)
to:
for (index = length-1; index >= 0; --index)
In the else leg of your if statement, you're setting the value of current, but never writing it out, so all that gets written out are what start as capital letters (and, as others have pointed out, your loop condition isn't correct).
If I were doing this, I'd structure it a bit differently. I'd write a small functor to encrypt a single letter:
struct encrypt {
char operator()(char input) {
if (isupper(input))
return input;
else
return input+1;
}
};
Then I'd put the input into an std::string, and operate on it using std::transform:
std::string msg("content of string goes here.");
std::transform(msg.rbegin(), msg.rend(),
std::ostream_iterator<char>(outFile, ""),
encrypt());
I need to compare string into following way. Can anyone provide me some insight or algorithm in c++.
For example:
"a5" < "a11" - because 5 is less than 11
"6xxx < 007asdf" - because 6 < 7
"00042Q < 42s" - because Q < s alphabetically
"6 8" < "006 9" - because 8 < 9
I suggest you look at the algorithm strverscmp uses - indeed it might be that this function will do the job for you.
What this function does is the following. If both strings are equal,
return 0. Otherwise find the position between two bytes with the
property that before it both strings are equal, while directly after
it there is a difference. Find the largest consecutive digit strings
containing (or starting at, or ending at) this position. If one or
both of these is empty, then return what strcmp(3) would have
returned (numerical ordering of byte values). Otherwise, compare both
digit strings numerically, where digit strings with one or more
leading zeros are interpreted as if they have a decimal point in front
(so that in particular digit strings with more leading zeros come
before digit strings with fewer leading zeros). Thus, the ordering is
000, 00, 01, 010, 09, 0, 1, 9, 10.
Your examples only show digits, letters, and spaces. So for the moment I'll assume you ignore every other symbol (effectively treat them as spaces). You also seem to want to treat uppercase and lowercase letters as equivalent.
It also appears that you interpret runs of digits as a "term" and runs of letters as a "term", with any transition between a letter and a digit being equivalent to a space. A single space is considered equivalent to any number of spaces.
(Note: You are conspicuously missing an example of what to do in cases like:
"5a" vs "a11"
"a5" vs "11a"
So you have to work out what to do when you face a comparison of a numeric term with a string term. You also don't mention intrinsic equalities...such as should "5 a" == "5a" just because "5 a" < "5b"?)
One clear way of doing this would be turn the strings into std::vector of "terms", and then compare these vectors (rather than trying to compare the strings directly). These terms would be either numeric or string. This might help get you started, especially the STL answer:
how to split a string value that contains characters and numbers
Trickier methods that worked on the strings themselves without making an intermediary will be faster in one-off comparisons. But they'll likely be harder to understand and modify, and perhaps slower if you are going to repeatedly compare the same structures.
A nice aspect of parsing into a structure is that you get an intrinsic "cleanup" of the data in the process. Getting the information into a canonical form is often a goal in programs that are tolerating such a variety of inputs.
I'm assuming that you want the compare to be done in this order: presence of digits in range 1-9; value of digits; number of digits; value of the string after the digits.
It's in C, but you can easily transform it into using the C++ std::string class.
int isdigit(int c)
{
return c >= '1' && c <= '9';
}
int ndigits(const char *s)
{
int i, nd = 0;
int n = strlen(s);
for (i = 0; i < n; i++) {
if (isdigit(s[i]))
nd++;
}
return nd;
}
int compare(const char *s, const char *t)
{
int sd, td;
int i, j;
sd = ndigits(s);
td = ndigits(t);
/* presence of digits */
if (!sd && !td)
return strcasecmp(s, t);
else if (!sd)
return 1;
else if (!td)
return -1;
/* value of digits */
for (i = 0, j = 0; i < sd && j < td; i++, j++) {
while (! isdigit(*s))
s++;
while (! isdigit(*t))
t++;
if (*s != *t)
return *s - *t;
s++;
t++;
}
/* number of digits */
if (i < sd)
return 1;
else if (j < td)
return -1;
/* value of string after last digit */
return strcasecmp(s, t);
}
Try this and read about std::string.compare:
#include <iostream>
using namespace std;
int main(){
std::string fred = "a5";
std::string joe = "a11";
char x;
if ( fred.compare( joe ) )
{
std::cout << "fred is less than joe" << std::endl;
}
else
{
std::cout << "joe is less than fred" << std::endl;
}
cin >> x;
}