0

The goal is to create substrings from an inputted string by extracting words which are separated by spaces.

The substrings have to be variables themselves.

Sounds easy, but what makes it hard is that you can only use strcpy, strcmp, strlen, strcat, strncpy, strncmp, strnlen, and strncat.

Example:

input:

"John 40 Hitman"

driver:

...

cout << word1 << endl 
     << word2 << endl 
     << word3 << endl;

output:

John
40
Hitman

Here is my code

#include <iostream>
#include <cstring>
#include <stdio.h>

int main(){

const char *string = "Name Age Job";
char name[10];
char age[10];
char job[10];
int length = strlen(string);
int temp = 0;
bool flag = false;

for(int i = 0; i < length + 1; i++){
  if(isspace(string[i]) && !flag){
    strncpy(name, string, i);
    name[i] = '\0';
    temp = i;
    flag = !flag;
    cout << name << endl;
    continue;
  }
  if(isspace(string[i])){
    strncpy(age, string + temp + 1, length - i - 1);
    age[temp - i] = '\0';
    temp = i;
    cout << age << endl;
    continue;
  }
  if(string[i] == '\0'){
    strncpy(job, string + temp + 1, length);
    job[temp - i] = '\0';
    cout << job << endl;
  }
}

It works but it has to use a flag boolean, strings are not dynamic, only works for a string with 2 spaces, and there is a lot of repeated code. Overall really janky, but I spent roughly two hours on this and I do not know how to improve it.

If you are wondering, this is indeed a homework problem, but it's an intro class and my professor only wants the correct output for a hard coded string with only 3 words. I, however, want to learn how to improve upon it and would be really grateful for any help. Thanks.

  • 7
    https://codereview.stackexchange.com/ – Raffallo Nov 22 '19 at 07:30
  • 3
    If only using C-style str functions is _really_ a requirement then your class is a C programming class and not a C++ one. Sorry, but that's the reality. C++ify it by changing char[] to std::string. – acraig5075 Nov 22 '19 at 07:31
  • @Raffallo Just posting a link might not be that helpful (even if it is the right destination). OP: The linked site is for optimization of working code, so it would be a better fit for the sister site – Kami Kaze Nov 22 '19 at 07:33
  • 2
    @acraig5075 This is good practice though, to actually understand how strings work. C++ programmers must know this even if they only ever use std::string. – Lundin Nov 22 '19 at 07:33
  • Try using strtok method. It will totally reduce the size of the code. Refer http://www.cplusplus.com/reference/cstring/strtok/ – samk Nov 22 '19 at 07:34
  • 1
    Regarding Code Review - yes that is the best site to get general feedback on complete working code. It isn't off-topic here though, as long as the code is localized enough. – Lundin Nov 22 '19 at 07:35
  • 4
    I'm voting to close this question as off-topic because its's code review request. – Sourav Ghosh Nov 22 '19 at 07:35
  • @SameerKhan I definitely would but my professor limited this problem to only the aforementioned c-string functions and strtok was not one of them. – user12414777 Nov 22 '19 at 07:35
  • Even if this intro is a C++ intro, I think you'd be better off converting what you've got to pure `C` and change the tag to `C`. – Ted Lyngmo Nov 22 '19 at 07:36
  • @SouravGhosh No such close reason exists. Questions asking for improvements could be too broad, but this is a minimal and complete example and the problem is clear. – Lundin Nov 22 '19 at 07:36
  • Does the limitation imply that you can't use std::string ? – Support Ukraine Nov 22 '19 at 07:46
  • @user12414777 you mention some functions you can only use - but then in the code uses other stuff - like `isspace` and `cout`. Its not clear what we are allowed. – darune Nov 22 '19 at 07:49
  • 1
    Ok I'm gonna answer this just because of the close votes. _It is more suitable for https://codereview.stackexchange.com/ but it is not off-topic here!_ – Lundin Nov 22 '19 at 07:50
  • @Lundin Actually I disagree. C strings are not suitable in C++. If you had said `std::vector`s and functions mirroring the C standard library, that might be pedagogically relevant. – Caleth Nov 22 '19 at 09:28
  • @Caleth So when you end up interacting with a C API you are just gonna sit there dumbfounded? I guess that will work fine as long as you avoid rare exotic systems like Windows and Linux. – Lundin Nov 22 '19 at 09:36
  • @Lundin no, you call `str.data()` at the *last possible moment*. You don't need to use the C string functions that (have to) mix allocations in with doing what they are there for. – Caleth Nov 22 '19 at 09:38
  • @Caleth C APIs will often expect you to provide a read/write buffer in the form of a character array, so that won't work. You actually have to understand how character arrays work, no way around it. – Lundin Nov 22 '19 at 09:39
  • @Lundin Each of `std::string`, `std::vector` and `std::unique_ptr` can provide a pointer to an array of `char`. C has no way of distinguishing those from raw arrays. – Caleth Nov 22 '19 at 09:41
  • @Caleth What I mean is `void c_api (char* buf, size_t bytes_to_return);`. You can't pass `c_str()` to that because it is const qualified and immutable. – Lundin Nov 22 '19 at 12:11
  • @Lundin not anymore. There is a non const overload. – Caleth Nov 22 '19 at 12:12

3 Answers3

2

All you need to do is replace space ' ' with '\0'(the string end) thus creating 3 substrings from the original. The following program does that and just dumps the string to cout but you could hold the pointers in an array as well (eg. char* substring[3]).

int main(){
    char string[] = "Name Age Job";
    char* temp = string;
    for(char* it = string; *it; ++it ) {
        if (*it == ' ') {
            *it = '\0';
            std::cout << temp << std::endl;
            temp= it + 1;
        }
    }
    std::cout << temp << std::endl;
}
darune
  • 10,480
  • 2
  • 24
  • 62
  • The input string is immutable in the question though. – Lundin Nov 22 '19 at 09:38
  • Then you'd end up iterating over the same array twice which is slow. – Lundin Nov 22 '19 at 12:12
  • Performance is about the only reason to roll out this code manually. And then you have to be pretty good at knowing the target system. Even my version is too naive to be library-quality code, though I expect it to perform well on low end systems. – Lundin Nov 22 '19 at 12:31
  • @Lundin you seem to miss the main take away here: it is already easy enough to optimize IF you need a copy/second iteration and IF you need optimized code in the first place. – darune Nov 22 '19 at 12:56
1

The proper way to do this with C functions only would be to use strtok, although that one chops up a string in-place.

Regarding your code, there's lot of needless branching and checks. You should avoid continue, it is an almost 100% certain sign of a loop in need of improvements. Whenever you find yourself needing it, there's usually a better way.

You should also avoid strncpy because as you've noticed, it is a pain to keep track of when it null terminates and when it doesn't. It's a dangerous function and much slower than memcpy, which could be used instead here.

Here is a simplified version based on using 2 pointers, one all the time set to point at the next space and one all the time set to point at the beginning of the next valid word.

#include <string.h>
#include <ctype.h>
#include <stdio.h>

const char* find_next_space (const char* str)
{
  while(*str != '\0' && !isspace(*str))
    str++;
  return str;
}

int main (void)
{
  const char str[] = "hello world how are you";
  char substr[5][10];


  const char* word_start = str;
  for(size_t i = 0; i<5; i++)
  {
    if(*word_start == '\0')
      break;

    const char* next_space = find_next_space(word_start); 

    size_t length = (size_t)(next_space-word_start);
    memcpy(substr[i], word_start, length); 
    substr[i][length] = '\0';
    puts(substr[i]);

    word_start = next_space+1;
  }
}

This code simplifies things by not checking if a string would fit and it doesn't allocate memory. Real production-quality C code wouldn't use char [5][10] but rather a pointer array char* [5] where each pointer is set to point at dynamically allocated memory.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thanks a lot! Maybe I will figure out how to do the pointer array thing on my own – user12414777 Nov 22 '19 at 08:11
  • @user12414777 - A pointer is simply a normal variable that holds the address of something else as its value -- instead of an immediate values like `int a = 5;`. A pointer would hold the address where `5` is stored in memory. E.g `int *b;` is a *pointer-to* `int`, and `b = &a;` assigns the address where `5` is stored in memory as the value held by `b` (e.g. `b` now points to `a`) -- where `5` resides in memory. – David C. Rankin Nov 22 '19 at 20:55
-1

I agree with @acraig5075 as your code is more C than C++. If you are thinking of writing this in C++ using the STL string, one way of doing this is the following.

#include <iostream>
#include <string>
#include <sstream>
#include <vector>


std::vector<std::string> split_string( const std::string &srcString, const char &delimiterKeywrd )
{
    /**
     *  Splits a string according to a keyword delimiter and returns a vector with the 
     *  segments of the split string.
     *  
     *  Args:
     *      @param  srcString:          The required string to split.
     *      @param  delimiterKeywrd:    The delimiter keyword that the string will be splitted to
     *      @return segmentsVectr:      A vector holding the segments of the srcString that was splitted. 
     *
    */

    std::stringstream inputStr;
    inputStr.str( srcString );
    std::string segment;

    std::vector<std::string> segmentsVectr;

    while( std::getline( inputStr, segment, delimiterKeywrd ) )
    {
        segmentsVectr.push_back( segment );
    }

    return segmentsVectr;
}


int main() {
    std::string inputStr{"John 40 Hitman"};

    std::vector<std::string> result;
    char delimiterKeywrd = ' '; 

    result = split_string( inputStr, delimiterKeywrd );

    //  Iterate through the vector and print items on a different line.
    for ( const std::string &item : result )
    {
        std::cout << item << std::endl;
    }

    return 0;
}

Check std::string at cppreference and examples if you are not familiar. Also here I am using std::stringstream which makes it easy to use std::getline.

If std::stringstream is not your preferred way you can always check this implementation from another question about splitting a string by character.

Hope this helps.

bad_locality
  • 135
  • 2
  • 12