Как переставить слова в предложении без использования встроенных функций?

Это был вопрос интервью:

Как преобразовать Dogs like cats в cats like Dogs?

Мой код показывает: cats like cats. Где я делаю ошибки?

#include <iostream>
using namespace std;

int main()
{
    char sentence[] = ("dogs like cats");
    cout << sentence << endl;

    int len = 0;

    for (int i = 0; sentence[i] != '\0'; i++)
    {
        len++;
    }
    cout << len << endl;

    char reverse[len];
    int k = 0;

    for (int j = len - 1; j >= 0; j--)
    {
        reverse[k] = sentence[j];
        k++;
    }

    cout << reverse << endl;

    int words = 0;
    char str[len];

    for (int l = 0; reverse[l] != '\0'; l++)
    {
        if (reverse[l] == ' ' || reverse[l] == '\0') // not sure about this part
        {
            for (int m = l; m >= 0; m--)
            {
                str[words] = reverse[m];
                words++;
            }
        }
    }

    cout << str;

    return 0;
}

Я знаю, что вы можете сделать это, используя указатели, стек, векторы... но интервьюера это не интересовало!


person Erhan    schedule 13.08.2014    source источник
comment
1) поменять местами каждое слово. 2) перевернуть всю строку. Ваш код выглядит довольно сложным.   -  person Carl Norum    schedule 13.08.2014
comment
Я не знаю... Я думаю, что ваш результат более точен.   -  person chris    schedule 13.08.2014
comment
Напишите свою версию strrev (оставлено в качестве упражнения для читателя). Используйте его, чтобы перевернуть все предложение. Затем приступайте к поиску пробелов и поиску отдельных слов.   -  person Igor Tandetnik    schedule 13.08.2014
comment
Звучит как ужасный вопрос для интервью на С++, я был бы больше обеспокоен тем, что программисты на С++ поняли, как стандартная библиотека может сделать такие операции совершенно тривиальными, вместо того, чтобы возиться с необработанными массивами.   -  person user657267    schedule 13.08.2014
comment
m ведет обратный отсчет до 0 для каждого слова. Если я правильно понимаю, вы имели в виду, что он будет отсчитываться только до конца предыдущего слова.   -  person jogojapan    schedule 13.08.2014
comment
Большое спасибо за комментарии! это очень помогает.   -  person Erhan    schedule 14.08.2014


Ответы (5)


Это фиксированная версия вашего примера кода:

  • Ваша основная проблема заключается в том, что каждый раз, когда вы находите и ' ' или '\0', вы копируете байты обратной строки с начала до этой точки. Например, в loop 5 вы копируете из индекса 0-5 (stac) из reverse в str в обратном порядке, а в loop 10 вы копируете из индекса 0-10 (stac ekil) из reverse в str в обратном порядке, пока здесь вы уже не напечатаете строка результата («кошки как кошки»), и то же самое в loop 15 все это увеличивает индекс str, в последнем цикле вы пишете конец действительной памяти str (и из-за этого не печатается как вывод) .
  • Вам нужно отслеживать, когда заканчивается последнее слово, чтобы обратить только фактическое слово, а не строку от начала до фактического индекса.
  • Вы не хотите подсчитывать специальный символ (' ' и '\ 0') в перестановке слов, вы закончите с cats like\0dogs

Модифицированный образец кода:

#include <iostream>
using namespace std;

int main() {
    char sentence[] = ("dogs like cats");
    cout << sentence << endl;

    int len = 0;

    for (int i = 0; sentence[i] != '\0'; i++) {
        len++;
    }
    cout << len << endl;

    char reverse[len];
    int k = 0;

    for (int j = len - 1; j >= 0; j--) {
        reverse[k] = sentence[j];
        k++;
    }

    cout << reverse << endl;

    int words = 0;
    char str[len];

    // change here added last_l to track the end of the last word reversed, moved
    // the check of the end condition to the end of loop body for handling the \0
    // case
    for (int l = 0, last_l = 0; ; l++) {
        if (reverse[l] == ' ' || reverse[l] == '\0')
        {
            for (int m = l - 1; m >= last_l; m--) { // change here, using last_t to
                str[words] = reverse[m];            // only reverse the last word
                words++;                            // without the split character
            }
            last_l = l + 1;                         // update the end of the last
                                                    // word reversed
            str[words] = reverse[l];                // copy the split character
            words++;
        }
        if (reverse[l] == '\0')                     // break the loop
            break;
    }

    cout << str << endl;

    return 0;
}

Некоторый код, написанный с ограничением использования самых простых возможностей языка.

#include <iostream>

// reverse any block of text.
void reverse(char* left, char* right) {
    while (left < right) {
        char tmp = *left;
        *left = *right;
        *right = tmp;

        left++;
        right--;
    }
}

int main() {
    char sentence[] = "dogs like cats";
    std::cout << sentence << std::endl;

    // The same length calculation as sample code.
    int len = 0;
    for (int i = 0; sentence[i] != '\0'; i++) {
        len++;
    }
    std::cout << len << std::endl;

    // reverse all the text (ex: 'stac ekil sgod')
    reverse(sentence, sentence + len - 1);

    // reverse word by word.
    char* end = sentence;
    char* begin = sentence;
    while (end < sentence + len) {
        if (*end != ' ')
            end++;

        if (end == sentence + len || *end == ' ') {
            reverse(begin, end - 1);
            begin = end + 1;
            end = begin;
        }
    }

    std::cout << sentence << std::endl;

    return 0;
}
person NetVipeC    schedule 13.08.2014

Разбивая алгоритм на части. Во-первых, вы находите длину строки, не включая ограничитель нулевого символа. Это правильно, хотя можно упростить.

size_t len = 0;
for (int i = 0; sentence[i] != '\0'; i++) {
    len++;
}
cout << len << endl;

Это можно было бы легко записать просто так:

size_t len = 0;
while (sentence[len])
    ++len;

Затем вы переворачиваете всю строку, но появляется первый дефект. VLA (массив переменной длины), который вы объявляете здесь (который вам не нужен и не должен использоваться, поскольку это расширение С++ и нестандартно), не учитывает и не устанавливает завершающий нулевой символ.

char reverse[len]; // !! should be len+1
int k = 0;
for (int j = len - 1; j >= 0; j--) {
    reverse[k] = sentence[j];
    k++;
}
// !! Should have reverse[k] = 0; here.
cout << reverse << endl; // !! Undefined-behavior. no terminator.

Эта строка временного буфера совсем не нужна. Нет никаких причин, по которым вы не можете выполнить всю эту операцию на месте. Как только мы вычислим len правильно, вы просто сделаете что-то вроде следующего, чтобы перевернуть всю последовательность, которая сохраняет нулевой терминатор символа в правильном положении:

// reverse entire sequence
int i = 0, j = len;
while (i < j--)
{
    char c = sentence[i];
    sentence[i++] = sentence[j];
    sentence[j] = c;
}

Далее мы переходим к тому, где вы пытаетесь перевернуть каждое внутреннее слово. Опять же, как и раньше, длина буфера неверна. Должно быть len+1. Что еще хуже (трудно себе представить), вы никогда не помните, где остановились, когда искали окончание слова. Это место должно быть следующей точкой, которую вы начинаете проверять и пропускать пробелы. Не сохраняя этого, вы копируете из текущей точки обратно в начало строки. который по существу взрывает cats над dogs.

int words = 0;
char str[len]; // !! should be len+1
for (int l = 0; reverse[l] != '\0'; l++) 
{
    if (reverse[l] == ' ' || reverse[l] == '\0') // not sure about this part
    {
        for (int m = l; m >= 0; m--) {
            str[words] = reverse[m];
            words++;
        }
    }
}
cout << str; //!! Undefined behavior. non-terminated string.

Опять же, это можно сделать на месте без каких-либо затруднений. Один из таких алгоритмов выглядит следующим образом (и обратите внимание, что цикл, который переворачивает фактическое слово, не является тем же самым алгоритмом, что и переворачивание всего нашего буфера):

// walk again, reversing each word.
i = 0;
while (sentence[i])
{
    // skip ws; root 'i' at beginning of word
    while (sentence[i] == ' ') // or use std::isspace(sentence[i])
        ++i;

    // skip until ws or eos; root 'j' at one-past end of word
    j = i;
    while (sentence[j] && sentence[j] != ' ') // or use !std::isspace(sentence[j])
        ++j;

    // remember the last position
    size_t last = j;

    // same reversal algorithm we had before
    while (i < j--)
    {
        char c = sentence[i];
        sentence[i++] = sentence[j];
        sentence[j] = c;
    }

    // start at the termination point where we last stopped
    i = last;
}

Собираем все вместе

Хотя использовать указатели значительно проще, чем все эти индексные переменные, следующее будет делать то, что вы пытаетесь сделать на месте.

#include <iostream>

int main()
{
    char s[] = "dogs like cats";
    std::cout << s << '\n';

    size_t len = 0, i, j;
    while (s[len])
        ++len;

    // reverse entire sequence
    i = 0, j = len;
    while (i < j--)
    {
        char c = s[i]; // or use std::swap
        s[i++] = s[j];
        s[j] = c;
    }

    // walk again, reversing each word.
    i = 0;
    while (s[i])
    {
        // skip ws; root 'i' at beginning of word
        while (s[i] == ' ') // or use std::isspace
            ++i;

        // skip until ws or eos; root 'j' at one-past end of word
        j = i;
        while (s[j] && s[j] != ' ') // or use !std::isspace
            ++j;

        // remember the last position
        size_t last = j;
        while (i < j--)
        {
            char c = s[i]; // or use std::swap
            s[i++] = s[j];
            s[j] = c;
        }

        // start at last-left posiion
        i = last;
    }

    std::cout << s << '\n';
    return 0;
}

Вывод

dogs like cats
cats like dogs
person WhozCraig    schedule 13.08.2014

Я бы посоветовал разбить исходную строку на массив слов, перевернуть этот массив. Затем добавьте эти слова в перевернутое предложение с пробелом между ними.

person user3931595    schedule 13.08.2014
comment
или массив указателей на каждое слово, затем инвертирование массива. Вам нужно будет использовать пробелы в качестве разделителей для каждого слова (или заменить пробелы нулевыми символами ('\0'). - person rcgldr; 13.08.2014
comment
Да, но в постановке задачи конкретно говорится, что он не может использовать указатели. - person user3931595; 13.08.2014
comment
Значит, можно использовать индексы, но не указатели? Индексы можно использовать таким же образом, массив индексов в начале каждого слова, но я не вижу существенной разницы. - person rcgldr; 13.08.2014

Поскольку они не просили никаких библиотек, я предположил, что нет std::string, нет vectors, вообще ничего, и поэтому я написал это на C. Единственное, что использовалось, это printf. Все остальное с нуля :l

Идея состоит в том, что вы сначала переворачиваете массив. Затем разделите массив по пробелу и переверните каждое слово.

Пример: http://ideone.com/io6Bh9

Код:

#include <stdio.h>

int strlen(const char* s)
{
    int l = 0;
    while (*s++) ++l;
    return l;
}


void reverse(char* str)
{
    int i = 0, j = strlen(str) - 1;

    for(; i < j; ++i, --j)
    {
       str[i] ^= str[j];
       str[j] ^= str[i];
       str[i] ^= str[j];
    }
}

void nulltok(char* str, char tok, int* parts)
{
    int i = 0, len = strlen(str);
    *parts = 1;

    for (; i < len; ++i)
    {
        if (str[i] == tok)
        {
            str[i] = '\0';
            ++(*parts);
        }
    }
}

char* reverse_sentence(char* str)
{
    char* tmp = str;
    reverse(str);

    int i = 0, parts = 0, len = strlen(str);
    nulltok(str, 0x20, &parts);

    while(parts--)
    {
        reverse(str);
        str += strlen(str) + 1;
    }

    for(; i < len; ++i)
        if (tmp[i] == '\0')
            tmp[i] = 0x20;

    return tmp;
}


int main(void)
{
    char str[] = "dogs like cats";
    printf("%s", reverse_sentence(str));

    return 0;
}
person Brandon    schedule 13.08.2014

Мое решение

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
string str;
cout<<"enter the sentence"<<endl;
getline(cin,str);
char* pch;
pch = strtok((char*)str.c_str()," ");
string rev = "";

while(NULL != pch)
{
    rev.insert(0,pch);
    rev.insert(0," ");
    pch = strtok(NULL," ");
}
cout<<"the reversed string is :"<<rev<<endl;
return 0;

}

person Sankeerna    schedule 20.09.2015