템플릿 회문

Sep 07 2020

벡터의 각 요소에 대해 회문 검사를 구현하고 회문 만있는 벡터를 반환하려고합니다. 이것은 다른 유형의 .eg strings int vector of int. 템플릿 솔루션을 수행했지만 템플릿을 최대한 활용하지 못하는 것 같습니다.

//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

첫째 : 이것은 "OO Antipattern" 의 응용 프로그램처럼 보입니다 . 왜 당신이 필요한지 모르겠습니다 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_ifIsPalindrome술어가 좋은 시작이 될 것이라고한다.

IV. 그리고 생성자 자체 (작성된대로)를 템플릿 화하여 임의의 인수 팩을 받아 datasetctor 에게 전달할 수 있습니다.