Как бы вы улучшили этот алгоритм? (разворот строки c)

Работая над некоторыми задачами на собеседовании по программированию, которые я нашел в Интернете, мне пришлось написать алгоритм для обратного преобразования const char * и возврата указателя на новый char *. Я думаю, что он у меня есть, но для того, чтобы он работал должным образом, мне пришлось сделать некоторые шаткие вещи - в основном, чтобы самому учитывать символ завершения нуля. Почему-то я чувствую, что это неправильно, но я в тупике, и мне было интересно, может ли кто-нибудь мне помочь:

char * reverse(const char * str)
{
  int length = strlen(str);
  char * reversed_string = new char[length+1];

  for(int i = 0; i < length; ++i)
  {
    reversed_string[i] = str[(length-1) - i];
  }
  //need to null terminate the string
  reversed_string[length] = '\0';

  return reversed_string;

}

int main(int argc, char * argv[])
{

  char * rev_str = reverse("Testing");

  cout << "Your string reversed is this: " << rev_str << endl;

  delete rev_str;
  rev_str = 0;

  return 0;
}

Вы должны использовать delete [], а не простое удаление.

Adam Rosenfield 20.10.2008 22:51

Не забудьте проверить строку NULL.

Michael McCarty 20.10.2008 22:51

спасибо адам и майкл, хорошие советы!

Keith 20.10.2008 22:56

Мой C++ заржавел, но не следует ли ему удалить [] rev_str вместо delete rev_str?

Rontologist 20.10.2008 23:59

Хорошо, у меня действительно был этот вопрос во время интервью на прошлой неделе.

smaclell 21.10.2008 00:28

Это C++, а не C. Если вы хотите, чтобы это было и то, и другое, используйте malloc () и free (), а не new и delete.

wnoise 21.10.2008 07:18

Это должно быть в CodeReview.

user3139831 03.03.2014 22:29

«Это должно быть в CodeReview» Я не думаю, что это было в 2008 году :)

Daniel Ryan 14.10.2015 04:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
8
8 928
20

Ответы 20

У меня однажды был этот вопрос. Это первый ответ, который приходит на ум, но следующий: «Теперь сделайте это, не выделяя памяти».

int length = strlen(string);
for(int i = 0; i < length/2; i++) {
  char c = string[i];
  string[i] = string[length - i];
  string[length - i] = c;
}

Обновлено: Некоторые люди выразили пренебрежение тем, что не используют указатели. Это немного более читабельно, хотя и не полностью оптимально. Другие уже вошли в решение указателя, поэтому я не буду повторять его здесь.

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

string[i] = string[i] ^ string[length - i];
string[length - i] = string[i] ^ string[length - i];
string[i] = string[i] ^ string[length - i];

Но в целом современные компиляторы могут оптимизировать локальную переменную наивного свопа. Подробности: См. Википедию

Поскольку JDS начинается с const char *, требуется выделение (или нарушение постоянной правильности путем литья) ...

dmckee --- ex-moderator kitten 20.10.2008 22:43

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

nsayer 20.10.2008 22:44

Не забудьте проверить нулевую строку

Michael McCarty 20.10.2008 22:48

Теперь сделайте это без использования временной переменной хранения.

Giao 20.10.2008 23:51

Чуть быстрее, с арифметикой указателя: int top = strlen (string) -1; char c; for (; top! = 0; top--, строка ++) {c = строка [0]; строка [0] = строка [вверху]; строка [вверху] = c; }

Joe Pineda 21.10.2008 01:45

Собственно, в этом коде есть ошибка. length = 4. Это должно быть: swap(0, 3), swap(1, 2);, текущий код: swap(0, 4), swap(1, 3). Чтобы исправить это, замените length - i на length - i - 1

Jack 12.08.2014 22:08

это прекрасно работает:

#include <algorithm>
#include <iostream>
#include <cstring>

void reverse_string(char *str) {    
    char *end = str + strlen(str) - 1;
    while (str < end) {
        std::iter_swap(str++, end--);
    }
}

int main() {
    char s[] = "this is a test";
    reverse_string(s);
    std::cout << "[" << s << "]" << std::endl;
}
if ( string[0] )
{
    char *end = string + strlen(string)-1;
    while( start < end )
    {
        char temp = *string;
        *string++ = *end;
        *end-- = temp;
    }
}

Вы, вероятно, имеете в виду * end--, а не * end ++.

nsayer 20.10.2008 22:48

Даже если *end++ зафиксирован как *end--, строка, которая инициализирует `end, приводит к неопределенному поведению, если strlen () возвращает 0.

Michael Burr 20.10.2008 22:53

(фейспалм) Ой. @Mike B: Не определено, но полностью надежно в реальном мире (как и большая часть продукции C)

Menkboy 20.10.2008 23:01

Да - у меня не было бы сердечного приступа, если бы я увидел это в обзоре кода, но я бы упомянул об этом и ожидал, что это будет исправлено в результате того, что оно будет поднято в обзоре кода.

Michael Burr 20.10.2008 23:12

Как (p + 0-1) undefined для указателя p? * p будет undefined, но вполне нормально переместить указатель на один назад, даже если он указывает на начало выделенной области. И, конечно же, с end == staring - 1, while () не будет введен. Я называю это «элегантным».

user3458 21.10.2008 00:23

@Arkaiy: Стандарт C гласит: «Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, оценка не должна вызывать переполнения; в противном случае поведение неопределенный."

Michael Burr 22.10.2008 19:08

Однако, как заметил Менкбой, на самом деле он работает практически все время, но, тем не менее, не определен (например, он может сломаться на сегментированных архитектурах).

Michael Burr 22.10.2008 19:09

std::reverse из <algorithm> работает для строк и массивов char:

string str = "Hello";
char chx[] = "Hello";

reverse(str.begin(), str.end());
reverse(chx, chx + strlen(chx));

cout << str << endl;
cout << chx << endl;

/ EDIT: это, конечно, изменяет исходную строку. Но STL пришел на помощь. Следующее создает новую перевернутую строку. К сожалению (?), Это не работает напрямую с массивами C char без создания дополнительной (неявной) копии:

string reverse_string(string const& old) {
    return string(old.rbegin(), old.rend());
}

cout << reverse_string("Hello") << endl;

да. Предпочитайте библиотеку, если она доступна.

dmckee --- ex-moderator kitten 20.10.2008 22:55

Изменение исходной строки - очень плохая идея, если исходная строка является указателем на строковый литерал. Лучше бы массив.

bk1e 21.10.2008 08:46

b1ke: ой. Следует проверить больше. ;-)

Konrad Rudolph 21.10.2008 12:03

Является ли использование библиотеки хорошим ответом на собеседование по программированию, если задача состоит в том, чтобы «написать алгоритм»?

akaihola 10.02.2009 12:18

акайхола: это зависит от ситуации интервью. Но по моему опыту, знания стандартной библиотеки C++ ужасны, даже у программистов, которые заявляют, что они являются опытными программистами на C++. Перевернуть строку (даже эффективно) - тривиальная проблема. Важно знать правильную функцию C++ stdlib.

Konrad Rudolph 10.02.2009 13:47

akaihola: Кроме того, уже были опубликованы правильные решения - зачем их повторять? ИМХО, гораздо больше смысла расширять и показывать альтернативные подходы, даже если они работают для немного других требований.

Konrad Rudolph 10.02.2009 14:13

Я бы решил это примерно так (мой c немного ржавый, простите меня)

char *reverse( const char *source ) {
  int len = strlen( source );
  char *dest = new char[ len + 1 ];
  int i = 0;
  int j = len;
  while( j > 0 ) {
    dest[j--] = src[i++];
  }
  dest[i] = \0;
  return dest;
}

На самом деле, учитывая ограничение, согласно которому исходная строка остается неизменной, я думаю, что исходный подход, указанный в вопросе, является лучшим. Все эти причудливые подходы к изменению места, которое публикуют люди, великолепны, но как только копирование данной строки учтено, все они менее эффективны, чем простое копирование строки в обратном направлении.

Только если он остановится на полпути; как написано, переключает все дважды, не так ли?

Jonathan Leffler 20.10.2008 23:45

Нет - вычеркните этот комментарий; это не на месте.

Jonathan Leffler 20.10.2008 23:46

Ну, конечно, это модные подходы. Это является вопрос интервью, о котором мы говорим. :)

nsayer 21.10.2008 02:26

Мы уже задавали этот вопрос раньше - с удивительными результатами, обнаружившими множество людей, которые не могут этого сделать (даже со значительным опытом работы с C / C++!). Я предпочитаю вариант на месте, так как он экономит некоторые накладные расходы и имеет дополнительный поворот, заключающийся в необходимости повторять только символы strlen (s) / 2.

Ваше решение на собеседовании подойдет. Решение (правильное!), Использующее указатель вместо синтаксиса массива, будет оцениваться немного выше, поскольку оно показывает больший уровень комфорта с указателями, которые так важны в программировании на C / C++.

Незначительной критикой было бы указать, что strlen возвращает size_t, а не int, и вы должны использовать delete [] для rev_str.

Вариант на месте должен перебирать 3 * strlen (s) / 2 символа, потому что сначала он должен вызвать strlen.

Sol 20.10.2008 23:15

Я бы сказал, что причиной Только использования указателей в этом упражнении было бы продемонстрировать навыки работы с указателями. Синтаксис массива делает код более понятным. Вы можете возразить, что выбор указателей демонстрирует склонность к показухе!

slim 21.10.2008 11:39

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

Потребовалось бы два прохода и скретч-стек, но я бы, вероятно, больше доверял себе, чтобы сделать это правильно с первого раза, а не делать одну ошибку, подобную приведенной выше.

char* stringReverse(const char* sInput)
{
    std::size_t nLen = strlen(sInput);
    std::stack<char> charStack;
    for(std::size_t i = 0; i < nLen; ++i)
    {
        charStack.push(sInput[i]);
    }
    char * result = new char[nLen + 1];
    std::size_t counter = 0;
    while (!charStack.empty())
    {
        result[counter++] = charStack.top();
        charStack.pop();
    }
    result[counter] = '\0';
    return result;
}

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

MichaelGG 21.10.2008 00:25

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

JohnMcG 21.10.2008 00:39

Эм-м-м? С указателями никто не делал?

char *reverse(const char *s) {
    size_t n = strlen(s);
    char *dest = new char[n + 1];
    char *d = (dest + n - 1);

    dest[n] = 0;
    while (*s) {
        *d-- = *s++
    }

    return dest;
}

Надеюсь, годы Java не испортили мой C ;-)

Редактировать: заменил все эти вызовы strlen на дополнительную переменную. Что возвращает strlen в наши дни? (Спасибо цоколь).

Будьте осторожны - вы трижды вызывали strlen () для s, хотя одна из них могла бы сделать это - это означает, что вы проходите строку 4 раза.

plinth 21.10.2008 00:01

Ваш код прост и неудивителен. Несколько вещей:

  1. Используйте size_t вместо int для индекса цикла
  2. Хотя ваш компилятор, скорее всего, достаточно умен, чтобы понять, что (длина -1) инвариантна, он, вероятно, недостаточно умен, чтобы понять, что (длина-1) -i лучше всего заменить другой переменной цикла, которая уменьшается на каждый проход
  3. Я бы использовал указатели вместо синтаксиса массива - мне будет проще иметь * dst-- = * srC++; в петле.

Другими словами:

char *dst = reversed_string + length;
*dst-- = '\0';
while (*src) {
   *dst-- = *src++;
}

Задавая этот вопрос в качестве интервьюера, я ищу ясное, понятное решение и могу спросить, как можно сделать первоначальное решение более эффективным. Меня не интересуют «умные» решения.

Я думаю о таких вещах; кандидат сделал старую с отключенной одной ошибкой в ​​своем цикле, выделяют ли они заранее достаточно памяти, проверяют ли они неправильный ввод, используют ли они эффективные типы достаточно.

К сожалению, как уже отмечалось, слишком многие люди не могут даже этого сделать.

@ Конрад Рудольф: (извините, у меня нет опыта, чтобы оставлять комментарии)

Я хочу отметить, что STL предоставляет алгоритм reverse_copy (), аналогичный обратный(). Вам не нужно вводить временный объект, как вы это сделали, просто выделите новый char * нужного размера.

Насчет reverse_copy: правда. Однако не вижу никаких преимуществ. О временной копии: она необходима, если я хочу вернуть string. Если я выделю char array и верну его, вызывающий должен очистить эту память - беспорядок. Лучше придерживаться строк C++.

Konrad Rudolph 21.10.2008 12:07

В опубликованном вопросе требовалось вернуть char *, я просто пытался указать, что это тоже можно сделать. Если вы действительно хотите вернуть std :: string, ваш метод великолепен, потому что он хорошо работает с RVO и использует конструктор диапазона std :: string для возможных оптимизаций.

mmocny 21.10.2008 19:49

WRT: «Теперь сделайте это без временной переменной хранения» ... Возможно, что-то вроде этого (и пока сохраняем индексацию массива):

int length = strlen(string);
for(int i = 0; i < length/2; i++) {
  string[i] ^= string[length - i];
  string[length - i] ^= string[i];
  string[i] ^= string[length - i];
}

Я знаю, что это очень непереносимо, но инструкция ассемблера x86 bswap позволяет вам менять местами четыре байта с помощью всего одной инструкции, что может быть хорошим путем для ускорения кода.

Это пример того, как заставить его работать с GCC.

/* 
 * reverse.c
 *
 * $20081020 23:33 fernando DOT miguelez AT gmail DOT com$
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_CHARS 10 * 1024 * 1024

/*
 * Borrowed from http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2007-03/msg00004.html
 * GNU Compiler syntax
 */
inline uint32_t bswap(uint32_t val)
{
    __asm__("bswap %0" : "=r" (val) : "0" (val));
    return val;
}

char * reverseAsm(const char * str)
{
    int i;
    int length = strlen(str);
    int dwordLength = length/4;

    if (length % 4 != 0)
    {
        printf("Error: Input string length must be multiple of 4: %d\n", length);       
        return NULL;
    }

    char * reversed_string = (char *) malloc(length+1);
    for(i = 0; i < dwordLength; i++)
    {
        *(((uint32_t *) reversed_string) + dwordLength - i - 1) = bswap(*(((uint32_t *) str) + i));
    }

    reversed_string[length] = '\0';

    return reversed_string;
}

char * reverse(const char * str)
{
    int i;
    int length = strlen(str);
    char * reversed_string = (char *) malloc(length+1);

    for(i = 0; i < length; ++i)
    {
        reversed_string[i] = str[(length-1) - i];
    }

        //need to null terminate the string

    reversed_string[length] = '\0';

    return reversed_string;
}

int main(void)
{
    int i;
    char *reversed_str, *reversed_str2;
    clock_t start, total;
    char *str = (char *) malloc(MAX_CHARS+1);

    str[MAX_CHARS] = '\0';

    srand(time(0));

    for(i = 0; i < MAX_CHARS; i++)
    {
        str[i] = 'A' + rand() % 26;     
    }

    start = clock();
    reversed_str = reverse(str);
    total = clock() - start;
    if (reversed_str != NULL)
    {
        printf("Total clock ticks to reverse %d chars with pure C method: %d\n", MAX_CHARS, total); 
        free(reversed_str);
    }
    start = clock();
    reversed_str2 = reverseAsm(str);
    total = clock() - start;
    if (reversed_str2 != NULL)
    {
        printf("Total clock ticks to reverse %d chars with ASM+C method: %d\n", MAX_CHARS, total); 
        free(reversed_str2);
    }

    free(str);

    return 0;
}

Результаты на моем старом компьютере под Cygwin:

fer@fernando /cygdrive/c/tmp$ ./reverse.exe
Total clock ticks to reverse 10485760 chars with pure C method: 221
Total clock ticks to reverse 10485760 chars with ASM+C method: 140

Перевернутая строка на месте, без временной переменной.

static inline void
byteswap (char *a, char *b)
{
  *a = *a^*b;
  *b = *a^*b;
  *a = *a^*b;
}

void
reverse (char *string)
{
  char *end = string + strlen(string) - 1;

  while (string < end) {
    byteswap(string++, end--);
  }
}

В целях безопасности, byteswap () должен сначала проверить, имеет ли значение a == b, и немедленно вернуться, если это так. Обратите внимание, что я сказал a == b, а не * a == * b. Если вы попытаетесь поменять местами одну и ту же ячейку памяти с собой, метод XOR вместо этого изменит ее на 0.

nsayer 21.10.2008 02:36

Хорошая точка зрения. Я также должен проверить, что строка не ПУСТО (NULL), но я хотел, чтобы пример был компактным.

Andrew Johnson 21.10.2008 02:48

Метод, не требующий временных переменных

int length = strlen(string);
for(int i = 0; i < length/2; i++) {
  string[i] ^= string[length - i] ^= string[i] ^= string[length - i];
}

Если бы я проводил собеседование, я был бы немного более привередлив к качеству решения с точки зрения его надежности, а не только его производительности.

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

Многие из ответов одержимы производительностью до такой степени, что упускают из виду одну из ключевых проблем вопроса: перевернуть const char *, то есть вам нужно сделать перевернутый копировать, а не перевернуть на месте. Вам будет сложно сократить вдвое количество итераций, если потребуется копия!

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

.

char * reverse(const char * str)
{
  if (!str)
    return NULL;

  int length = strlen(str);
  char * reversed_string = new char[length+1];

  for(int i = 0; i < length/2; ++i)
  {
    reversed_string[i] = str[(length-1) - i];
    reversed_string[(length-1) - i] = str[i];
  }
  //need to null terminate the string
  reversed_string[length] = '\0';

  return reversed_string;

}

Половина времени, но такая же сложность (примечание может быть отключено из-за одной ошибки)

Над циклом for есть опечатка. Проверка переменной цикла i должна быть <= вместо <, othrewise не удастся из-за нечетного количества элементов. для (int i = 0; i <= length / 2; ++ i)

Вы не можете (не должны) этого делать:

string[i] ^= string[length - i] ^= string[i] ^= string[length - i];

От: http://en.wikipedia.org/wiki/XOR_swap_algorithm#Code_example

  • * "Этот код имеет неопределенное поведение, поскольку он изменяет lvalue x дважды без промежуточной точки последовательности.

Другие вопросы по теме