WedX - журнал о программировании и компьютерных науках

Объяснение функции обрезки строки

Я наткнулся на приведенный ниже код, но мне нужна помощь в понимании кода. Предположим, что строка s имеет пробелы с обеих сторон.

string trim(string const& s){
   auto front = find_if_not(begin(s), end(s), isspace);
   auto back = find_if_not(rbegin(s), rend(s), isspace);
   return string { front, back.base() };
}

Автор заявил, что обратная сторона указывает на конец последнего пробела, тогда как передняя часть указывает на первый непробельный символ. Итак, был вызван back.base (), но я не понимаю почему.

Также что означают фигурные скобки, следующие за строкой в ​​операторе возврата?

26.06.2014

  • Если не считать профилирования всего этого, это на самом деле выглядит как аккуратный фрагмент кода. 26.06.2014
  • Этот код выйдет из строя при вводе, состоящем только из одного или нескольких пробелов, потому что итераторы будут пересекаться. 26.06.2014
  • @j_random_hacker Я согласен. ему нужен предохранительный клапан, чтобы гарантировать, что back.base() больше, чем front, иначе просто верните пустую строку. Идея мне все еще нравится в принципе. Я думаю, что это действительно вызовет исключение длины, потому что результат last - first будет отрицательным. 26.06.2014

Ответы:


1

Фигурные скобки - это новая инициализация C ++ 11.

.base() и обратные итераторы

.base() - это вернуть базовый итератор (back - это reverse_iterator), чтобы правильно построить новую строку из допустимого диапазона.

Картинка. Нормальные позиции итератора в строке (это немного сложнее в отношении того, как работает rend(), но в любом случае концептуально ...)

        begin                                 end
          v                                    v
        -------------------------------------
        | sp | sp | A | B | C | D | sp | sp |
        -------------------------------------
      ^                                   ^
    rend                                rbegin

Как только ваши два цикла поиска закончатся, результат этих итераторов в этой последовательности будет расположен по адресу:

                  front
                    v
        -------------------------------------
        | sp | sp | A | B | C | D | sp | sp |
        -------------------------------------
                                ^
                              back

Если бы мы взяли только эти итераторы и построили из них последовательность (чего мы не можем, поскольку они не соответствуют типам, но независимо от того, что мы предполагали), результатом было бы «копирование, начиная с A, останавливаясь на D». но он не будет включать D в итоговые данные.

Введите back() член обратного итератора. Он возвращает необратный итератор класса прямого итератора, который располагается в элементе «рядом с» обратным итератором; т.е.

                  front
                    v
        -------------------------------------
        | sp | sp | A | B | C | D | sp | sp |
        -------------------------------------
                                    ^
                               back.base()

Теперь, когда мы копируем наш диапазон { front, back.base() }, мы копируем, начиная с A и останавливаясь на первом пробеле (но не включая его), тем самым включая букву D, которую мы бы пропустили.

Кстати, это действительно небольшой кусочек кода.

Дополнительная проверка

В исходный код добавлены базовые проверки.

Пытаясь сохранить дух исходного кода (использование C ++ 1y / C ++ 14), добавляя некоторые базовые проверки для пустых строк и строк, содержащих только пробелы;

string trim_check(string const& s)
{
  auto is_space = [](char c) { return isspace(c, locale()); };
  auto front = find_if_not(begin(s), end(s), is_space);
  auto back = find_if_not(rbegin(s), make_reverse_iterator(front), is_space);
  return string { front, back.base() };
}
26.06.2014
  • Стоит отметить, что итератор base() ссылается на элемент рядом с, на который ссылается обратный итератор. В данном случае это в некоторой степени синоним std::next(back), но не в обратном направлении, а в прямом направлении базовой последовательности. 26.06.2014
  • @WhozCraig Да. В качестве дополнительной ссылки en.cppreference.com/w/cpp/iterator/reverse_iterator содержит красивую статью о reverse_iterator. 26.06.2014
  • Спасибо за ваши комментарии, но почему передняя часть указывает на первый непробельный символ, а обратная не указывает на последний непробельный символ? 26.06.2014
  • @Smithy back действительно ссылается на последний непробельный символ. Но если вы включите это как end итератор копии (или, в вашем случае, конструктор диапазона итераторов), это позиция остановки, и вы на один слот короче (и в любом случае это неправильный итератор типа). Вы не хотите останавливаться на достигнутом, вы хотите остановить один слот после этой позиции. Думайте об этом аналогично тому, как end() в обычных последовательностях итераторов ссылается на один последний элемент. Помните, что в C ++ конечные точки итератора означают остановку, когда вы попадаете сюда, а не остановку, когда вы проходите здесь. Надеюсь, это имело смысл. 26.06.2014
  • Мой ascii-art довольно неубедительный, но я надеюсь, что это приводит к изображению этого ответа (= 1 кстати). 26.06.2014
  • @WhozCraig, спасибо за иллюстрацию. Чем больше я смотрю на него, тем больше соглашаюсь, это довольно аккуратный фрагмент кода. 26.06.2014
  • @WhozCraig Для таких правок должен быть значок. +3 к вашим случайным ответам (и +1 здесь) 26.06.2014
  • @WhozCraig Спасибо за четкое объяснение. Теперь я понимаю. Спасибо всем, кто помогал. 26.06.2014
  • Чем лямбда лучше прямого вызова :: isspace? 08.07.2014
  • @Chnossos Честно говоря, на практике разница невелика, часто так и делается. Теория относится к преобразованию из char в int для int isspace(int ch), которое требует, чтобы ch был представлен как unsigned char en.cppreference.com/w/c/string/byte/isspace. Лямбда избегает преобразования (которое технически может вызывать неопределенное поведение, хотя я видел, как этот UB аргументировался очень подробно), а лямбда использует более приятный C ++ isspace(char, locale). Лично я предпочитаю использовать возможности C ++ вместо тех, которые существуют на C, но это только личное. 08.07.2014
  • @Niall Понятно. Нет ли способа устранить неоднозначность напрямую (поскольку тот, у которого есть локаль, кажется, является шаблоном)? Также обратите внимание, что этот код не компилируется как есть. rbegin(), например, не существует. 08.07.2014
  • @Chnossos Если я понимаю вопрос, да, есть. Компилятор определяет тип шаблона как char из предоставленного аргумента, поэтому вызов функции становится isspace(char,locale). C ++ isspace с локалью требует, чтобы locale был привязан к вызову, поэтому нельзя использовать простой адрес, это можно сделать с помощью лямбда; bind или одно из связывателей C ++ 98 (bind2nd и т. Д.). Хороший момент, rbegin - это средство библиотеки, которое будет введено в C ++ 14, некоторые библиотеки уже реализуют его. Поскольку он был в исходном коде, я сохранил его в образце. 08.07.2014
  • Зачем нужен <decltype(front)>? 07.08.2014
  • @Yakk, просто чтобы получить тип для шаблона обратного итератора, вы можете ввести его, но, поскольку использовалось auto, я также использовал decltype. 07.08.2014
  • Новые материалы

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

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

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..


    Для любых предложений по сайту: [email protected]