使用std算法库来获得关于二元关系的唯一等价

时间:2018-03-16 08:27:30

标签: c++ algorithm c++11 std

我对函数T引起的某种类型equivalent有二元关系:

bool equivalent(T const& a, T const& b); // returns true if a and b are equivalent

它具有

的属性
equivalent(a, a) == true

equivalent(a, b) == equivalent(b, a)

适用于所有ab

对于T类型的给定元素集合,我想删除除了每个等价类的第一个出现的所有元素。我想出了下面的代码,但是在徘徊:

是否存在没有显式循环的解决方案?

std::vector<T> filter_all_but_one_for_each_set_of_equivalent_T(std::vector<T> const& ts) {
  std::vector<T> result;
  for (auto iter = ts.begin(); iter != ts.end(); ++iter) {
     auto const& elem = *iter;
     bool has_equivalent_element_at_earlier_position = std::any_of(
        ts.begin(),
        iter,
        &equivalent
     );
     if (not has_equivalent_element_at_earlier_position) {
        result.push_back(routing_pin);
     }
  }
  return result;
}

更新

据我所知,std::unique不会因为我的类型T无法排序而无法做到。因为在我的案例中我只有C ++ 11,但我也会对教育中的其他选项感兴趣。

6 个答案:

答案 0 :(得分:2)

扩展我在AndyG的回答中的评论:

template<class T, class A, class Equivalent>
auto deduplicated2(std::vector<T, A> vec, Equivalent&& equivalent) -> std::vector<T, A>
{
    auto current = std::begin(vec);

    // current 'last of retained sequence'
    auto last = std::end(vec);

    while (current != last)
    {
        // define a predicate which checks for equivalence to current
        auto same = [&](T const& x) -> bool
        {
            return equivalent(*current, x);
        };

        // move non-equivalent items to end of sequence
        // return new 'end of valid sequence'
        last = std::remove_if(std::next(current), last, same);
    }
    // erase all items beyond the 'end of valid sequence'
    vec.erase(last, std::end(vec));
    return vec;
}

请相信AndyG。

对于T可以清洗的非常大的向量,我们可以针对O(n)解决方案:

template<class T, class A, class Equivalent>
auto deduplicated(std::vector<T, A> const& vec, Equivalent&& equivalent) -> std::vector<T, A>
{
    auto seen = std::unordered_set<T, std::hash<T>, Equivalent>(vec.size(), std::hash<T>(), std::forward<Equivalent>(equivalent));

    auto result = std::vector<T, A>();
    result.resize(vec.size());

    auto current = std::begin(vec);
    while (current != std::end(vec))
    {
        if (seen.insert(*current).second)
        {
            result.push_back(*current);
        }
    }
    return result;
}

最后,重新审视第一个解决方案并重构为子问题(我无法帮助自己):

// in-place de-duplication of sequence, similar interface to remove_if
template<class Iter, class Equivalent>
Iter inplace_deduplicate_sequence(Iter first, Iter last, Equivalent&& equivalent)
{
    while (first != last)
    {
        // define a predicate which checks for equivalence to current
        using value_type = typename std::iterator_traits<Iter>::value_type;
        auto same = [&](value_type const& x) -> bool
        {
            return equivalent(*first, x);
        };

        // move non-equivalent items to end of sequence
        // return new 'end of valid sequence'
        last = std::remove_if(std::next(first), last, same);
    }
    return last;
}

// in-place de-duplication on while vector, including container truncation    
template<class T, class A, class Equivalent>
void inplace_deduplicate(std::vector<T, A>& vec, Equivalent&& equivalent)
{
    vec.erase(inplace_deduplicate_sequence(vec.begin(), 
                                           vec.end(), 
                                           std::forward<Equivalent>(equivalent)), 
              vec.end());
}

// non-destructive version   
template<class T, class A, class Equivalent>
auto deduplicated2(std::vector<T, A> vec, Equivalent&& equivalent) -> std::vector<T, A>
{
    inplace_deduplicate(vec, std::forward<Equivalent>(equivalent));
    return vec;
}

答案 1 :(得分:2)

这是一种只有一个非常简单的循环的方式:

首先定义我们的类,我将其称为A而不是T,因为T通常用于模板:

class A{
public:
    explicit A(int _i) : i(_i){};
    int get() const{return i;}
private:
    int i;
};

然后我们的equivalent函数只比较整数的相等性:

bool equivalent(A const& a, A const& b){return a.get() == b.get();}

接下来我将定义过滤功能。

这里的想法是利用library为我们有效地进行循环和擦除(它通常将元素交换到最后,这样你就不会为每次删除移动向量)。

我们首先删除与第一个元素匹配的所有内容,然后删除与第二个元素匹配的所有内容(现在保证!=第一个元素),依此类推。

std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
    for(size_t i = 1; i < as.size(); ++i){
       as.erase(std::remove_if(as.begin() + i, as.end(), [&as, i](const A& next){return equivalent(as[i-1], next);}), as.end());
    }
    return as;
}

std::remove

编辑:正如理查德·霍奇斯所说,有可能将任何删除延迟到最后。我不能让它看起来很漂亮:

std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {
    auto end = as.end();
    for(size_t i = 1; i < std::distance(as.begin(), end); ++i){
       end = std::remove_if(as.begin() + i, end, [&as, i](const A& next){return equivalent(as[i-1], next);});
    }
    as.erase(end, as.end());
    return as;
}

Demo

答案 2 :(得分:2)

你可以尝试这个。这里的技巧是在内部谓词中获取索引。

std::vector<T> output; 
std::copy_if(
    input.begin(), input.end(),
    std::back_inserter(output),
    [&](const T& x) {
        size_t index = &x - &input[0];
        return find_if(
            input.begin(), input.begin() + index, x,
            [&x](const T& y) {
                return equivalent(x, y);
            }) == input.begin() + index;
    });

答案 3 :(得分:1)

首先提出另一个循环版本,与您自己的版本相比,它将统一在一起,您可能会发现它很有趣:

std::vector<int> v({1, 7, 1, 8, 9, 8, 9, 1, 1, 7});

auto retained = v.begin();
for(auto i = v.begin(); i != v.end(); ++i)
{
    bool isFirst = true;
    for(auto j = v.begin(); j != retained; ++j)
    {
        if(*i == *j)
        {
            isFirst = false;
            break;
        }
    }

    if(isFirst)
    {
        *retained++ = *i;
    }
}
v.erase(retained, v.end());

这是使用std::remove_ifstd::find_if的版本的基础:

auto retained = v.begin();
auto c = [&v, &retained](int n)
        {
            if(std::find_if(v.begin(), retained, [n](int m) { return m == n; }) != retained)
                return true;
            // element remains, so we need to increase!!!
            ++retained;
            return false;
        };
v.erase(std::remove_if(v.begin(), v.end(), c), v.end());

在这种情况下你需要lambda,因为我们需要一个唯一谓词,而等效的(在我的int示例中由operator==表示)是二进制的...

答案 4 :(得分:1)

由于性能不是问题,您可以使用std::accumulate扫描元素并将其添加到累加器向量xs(如果还没有)  xs中的等价元素。

有了这个,你根本不需要任何手写的原始循环。

std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {       
    return std::accumulate(as.begin(), as.end(), 
                           std::vector<A>{}, [](std::vector<A> xs, A const& x) {
                               if ( std::find_if(xs.begin(), xs.end(), [x](A const& y) {return equivalent(x,y);}) == xs.end() ) {
                                   xs.push_back(x);
                               }

                               return xs;
                           });
}

使用两个辅助函数,这实际上是可读的:

bool contains_equivalent(std::vector<A> const& xs, A const& x) {
    return std::find_if(xs.begin(), xs.end(), 
                        [x](A const& y) {return equivalent(x,y);}) != xs.end();
};

std::vector<A> push_back_if(std::vector<A> xs, A const& x) {
        if ( !contains_equivalent(xs, x) ) {
            xs.push_back(x);
        }

        return xs;
    };

该功能本身只是对std::accumulate的调用:

std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> as) {       
    return std::accumulate(as.begin(), as.end(), std::vector<A>{}, push_back_if);
}

I've modified AndyG's example code with my proposed function.

如上所述,std::accumulate使用累加器变量的副本调用push_back_if,并将返回值再次移动分配给累加器。这是非常低效的,但可以通过更改push_back_if以获取引用来优化,以便就地修改向量。初始值需要作为参考包装器传递std::ref以消除剩余的副本。

std::vector<A>& push_back_if(std::vector<A>& xs, A const& x) {
        if ( !contains_equivalent(xs, x) ) {
            xs.push_back(x);
        }

        return xs;
    };

std::vector<A> filter_all_but_one_for_each_set_of_equivalent_A(std::vector<A> const& as) {       
    std::vector<A> acc;
    return std::accumulate(as.begin(), as.end(), std::ref(acc), push_back_if);
}

You can see in the example that the copy-constructor is almost completely eliminated.

答案 5 :(得分:0)

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 6
    at GameLoop$DrawBoard.paintComponent(GameLoop.java:69)
    at javax.swing.JComponent.paint(JComponent.java:1056)
    at javax.swing.JComponent.paintChildren(JComponent.java:889)
    at javax.swing.JComponent.paint(JComponent.java:1065)
    at javax.swing.JComponent.paintChildren(JComponent.java:889)
    at javax.swing.JComponent.paint(JComponent.java:1065)
    at javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
    at javax.swing.JComponent.paintChildren(JComponent.java:889)
    at javax.swing.JComponent.paintToOffscreen(JComponent.java:5217)
    at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
    at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
    at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
    at javax.swing.JComponent.paint(JComponent.java:1042)
    at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
    at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:79)
    at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:116)
    at java.awt.Container.paint(Container.java:1975)
    at java.awt.Window.paint(Window.java:3904)
    at javax.swing.RepaintManager$4.run(RepaintManager.java:842)
    at javax.swing.RepaintManager$4.run(RepaintManager.java:814)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
    at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
    at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789)
    at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738)
    at javax.swing.RepaintManager.access$1200(RepaintManager.java:64)
    at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:709)
    at java.awt.EventQueue$3.run(EventQueue.java:703)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

/**
 * Author : Matija & Zvonimir 
 * Version : 1.0
 * Purpose : Connect 4 game work. Creating working game with arrays
 *           
 *
*/
import java.util.Arrays;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.JComponent;
import java.awt.Graphics2D;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;

public class GameLoop extends JFrame{
   // attributes
   private int [][] matrix;
   int col;
   int row;
   private boolean gameOver = false;
   private int playerTurn = 0;

   Scanner scan = new Scanner(System.in);

   //constants
   private static final int COL = 7;
   private static final int ROWS = 6;

   public static final int SQUARE_SIZE = 100;
   public static final int WIDTH = COL * SQUARE_SIZE;
   public static final int HEIGHT = (ROWS+1) * SQUARE_SIZE;
   public static final int CIRCLE_WIDTH_HEIGHT = (SQUARE_SIZE) - 15;

   //MAIN METHOD
   public static void main(String []args){      

      GameLoop gl = new GameLoop();
      //gl.gameOver();

   }//main

   /*GRAPHICS*/  
   private class DrawBoard extends JPanel{      

      int[][] board = new int[ROWS][COL];

      public DrawBoard(){

         for(int i = 0; i < ROWS; i++){
            for(int j = 0; j < COL; j++){
               board[i][j] = 0;
            }         
         }//set to 0;
      }      


      public void paintComponent(Graphics g){

         super.paintComponents(g); //override  

         for(int i = 0; i < COL; i++){
            for(int j = 0; j < ROWS; j++){

               g.setColor(Color.BLUE);
               g.fillRect( (i*SQUARE_SIZE),(j * SQUARE_SIZE+SQUARE_SIZE),SQUARE_SIZE,SQUARE_SIZE);     

               if(board[i][j] == 0){
                  g.setColor(Color.BLACK);
                  g.fillOval( (i*SQUARE_SIZE),(j*SQUARE_SIZE +SQUARE_SIZE),CIRCLE_WIDTH_HEIGHT,CIRCLE_WIDTH_HEIGHT);                  

               }else if(board[i][j] == 1){

                  g.setColor(Color.RED);
                  g.fillRect( (i*SQUARE_SIZE),(j * SQUARE_SIZE+SQUARE_SIZE),SQUARE_SIZE,SQUARE_SIZE);                     

               }else if(board[i][j] == 2){ 

                  g.setColor(Color.YELLOW);
                  g.fillOval( (i*SQUARE_SIZE),(j*SQUARE_SIZE +SQUARE_SIZE),CIRCLE_WIDTH_HEIGHT,CIRCLE_WIDTH_HEIGHT);

               }
            }
         }     

         for(int i = 0; i < COL; i++){
            for(int j = 0; j < ROWS; j++){


            }//for J
         } //for I

      }//paint component method  

   }//CLASS


   public GameLoop(){            

      DrawBoard board = new DrawBoard();      

      add(board);
      setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
      setPreferredSize( new Dimension(701,732));
      //setResizable(false);
      setLocation( 400,200 );
      setVisible ( true );
      pack();    

   }

   /*
    * Method that creates a 2d array (matrix) filled with 0s
    * @param matrix 2d array
    */ 
   public int[][] createBoard(){
      matrix = new int[ROWS][COL];

      for(int i = 0; i < ROWS; i++){
         for(int j = 0; j < COL; j++){
            matrix[i][j] = 0;
         }         
      }//set to 0

      return matrix;

   }//create board       


}//class game loop