Рекурсивный алгоритм подстроки, не работающий

Я - студент программирования в своем первом классе C++, и недавно мы были поощрены записать простую рекурсивную функцию для нахождения первого вхождения подстроки в данной строке. Если найдено, это возвращает индекс. Если подстрока не найдена, index_of() функция должна возвратиться-1. Мы поощряемся использовать функцию помощника, которая берет индекс в качестве одного из его параметров, и это - то, что я попробовал.

Например:

int index_of("Mississippi", "sip"); // this would return a 6

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

Я сделал этот успешно C-стиль использования строки и указатели, но не со станд. C++:: строковые объекты. Что я делаю неправильно в моей программе? Мой преподаватель заявил, что мы должны легко смочь записать это в 5 минут, но я боролся с ним в течение двух часов. Вот то, что я сделал до сих пор:

int index_of(string s, string t)
{
    int index = 0;

    if (s[index] == NULL)
        return -1;
    else if (starts_with(s, t, ++index))
    {
        return index;
    }
    else 
        return index;
}

bool starts_with(string s, string t, int index)
{
    if (t[index] == NULL)
        return true;
    if ( s[index] == NULL || t[0] != s[index])
        return false;
    return starts_with(s, t, ++index);
}

Как записано, эта функция всегда возвращается index из 1.

5
задан jam 26 September 2012 в 11:55
поделиться

5 ответов

int index_of(string s, string t)
{
    int index = 0;

    if (s[index] == NULL)

Точка. Строки C ++ работают не так, и вы должны исправить это, если хотите их использовать. Даже со строками в стиле C не используйте NULL для обозначения нулевого символа ASCII. Они имеют общее имя, но имеют разные цели, и вы не должны использовать NULL для обозначения целого нуля (символы - это целые типы, а нулевой символ - их нулевое значение). Используйте '\ 0' или просто if (s [index]) .

Однако вам не разрешено индексировать std :: string, если вы не знаете, что индекс действителен. Для этого сравните индекс с s.size () (и убедитесь, что он больше или равен 0). Тем не менее, что вы здесь действительно проверяете, так это то, что s пусто, и для этого есть специальный метод:

    if (s.empty())

Продолжение:

    else if (starts_with(s, t, ++index))

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

Как ни странно, создатели Go, которые также были вовлечены в раннюю историю C, даже превратили инкремент из выражения в утверждение , и я считаю, что ясность - большая часть причины.


С самого начала

Вы хотите реализовать функцию с этой подписью:

int index_of(string haystack, string needle);
// returns the index of needle in haystack, if found
// otherwise returns -1

Я специально включил эти комментарии с подписью: они являются частью открытого интерфейса для этой функции. Более точные названия параметров также увеличивают ясность.

Определите случаи, которые вам необходимо учитывать:

  • игла пуста (вы можете справиться с этим несколькими способами)
  • стог сена пуст: return -1
  • на данный момент мы знаем, что стог сена и иголка нет empty
  • , что оставляет два случая, которые являются сутью алгоритма:
    • первый символ стога сена не соответствует первому символу иглы
    • есть совпадение первый символ

И когда есть совпадение первого символа, у вас есть два подварианта:

  • больше нет символов в игле: совпадение найдено
  • есть больше символов: продолжить проверку

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

Прямой перевод вышеупомянутой прозы в код:

int index_of(string haystack, string needle) {
  if (needle.empty()) return 0;
  // this implementation considers empty substrings to occur at the start of any
  // string, even an empty haystack; you could also make it an error to call
  // index_of when needle is empty, or just return -1

  if (haystack.empty()) return -1;

  assert(!needle.empty() && !haystack.empty()); // I wouldn't normally include
  // this, since we just checked these conditions, but this is the "at this
  // point we know both haystack and needle are not empty" that I mentioned

  if (haystack[0] != needle[0]) {
    // mark A, see below
    int index = index_of(haystack.substr(1), needle);
    return index != -1 ? index + 1 : index;
  }

  if (needle.length() == 1) return 0; // found complete match
  // note the way I chose to handle needle.empty() above makes this unnecessary

  // mark B, see below    
  // partial match (of the first character), continue matching
  int index = index_of(haystack.substr(1), needle.substr(1)); // strip first
  return index == 0 ? 0 : -1;
  // must check index == 0 exactly, if -1 then we must return that, and if not 0
  // then we've found a "broken" needle, which isn't a real match
}

Комментарий сломанной стрелки намекает на то, насколько неэффективен этот код, поскольку он разделяет рекурсивные вызовы на две категории: должен соответствовать 1 (который равен 0 после разделения на подстроки ), на отметке B, и может совпадать где угодно, на отметке A. Мы можем улучшить это с помощью вспомогательной функции, и я буду использовать для этого std :: string оператор == overload (работающий с подстрокой стога сена).Это дает рекурсивный эквивалент классического "наивного strstr ":

int index_of(string haystack, string needle) {
  if (needle.empty()) return 0;
  if (haystack.empty()) return -1;
  if (haystack.substr(0, needle.length()) == needle()) {
    return 0;
  }
  int index = index_of(haystack.substr(1), needle);
  if (index != -1) index++;
  return index;
}

И при использовании индекса для стога сена с string :: compare в качестве помощника, поэтому индекс иглы не требуется:

// might not be exposed publicly, but could be
int index_of(string const& haystack, int haystack_pos, string const& needle) {
  // would normally use string const& for all the string parameters in this
  // answer, but I've mostly stuck to the prototype you already have

  // shorter local name, keep parameter name the same for interface clarity
  int& h = haystack_pos;

  // preconditions:
  assert(0 <= h && h <= haystack.length());

  if (needle.empty()) return h;
  if (h == haystack.length()) return -1;
  if (haystack.compare(h, needle.length(), needle) == 0) {
    return h;
  }
  return index_of(haystack, h+1, needle);
}

int index_of(string haystack, string needle) {
  // sets up initial values or the "context" for the common case
  return index_of(haystack, 0, needle);
}

Замечание эта версия хвостовая рекурсивная, но это все еще наивный алгоритм, и существуют более продвинутые .


Если бы у меня было больше времени, я бы написал более короткое письмо.
- Цицерон

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

7
ответ дан 13 December 2019 в 05:34
поделиться

Я взял ответ из romkyns и сделал его немного более общим

def field_type(field, ftype):
    try:
        t = field.field.widget.__class__.__name__
        return t.lower() == ftype
    except:
        pass
    return False

Таким образом вы можете проверить тип виджета непосредственно со последовательностью

{% if field|field_type:'checkboxinput' %}
    <label>{{ field }} {{ field.label }}</label>
{% else %}
    <label> {{ field.label }} </label> {{ field }}
{% endif %}
-121--2526823-
public static class ExtenstionMethods
{
    public static IEnumerable<KeyValuePair<Int32, T>> Indexed<T>(this IEnumerable<T> collection)
    {
        Int32 index = 0;

        foreach (var value in collection)
        {
            yield return new KeyValuePair<Int32, T>(index, value);
            ++index;
        }
    }
}

foreach (var iter in websitePages.Indexed())
{
    var websitePage = iter.Value;
    if(iter.Key == 0) classAttributePart = " class=\"first\"";
    sb.AppendLine(String.Format("<li" + classAttributePart + "><a href=\"{0}\">{1}</a></li>", websitePage.GetFileName(), websitePage.Title));
}
-121--1609778-

Ваш код сводится к этому

int index_of(string s, string t)
{
    int index = 0;

    //if (s[index] == NULL)
    //    return -1;
    ++index // from this: else if (starts_with(s, t, ++index))
    //{
    //    return index;
   // }
   //else 
        return index;
}

, поэтому да, он всегда возвращает 1

индекс продолжает увеличиваться внутри рекурсивной функции start _ with , но измененные значения никогда не делают его возвращаемым значением.

5
ответ дан 13 December 2019 в 05:34
поделиться
public static class ExtenstionMethods
{
    public static IEnumerable<KeyValuePair<Int32, T>> Indexed<T>(this IEnumerable<T> collection)
    {
        Int32 index = 0;

        foreach (var value in collection)
        {
            yield return new KeyValuePair<Int32, T>(index, value);
            ++index;
        }
    }
}

foreach (var iter in websitePages.Indexed())
{
    var websitePage = iter.Value;
    if(iter.Key == 0) classAttributePart = " class=\"first\"";
    sb.AppendLine(String.Format("<li" + classAttributePart + "><a href=\"{0}\">{1}</a></li>", websitePage.GetFileName(), websitePage.Title));
}
-121--1609778-

Ваш код сводится к этому

int index_of(string s, string t)
{
    int index = 0;

    //if (s[index] == NULL)
    //    return -1;
    ++index // from this: else if (starts_with(s, t, ++index))
    //{
    //    return index;
   // }
   //else 
        return index;
}

, поэтому да, он всегда возвращает 1

индекс продолжает увеличиваться внутри рекурсивной функции start _ with , но измененные значения никогда не делают его возвращаемым значением.

-121--4716206-

Вы не обновляете переменную index в функции index _ функции () . При передаче в начинается _ с () создается копия в стеке. Необходимо каким-то образом вернуть обновленное значение - эфир взять его по ссылке/указателю или вернуть вместо bool .

0
ответ дан 13 December 2019 в 05:34
поделиться

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

#include <string>
#include <iostream>
using namespace std;

int starts_with(string s, string sub, unsigned int i) {
  if (i >= s.length())
    return -1;
  if (s.compare(i, sub.length(), sub) == 0)
    return i;
  return starts_with(s, sub, i + 1);
}
int index_of(string s, string sub) { return starts_with(s, sub, 0U); }
int main(void) { cout << index_of("Mississippi", "sip") << "\n"; }
0
ответ дан 13 December 2019 в 05:34
поделиться

(примечание - отредактировано для простоты)

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

Вот как вам нужно подумать об этом - рекурсивная функция содержит два основных компонента:

1) базовый случай и

2) рекурсивный случай

Ключевым моментом является то, что при каждом вызове вы определяете, какой из двух case верно для этого запуска функции (на основе входных параметров).

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


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

Если при вызове функции это не базовый случай, это рекурсивный случай. Итак, ваша функция должна выглядеть так:

function index_of(params...){
    if base case
        do something simple and return
    else
        do something and call index_of again
}

Подсказка: в вашей функции нет рекурсивного регистра.

0
ответ дан 13 December 2019 в 05:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: