Шаблонный палиндром

Sep 07 2020

Я пытаюсь реализовать проверку палиндрома для каждого элемента в векторе и вернуть вектор только с палиндромами. Это должно работать для другого типа .eg strings int vector of ints. Я сделал шаблонное решение, но мне кажется, что оно не использует все преимущества шаблонов.

//main.cpp
  // Checking palindrome with integers
  Palindrome <int>pal = {125125, 4947, 74347, 11};
  pal.FindPalindromeDataset();
  pal.Print();

  // Checking palindrome with strings
  Palindrome <std::string>pal1  = {"yay", "world", "level", "hello"};
  pal1.FindPalindromeDataset();
  pal1.Print();

  // Checking palindrome with vector of ints
  Palindrome<std::vector<int> > pal3 = {{6, 2, 2, 6},
                                        {1, 2, 2},
                                        {1, 4, 6, 3, 5, 3, 6, 4, 1},
                                        {5, 2, 2, 6, 9, 1, 2}};
    

//palindrome.hpp
#ifndef PALINDROME_HHP
#define PALINDROME_HHP

#include <vector>
#include <iostream>

template <class T>
class Palindrome {
public:
  //! Construct from a std::initializer list 
  Palindrome(std::initializer_list<T> _dataset) : dataset(_dataset)
  {}

  //! Print the palindromeDataset
  void Print() const;
  
  /* Traverse to check if each element of the vector is a palindrome
   * and push them in the new array
   */
  void FindPalindromeDataset()
  {
    for (auto i : dataset)
    {
      if (IsPalindrome(i))
      {
        palindromeDataset.push_back(i);
      }
    }
  }
  
private:
  //! Is the element of the vector palindrome
  bool IsPalindrome(const T& s) const;

  //! Initial dataset
  std::vector<T> dataset;
  
  //! Dataset after palindrome check
  std::vector<T> palindromeDataset;
};

#endif
#include "palindrome.hpp"
#include "iostream"
#include "string"

template <>
void Palindrome<int>::Print() const
{
  std::cout << "{";
  for (auto iter = palindromeDataset.begin(); iter != palindromeDataset.end();)
  {
    std::cout << *iter;
    if (++iter != palindromeDataset.end())
    {
      std::cout << ", ";
    }
  }
  std::cout << "}"<<std::endl;
}

template <>
void Palindrome<std::string>::Print() const
{
  std::cout << "{";
  for (auto iter = palindromeDataset.begin(); iter != palindromeDataset.end();)
  {
    std::cout << *iter;
    if (++iter != palindromeDataset.end())
    {
      std::cout << ", ";
    }
  }
  std::cout << "}"<<std::endl;
}

template <>
void Palindrome<std::vector<int>>::Print() const
{
  std::cout << "{";
  for (auto iter1 = palindromeDataset.begin(); iter1 != palindromeDataset.end();)
  {
    std::cout << "{";
    for (auto iter2 = iter1->begin(); iter2 != iter1->end();)
    {
      std::cout << *iter2;
      if (++iter2 != iter1->end())
      {
        std::cout << ", ";
      }
    }
    std::cout << "}";
    if (++iter1 != palindromeDataset.end())
    {
      std::cout << ", ";
    }
  }
  std::cout << "}"<<std::endl;
}

template <>
bool Palindrome<int>::IsPalindrome(const int& s) const
{
  int x = s;
  long int rev = 0;
  if (x<0)
  {
    return false;
  }
  
  while (x!=0)
  {
    rev= rev*10+(x%10);
    x=x/10;
  }
  return s == rev;
}

template <>
bool Palindrome<std::string>::IsPalindrome(const std::string& s) const
{
  const size_t len = s.size();
  if (!len)
  {
    return true;
  }
  
  size_t l = 0;
  size_t r = len - 1;
  while (l < r)
  {
    if (s[l] != s[r])
    {
      return false;
    }
    ++l;
    --r;
  }
  return true;
}

template <>
bool Palindrome<std::vector<int>>::IsPalindrome(const std::vector<int>& s) const
{
  const size_t len = s.size();
  if (!len)
  {
    return true;
  }
  
  size_t l = 0;
  size_t r = len - 1;
  while (l < r)
  {
    if(s[l] != s[r])
    {
      return false;
    }
    ++l;
    --r;
  }
  return true;
}

Ответы

7 Quuxplusone Sep 07 2020 at 05:10

Во-первых: это похоже на приложение «Антипаттерна ОО» . Я не понимаю, зачем тебе class Palindromeвообще нужно ; и если вы должны сохранить его, вы, конечно, не должны хранить весь набор данных - просто обработайте его один раз в конструкторе и сохраните палиндромы!

Точно так же Palindrome<T>::Print()кажется, что его следует обобщить, чтобы «напечатать эту вещь , чем бы она ни была»; эта операция не имеет ничего общего с палиндромами и может быть разделена на отдельную функцию полезности.

Итак, мы остались с этим:

template<class T>
std::vector<T> keep_only_palindromes(std::vector<T> dataset) {
    std::erase_if(dataset, [](auto&& elt) {
        return !is_palindromic(elt);
    });
    return dataset;
}

template<class T>
class PrintableVector {
    const std::vector<T> *v_;
public:
    explicit PrintableVector(const std::vector<T>& v) : v_(&v) {}
    friend std::ostream& operator<<(std::ostream& os, const PrintableVector& me) {
        os << "{ ";
        for (auto&& elt : *me.v_) os << elt << ", ";
        os << "}";
        return os;
    }
};

Тогда мы могли бы переписать ваши тестовые примеры как:

int main() {
    auto pal = keep_only_palindromes(
        std::vector<int>{125125, 4947, 74347, 11}
    );
    std::cout << PrintableVector(pal) << "\n";

    auto pal1 = keep_only_palindromes(
       std::vector<std::string>{"yay", "world", "level", "hello"}
    );
    std::cout << PrintableVector(pal1) << "\n";

    std::vector<std::vector<int>> pal2_data = {
        {6, 2, 2, 6},
        {1, 2, 2},
        {1, 4, 6, 3, 5, 3, 6, 4, 1},
        {5, 2, 2, 6, 9, 1, 2}
    };
    auto pal2 = keep_only_palindromes(pal2_data);
    std::cout << PrintableVector(pal2) << "\n";
}

Кстати, очень приятно, что вы написали тест-кейсы! Очень немногие делают. Ваши тестовые примеры полезны, потому что они показывают, как вы намереваетесь использовать класс - и позволяют мне показать, как я собираюсь использовать свою перезапись!

Я заметил , что вы не проверить любые угловые случаи, такие как 1, 0, -1, "", {42}, или {}. Это не так уж и здорово.


Ваш IsPalindromeдля чего-либо итеративного будет точно таким же. Так что предпочитаю писать что-то вроде

template<class T>
auto is_palindromic(const T& seq)
    -> decltype(std::begin(seq), std::rbegin(seq), true)
{
    return std::equal(
        std::begin(seq), std::end(seq),
        std::rbegin(seq), std::rend(seq)
    );
}

Здесь я использую возвращаемый тип SFINAE, чтобы сказать, что этот шаблон следует рассматривать для создания экземпляра, только когда выражение std::begin(seq), std::rbegin(seq), trueправильно сформировано. В C ++ 20 вы могли бы лучше передать намерение примерно так:

template<class T>
concept sequence = requires (const T& seq) {
    seq.begin(); seq.rbegin();
};

template<class T> requires sequence<T>  // !!
bool is_palindromic(const T& seq) {
    return std::equal(
        std::begin(seq), std::end(seq),
        std::rbegin(seq), std::rend(seq)
    );
}

В любом случае вам все равно придется написать другую перегрузку

bool is_palindromic(int x)

рукой.

Версия этого кода с немного меньшим количеством размахивания руками и более загадочным синтаксисом находится по адресу https://godbolt.org/z/aqPfGx - может быть интересно взглянуть, даже если какой-то загадочный синтаксис пугает (и, честно говоря, не нужен - если бы я собирался распечатать вектор векторов на C ++, «я бы не стал начинать отсюда»).

2 bipll Sep 07 2020 at 02:00

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

II. Код Printявляется абсолютно идентичен между Интсами и строками, и для векторов, отличается только в печати-ан-части элемента.

III. Сам по себе палиндромный фильтр мог бы быть более полезным, если бы он был реализован просто как функция, работающая с диапазонами, подобными тем, которые определены в <algorithm>; или , по крайней мере , если он реализован идиоматическое iterator/ begin/ endинтерфейс. std::remove_ifс IsPalindromeпредикатом as было бы хорошим началом.

IV. А сам конструктор (в том виде, в котором он написан) можно создать по шаблону, принимая произвольный пакет аргументов и пересылая его в datasetctor.