Related
I need to read a string char by char in order to perform some controls on it. Is it possible to do that? Have I necessarily got to convert it to a char array?
I tried to point at single chars with string_to_control[i] and then increase i to move, but this doesn't seem to work.
As an example, I post a piece of the code for the control of parenthesis.
bool Class::func(const string& cont){
const string *p = &cont;
int k = 0;
//control for parenthesis
while (p[k].compare('\0') != 0) {
if (p[k].compare("(") == 0) { ap++; };
if (p[k].compare(")") == 0) { ch++; };
k++;
};
//...
};
The string is copied alright, but as soon as I try the first comparison an exception is thrown.
EDIT: I add that I would like to have different copies of the initial string cont (and move on them, rather than on cont directly) in order to manipulate them (later on in the code, I need to verify that certain words are in the right place).
The simplest way to iterate through a string character by character is a range-for:
bool Class::func(const string& cont){
for (char c : cont) {
if (c == '(') { ap++; }
if (c == ')') { ch++; }
}
//...
};
The range-for syntax was added in C++11. If, for some reason, you're using an old compiler that doesn't have C++11 support, you can iterate by index perfectly well without any casts or copies:
bool Class::func(const string& cont){
for (size_t i = 0; i < cont.size(); ++i) {
if (cont[i] == '(') { ap++; }
if (cont[i] == ')') { ch++; }
}
//...
};
If you just want to count the opening and closing parentheses take a look at this:
bool Class::func(const string& cont) {
for (const auto c : cont) {
switch (c) {
case '(': ++ap; break;
case ')': ++ch; break;
}
}
// ...
}
const string *p = &cont;
int k = 0;
while (p[k].compare('\0') != 0)
Treats p as if it were an array, as p only points to a single value your code has undefined behaviour when k is non-zero. I assume what you actually wanted to write was:
bool Class::func(const string& cont){
while (cont[k] != '\0') {
if (cont[k] == '(') { ap++; };
if (cont[k] == ') { ch++; };
k++;
};
};
A simpler way would be to iterate over std::string using begin() and end() or even more simply just use a range for loop:
bool Class::func(const string& cont){
for (char ch : cont) {
if (ch == '(') { ap++; };
if (ch == ')') { ch++; };
};
};
If you want to copy your string simply declare a new string:
std::string copy = cont;
The std::string::operator[] overload allows expressions such as cont[k]. Your code treats p as an array of std::string rather then an array of characters as you intended. That could be corrected by:
const string &p = cont;
but is unnecessary since you can already access cont directly.
cont[k] has type char so calling std::string::compare() is not valid. You can compare chars in the normal manner:
cont[k] == '('`
You should also be aware that before C++11 the end of a std::string is not delimited by a \0 like a C string (there may happen to be a NUL after the string data, but that is trusting to luck). C++11 does guarantee that, but probably only to "fix" older code that made the assumption that it was.
If you use std::string::at rather then std::string::operator[] an exception will be thrown if you exceed the bounds. But you should use either range-based for, a std::string::iterator or std::string::length() to iterate a string to the end.
If you don't want to use iterators std::string also overloads operator[], so you can access the chars like you would do with a char[].
cont[i] will return the character at index i for example, then you can use == to compare it to another char:
bool Class::func(const string& cont){
int k = 0;
while (k < cont.length()) {
if (cont[k] == '(') { ap++; };
if (cont[k] == ')') { ch++; };
k++;
};
};
To count parentheses, you can use std::count algorithm from the standard library:
/* const */ auto ap = std::count(cont.begin(), cont.end(), '(');
/* const */ auto ch = std::count(cont.begin(), cont.end(), ')');
The string will be traversed twice.
For single traversal you can implement a generic function (requires C++17):
template<class C, typename... Ts>
auto count(const C& c, const Ts&... values) {
std::array<typename C::difference_type, sizeof...(Ts)> counts{};
for (auto& value : c) {
auto it = counts.begin();
((*it++ += (value == values)), ...);
}
return counts;
}
and then write
/* const */ auto [ap, ch] = count(cont, '(', ')');
First convert the string to a char array like this:
bool Class::func(const string& cont){
char p[cont.size() + 1];
strcpy(p, cont.c_str());
int k = 0;
//control for parenthesis
while (p[k].compare('\0') != 0) {
if (p[k].compare("(") == 0) { ap++; };
if (p[k].compare(")") == 0) { ch++; };
k++;
};
//...
};
You could do what you want with an algorithm, which means you can avoid the array conversion:
#include <iostream>
#include <string>
#include <cstring>
#include <algorithm> // std::count
int main()
{
std::string s = "hi(there),(now))";
int ap = std::count (s.c_str(), s.c_str()+s.size(), '(');
int ch = std::count (s.c_str(), s.c_str()+s.size(), ')');
std::cout << ap << "," << ch << '\n'; // prints 2,3
return 0;
}
I am creating a c++ program to validate book ID using function in c++. The program must return 1 if the input is valid and 0 if the input is invalid. INPUT Pattern: "123-AB-12345" This will be considered as a valid input. The valid input is: (a) Total characters must be 12 (b) First three characters must be integers from 1 to 9 each. (c) 4th and 7th characters must be hiphen "-". (d) Last 5 characters must be integers from 1 to 9 each.
I tried the following way but I am not getting the desired answer. Need help plz
#include<iostream>
using namespace std;
bool isValidBookId(char bookId[13]);
int main()
{
char book[13];
cin.getline(book,12);
bool id = isValidBookId(book);
cout<<id;
}
bool isValidBookId(char bookId[13])
{
int i;
bool check1,check2,check3,check4,check5,check6;
check1=check2=check3=check4=check5=true;
if(bookId[12]=='\0'){
check1=true;
}
if(bookId[3]=='-')
{
check2=true;
}
if(bookId[6]=='-')
{
check3=true;
}
for(i=0; i<3;i++){
if(bookId[i]>=0 || bookId[i]<=9)
{
check4=true;
}
}
if(bookId[i]>= 'A' || bookId[i]<= 'Z')
{
check5=true;
}
for(i=7; i<12; i++)
{
if(bookId[i]>=0 || bookId[i]<=9)
{
check6=true;
}
}
if(check1==true && check2==true && check3==true && check4==true && check5==true && check6==true)
{
return true;
}
else
{
return false;
}
}
There's a few errors that you have in your code.
First, you initialize all your checks to true, and never set anything to false, so the answer will always be true. Realistically, you want to initialize them all to false, and change to true when all the conditions are met, or assume true, and set to false when the condition is not met.
Second, your check for the values 0-9 is incorrect. You cannot compare bookId[i] to 0, you want to compare it to the character '0'. Also note that the question you have also says 1-9 not 0-9
Third, your check for A-Z is incorrect (note, this issue also applies to 0-9). You're code basically says is bookId[i] greater than or equal to 'A' OR less than or equal to Z, which is always going to be true.
I've written your code below:
bool isValidBookId( char bookId[13] ) {
if ( bookId[12] != '\0' )
return false;
if ( bookId[3] != '-' )
return false;
if ( bookId[6] != '-' )
return false;
for ( int i = 0; i < 3; i++ ) {
if ( bookId[i] < '1' || bookId[i] > '9' ) {
return false;
}
}
for ( int i = 4; i < 6; i++ ) {
if ( bookId[i] < 'A' || bookId[i] > 'Z' ) {
return false;
}
}
for ( int i = 7; i < 12; i++ ) {
if ( bookId[i] < '1' || bookId[i] > '9' ) {
return false;
}
}
return true;
}
This method doesn't require any Boolean variables. Instead, I assume true (the last return statement) and instead try to prove false. As soon as something is false, you can return without doing any other checks.
Since the given code is in C-Style format, I would like to present 2 additional solutions in C++. I think the task is anyway to think about patterns and how these could be detected.
In my first solutions I just added more C++ elements. The 2nd solution should be the correct on.
Please see:
#include <iostream>
#include <regex>
#include <string>
#include <cctype>
#include <vector>
bool isValidBookId1(const std::string& bookId) {
// Lambda to detect hyphen
auto ishyphen = [](int i){ return static_cast<int>(i == '-');};
// First check size of given string
bool result{(bookId.size() == 12)};
// Define the position of the types
std::vector<size_t> digitIndex{0,1,2,7,8,9,10,11};
std::vector<size_t> letterIndex{4,5};
std::vector<size_t> hyphenIndex{3,6};
// Check types
if (result) for (size_t index : digitIndex) result = result && std::isdigit(bookId[index]);
if (result) for (size_t index : letterIndex) result = result && std::isupper(bookId[index]);
if (result) for (size_t index : hyphenIndex) result = result && ishyphen(bookId[index]);
// Return resulting value
return result;
}
bool isValidBookId2(const std::string& bookId) {
// Define pattern as a regex
std::regex re{R"(\d{3}-[A-Z]{2}-\d{5})"};
// Check, if the book id matches the pattern
return std::regex_match(bookId, re);
}
int main()
{
// Get input from user
if (std::string line{}; std::getline(std::cin, line)) {
std::cout << "Check for valid Book input 1: " << isValidBookId1(line) << "\n";
std::cout << "Check for valid Book input 2: " << isValidBookId2(line) << "\n";
}
return 0;
}
I have a directory containing files {"good_6", good_7", "good_8"...,"good_660"}, after reading it using readdir and storing in a vector I get {"good_10", "good_100", "good_101", "good_102"...}.
What I want to do is to keep the file names as {"good_6", good_7", "good_8"...,"good_660"} in the vector and then replacing first name with 1, second with 2 and so on... such that good_6 will be 1, good_7 will be 2 and so on. but now good_10 corresponds to 1 and good_100 to 2 and so on.
I tried std::sort on vector but the values are already sorted, just not in a way that I desire (based on integer after _). Even if I just get the last integer and sort on that, it will still be sorted as 1, 100, 101...
Any help would be appreciated. Thanks.
You can use a custom function that compares strings with a special case for digits:
#include <ctype.h>
int natural_string_cmp(const char *sa, const char *sb) {
for (;;) {
int a = (unsigned char)*sa++;
int b = (unsigned char)*sb++;
/* simplistic version with overflow issues */
if (isdigit(a) && isdigit(b)) {
const char *sa1 = sa - 1;
const char *sb1 = sb - 1;
unsigned long na = strtoul(sa1, (char **)&sa, 10);
unsigned long nb = strtoul(sb1, (char **)&sb, 10);
if (na == nb) {
if ((sa - sa1) == (sb - sb1)) {
/* XXX should check for '.' */
continue;
} else {
/* Perform regular strcmp to handle 0 :: 00 */
return strcmp(sa1, sb1);
}
} else {
return (na < nb) ? -1 : +1;
}
} else {
if (a == b) {
if (a != '\0')
continue;
else
return 0;
} else {
return (a < b) ? -1 : 1;
}
}
}
}
Depending on your sorting algorithm, you may need to wrap it with an extra level of indirection:
int natural_string_cmp_ind(const void *p1, const void *p2) {
return natural_string_cmp(*(const char * const *)p1, *(const char * const *)p2);
}
char *array[size];
... // array is initialized with filenames
qsort(array, size, sizeof(*array), natural_string_cmp_ind);
I think you can play around with your data structure. For example instead of vector<string>, you can convert your data to vector< pair<int, string> >. Then {"good_6", "good_7", "good_8"...,"good_660"} should be {(6, "good"), (7, "good"), (7, "good")..., (660, "good")}. In the end, you convert it back and do whatever you want.
Another way is just to define your own comparator to do the exact comparison as what you want.
You can use string::replace to replace string "good_" with empty string, and use stoi to convert the rest of the integral part of the string. Lets say the value obtained is x.
Create std::map and populate it in this way myMap[x] = vec_element.
Then you can traverse from m.begin() till m.end() to find sorted order.
Code:
myMap[ stoi( vec[i].replace(0,5,"") )] = vec[i];
for( MapType::iterator it = myMap.begin(); it != myMap.end(); ++it ) {
sortedVec.push_back( it->second );
If I understand your question, you're just having trouble with the sorting and not how you plan to change the names after you sort.
Something like this might work for you:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <tuple>
#include <string.h>
int main()
{
std::vector<std::string> v;
char buffer[64] = {};
for (size_t i = 1; i < 10; ++i)
{
sprintf(buffer, "good_%d", i * 3);
v.push_back(buffer);
sprintf(buffer, "bad_%d", i * 2);
v.push_back(buffer);
}
std::random_shuffle(v.begin(), v.end());
for (const auto& s : v)
{
std::cout << s << "\n";
}
std::sort(v.begin(), v.end(),
[](const std::string& lhs, const std::string& rhs)
{
//This assumes a lot about the contents of the strings
//and has no error checking just to keep things short.
size_t l_pos = lhs.find('_');
size_t r_pos = rhs.find('_');
std::string l_str = lhs.substr(0, l_pos);
std::string r_str = rhs.substr(0, r_pos);
int l_num = std::stoi(lhs.substr(l_pos + 1));
int r_num = std::stoi(rhs.substr(r_pos + 1));
return std::tie(l_str, l_num) < std::tie(r_str, r_num);
});
std::cout << "-----\n";
for (const auto& s : v)
{
std::cout << s << "\n";
}
return 0;
}
Managed to do it with the following compare function:
bool numericStringComapre(const std::string& s1, const std::string& s2)
{
size_t foundUnderScore = s1.find_last_of("_");
size_t foundDot = s1.find_last_of(".");
string s11 = s1.substr(foundUnderScore+1, foundDot - foundUnderScore - 1);
foundUnderScore = s2.find_last_of("_");
foundDot = s2.find_last_of(".");
string s22 = s2.substr(foundUnderScore+1, foundDot-foundUnderScore - 1);
int i1 = stoi(s11);
int i2 = stoi(s22);
if (i1 < i2) return true;
return false;
}
full file name was good_0.png, hence that find_last_of(".").
I have used the following code to sort files in alphabetical order and it sorts the files as shown in the figure:
for(int i = 0;i < maxcnt;i++)
{
for(int j = i+1;j < maxcnt;j++)
{
if(strcmp(Array[i],Array[j]) > 0)
{
strcpy(temp,Array[i]);
strcpy(Array[i],Array[j]);
strcpy(Array[j],temp);
}
}
}
But I need to sort it as order seen in Windows explorer
How to sort like this way? Please help
For a C answer, the following is a replacement for strcasecmp(). This function recurses to handle strings that contain alternating numeric and non-numeric substrings. You can use it with qsort():
int strcasecmp_withNumbers(const void *void_a, const void *void_b) {
const char *a = void_a;
const char *b = void_b;
if (!a || !b) { // if one doesn't exist, other wins by default
return a ? 1 : b ? -1 : 0;
}
if (isdigit(*a) && isdigit(*b)) { // if both start with numbers
char *remainderA;
char *remainderB;
long valA = strtol(a, &remainderA, 10);
long valB = strtol(b, &remainderB, 10);
if (valA != valB)
return valA - valB;
// if you wish 7 == 007, comment out the next two lines
else if (remainderB - b != remainderA - a) // equal with diff lengths
return (remainderB - b) - (remainderA - a); // set 007 before 7
else // if numerical parts equal, recurse
return strcasecmp_withNumbers(remainderA, remainderB);
}
if (isdigit(*a) || isdigit(*b)) { // if just one is a number
return isdigit(*a) ? -1 : 1; // numbers always come first
}
while (*a && *b) { // non-numeric characters
if (isdigit(*a) || isdigit(*b))
return strcasecmp_withNumbers(a, b); // recurse
if (tolower(*a) != tolower(*b))
return tolower(*a) - tolower(*b);
a++;
b++;
}
return *a ? 1 : *b ? -1 : 0;
}
Notes:
Windows needs stricmp() rather than the Unix equivalent strcasecmp().
The above code will (obviously) give incorrect results if the numbers are really big.
Leading zeros are ignored here. In my area, this is a feature, not a bug: we usually want UAL0123 to match UAL123. But this may or may not be what you require.
See also Sort on a string that may contain a number and How to implement a natural sort algorithm in c++?, although the answers there, or in their links, are certainly long and rambling compared with the above code, by about a factor of at least four.
Natural sorting is the way that you must take here . I have a working code for my scenario. You probably can make use of it by altering it according to your needs :
#ifndef JSW_NATURAL_COMPARE
#define JSW_NATURAL_COMPARE
#include <string>
int natural_compare(const char *a, const char *b);
int natural_compare(const std::string& a, const std::string& b);
#endif
#include <cctype>
namespace {
// Note: This is a convenience for the natural_compare
// function, it is *not* designed for general use
class int_span {
int _ws;
int _zeros;
const char *_value;
const char *_end;
public:
int_span(const char *src)
{
const char *start = src;
// Save and skip leading whitespace
while (std::isspace(*(unsigned char*)src)) ++src;
_ws = src - start;
// Save and skip leading zeros
start = src;
while (*src == '0') ++src;
_zeros = src - start;
// Save the edges of the value
_value = src;
while (std::isdigit(*(unsigned char*)src)) ++src;
_end = src;
}
bool is_int() const { return _value != _end; }
const char *value() const { return _value; }
int whitespace() const { return _ws; }
int zeros() const { return _zeros; }
int digits() const { return _end - _value; }
int non_value() const { return whitespace() + zeros(); }
};
inline int safe_compare(int a, int b)
{
return a < b ? -1 : a > b;
}
}
int natural_compare(const char *a, const char *b)
{
int cmp = 0;
while (cmp == 0 && *a != '\0' && *b != '\0') {
int_span lhs(a), rhs(b);
if (lhs.is_int() && rhs.is_int()) {
if (lhs.digits() != rhs.digits()) {
// For differing widths (excluding leading characters),
// the value with fewer digits takes priority
cmp = safe_compare(lhs.digits(), rhs.digits());
}
else {
int digits = lhs.digits();
a = lhs.value();
b = rhs.value();
// For matching widths (excluding leading characters),
// search from MSD to LSD for the larger value
while (--digits >= 0 && cmp == 0)
cmp = safe_compare(*a++, *b++);
}
if (cmp == 0) {
// If the values are equal, we need a tie
// breaker using leading whitespace and zeros
if (lhs.non_value() != rhs.non_value()) {
// For differing widths of combined whitespace and
// leading zeros, the smaller width takes priority
cmp = safe_compare(lhs.non_value(), rhs.non_value());
}
else {
// For matching widths of combined whitespace
// and leading zeros, more whitespace takes priority
cmp = safe_compare(rhs.whitespace(), lhs.whitespace());
}
}
}
else {
// No special logic unless both spans are integers
cmp = safe_compare(*a++, *b++);
}
}
// All else being equal so far, the shorter string takes priority
return cmp == 0 ? safe_compare(*a, *b) : cmp;
}
#include <string>
int natural_compare(const std::string& a, const std::string& b)
{
return natural_compare(a.c_str(), b.c_str());
}
What you want to do is perform "Natural Sort". Here is a blog post about it, explaining implementation in python I believe. Here is a perl module that accomplishes it. There also seems to be a similar question at How to implement a natural sort algorithm in c++?
Taking into account that this has a c++ tag, you could elaborate on #Joseph Quinsey's answer and create a natural_less function to be passed to the standard library.
using namespace std;
bool natural_less(const string& lhs, const string& rhs)
{
return strcasecmp_withNumbers(lhs.c_str(), rhs.c_str()) < 0;
}
void example(vector<string>& data)
{
std::sort(data.begin(), data.end(), natural_less);
}
I took the time to write some working code as an exercise
https://github.com/kennethlaskoski/natural_less
Modifying this answer:
bool compareNat(const std::string& a, const std::string& b){
if (a.empty())
return true;
if (b.empty())
return false;
if (std::isdigit(a[0]) && !std::isdigit(b[0]))
return true;
if (!std::isdigit(a[0]) && std::isdigit(b[0]))
return false;
if (!std::isdigit(a[0]) && !std::isdigit(b[0]))
{
if (a[0] == b[0])
return compareNat(a.substr(1), b.substr(1));
return (toUpper(a) < toUpper(b));
//toUpper() is a function to convert a std::string to uppercase.
}
// Both strings begin with digit --> parse both numbers
std::istringstream issa(a);
std::istringstream issb(b);
int ia, ib;
issa >> ia;
issb >> ib;
if (ia != ib)
return ia < ib;
// Numbers are the same --> remove numbers and recurse
std::string anew, bnew;
std::getline(issa, anew);
std::getline(issb, bnew);
return (compareNat(anew, bnew));
}
toUpper() function:
std::string toUpper(std::string s){
for(int i=0;i<(int)s.length();i++){s[i]=toupper(s[i]);}
return s;
}
Usage:
#include <iostream> // std::cout
#include <string>
#include <algorithm> // std::sort, std::copy
#include <iterator> // std::ostream_iterator
#include <sstream> // std::istringstream
#include <vector>
#include <cctype> // std::isdigit
int main()
{
std::vector<std::string> str;
str.push_back("20.txt");
str.push_back("10.txt");
str.push_back("1.txt");
str.push_back("z2.txt");
str.push_back("z10.txt");
str.push_back("z100.txt");
str.push_back("1_t.txt");
str.push_back("abc.txt");
str.push_back("Abc.txt");
str.push_back("bcd.txt");
std::sort(str.begin(), str.end(), compareNat);
std::copy(str.begin(), str.end(),
std::ostream_iterator<std::string>(std::cout, "\n"));
}
Your problem is that you have an interpretation behind parts of the file name.
In lexicographical order, Slide1 is before Slide10 which is before Slide5.
You expect Slide5 before Slide10 as you have an interpretation of the substrings 5 and 10 (as integers).
You will run into more problems, if you had the
name of the month in the filename, and would expect them to be ordered by date (i.e. January comes before August). You will need to adjust your sorting to this interpretation (and the "natural" order will depend on your interpretation, there is no generic solution).
Another approach is to format the filenames in a way that your sorting and the lexicographical order agree. In your case, you would use leading zeroes and a fixed length for the number. So Slide1 becomes Slide01, and then you will see that sorting them lexicographically will yield the result you would like to have.
However, often you cannot influence the output of an application, and thus cannot enforce your format directly.
What I do in those cases: write a little script/function that renames the file to a proper format, and then use standard sorting algorithms to sort them. The advantage of this is that you do not need to adapt your sorting, and can use existing software for the sorting.
On the downside, there are situations where this is not feasible (as filenames need to be fixed).
I'm reading a string from a file so it's in the form of a char array. I need to tokenize the string and save each char array token as a uint8_t hex value in an array.
char* starting = "001122AABBCC";
// ...
uint8_t[] ending = {0x00,0x11,0x22,0xAA,0xBB,0xCC}
How can I convert from starting to ending? Thanks.
Here is a complete working program. It is based on Rob I's solution, but fixes several problems has been tested to work.
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>
const char* starting = "001122AABBCC";
int main()
{
std::string starting_str = starting;
std::vector<unsigned char> ending;
ending.reserve( starting_str.size());
for (int i = 0 ; i < starting_str.length() ; i+=2) {
std::string pair = starting_str.substr( i, 2 );
ending.push_back(::strtol( pair.c_str(), 0, 16 ));
}
for(int i=0; i<ending.size(); ++i) {
printf("0x%X\n", ending[i]);
}
}
strtoul will convert text in any base you choose into bytes. You have to do a little work to chop the input string into individual digits, or you can convert 32 or 64bits at a time.
ps uint8_t[] ending = {0x00,0x11,0x22,0xAA,0xBB,0xCC}
Doesn't mean anything, you aren't storing the data in a uint8 as 'hex', you are storing bytes, it's upto how you (or your debugger) interpretes the binary data
With C++11, you may use std::stoi for that :
std::vector<uint8_t> convert(const std::string& s)
{
if (s.size() % 2 != 0) {
throw std::runtime_error("Bad size argument");
}
std::vector<uint8_t> res;
res.reserve(s.size() / 2);
for (std::size_t i = 0, size = s.size(); i != size; i += 2) {
std::size_t pos = 0;
res.push_back(std::stoi(s.substr(i, 2), &pos, 16));
if (pos != 2) {
throw std::runtime_error("bad character in argument");
}
}
return res;
}
Live example.
I think any canonical answer (w.r.t. the bounty notes) would involve some distinct phases in the solution:
Error checking for valid input
Length check and
Data content check
Element conversion
Output creation
Given the usefulness of such conversions, the solution should probably include some flexibility w.r.t. the types being used and the locale required.
From the outset, given the date of the request for a "more canonical answer" (circa August 2014) liberal use of C++11 will be applied.
An annotated version of the code, with types corresponding to the OP:
std::vector<std::uint8_t> convert(std::string const& src)
{
// error check on the length
if ((src.length() % 2) != 0) {
throw std::invalid_argument("conversion error: input is not even length");
}
auto ishex = [] (decltype(*src.begin()) c) {
return std::isxdigit(c, std::locale()); };
// error check on the data contents
if (!std::all_of(std::begin(src), std::end(src), ishex)) {
throw std::invalid_argument("conversion error: input values are not not all xdigits");
}
// allocate the result, initialised to 0 and size it to the correct length
std::vector<std::uint8_t> result(src.length() / 2, 0);
// run the actual conversion
auto str = src.begin(); // track the location in the string
std::for_each(result.begin(), result.end(), [&str](decltype(*result.begin())& element) {
element = static_cast<std::uint8_t>(std::stoul(std::string(str, str + 2), nullptr, 16));
std::advance(str, 2); // next two elements
});
return result;
}
The template version of the code adds flexibility;
template <typename Int /*= std::uint8_t*/,
typename Char = char,
typename Traits = std::char_traits<Char>,
typename Allocate = std::allocator<Char>,
typename Locale = std::locale>
std::vector<Int> basic_convert(std::basic_string<Char, Traits, Allocate> const& src, Locale locale = Locale())
{
using string_type = std::basic_string<Char, Traits, Allocate>;
auto ishex = [&locale] (decltype(*src.begin()) c) {
return std::isxdigit(c, locale); };
if ((src.length() % 2) != 0) {
throw std::invalid_argument("conversion error: input is not even length");
}
if (!std::all_of(std::begin(src), std::end(src), ishex)) {
throw std::invalid_argument("conversion error: input values are not not all xdigits");
}
std::vector<Int> result(src.length() / 2, 0);
auto str = std::begin(src);
std::for_each(std::begin(result), std::end(result), [&str](decltype(*std::begin(result))& element) {
element = static_cast<Int>(std::stoul(string_type(str, str + 2), nullptr, 16));
std::advance(str, 2);
});
return result;
}
The convert() function can then be based on the basic_convert() as follows:
std::vector<std::uint8_t> convert(std::string const& src)
{
return basic_convert<std::uint8_t>(src, std::locale());
}
Live sample.
uint8_t is typically no more than a typedef of an unsigned char. If you're reading characters from a file, you should be able to read them into an unsigned char array just as easily as a signed char array, and an unsigned char array is a uint8_t array.
I'd try something like this:
std::string starting_str = starting;
uint8_t[] ending = new uint8_t[starting_str.length()/2];
for (int i = 0 ; i < starting_str.length() ; i+=2) {
std::string pair = starting_str.substr( i, i+2 );
ending[i/2] = ::strtol( pair.c_str(), 0, 16 );
}
Didn't test it but it looks good to me...
You may add your own conversion from set of char { '0','1',...'E','F' } to uint8_t:
uint8_t ctoa(char c)
{
if( c >= '0' && c <= '9' ) return c - '0';
else if( c >= 'a' && c <= 'f' ) return 0xA + c - 'a';
else if( c >= 'A' && c <= 'F' ) return 0xA + c - 'A';
else return 0;
}
Then it will be easy to convert a string in to array:
uint32_t endingSize = strlen(starting)/2;
uint8_t* ending = new uint8_t[endingSize];
for( uint32_t i=0; i<endingSize; i++ )
{
ending[i] = ( ctoa( starting[i*2] ) << 4 ) + ctoa( starting[i*2+1] );
}
This simple solution should work for your problem
char* starting = "001122AABBCC";
uint8_t ending[12];
// This algo will work for any size of starting
// However, you have to make sure that the ending have enough space.
int i=0;
while (i<strlen(starting))
{
// convert the character to string
char str[2] = "\0";
str[0] = starting[i];
// convert string to int base 16
ending[i]= (uint8_t)atoi(str,16);
i++;
}
uint8_t* ending = static_cast<uint8_t*>(starting);