动态查找矩形的边缘

时间:2015-12-17 12:37:57

标签: c++ c 2d bitwise-operators

我有2个2D点,​​它们被塞在一起成为一个数组:int square[4]。这四个数字被解释为矩形的定义,其中水平线平行于X轴,垂直线平行于Y轴。然后,数组的元素分别定义:

  1. 左边缘的X坐标
  2. 底边的Y坐标
  3. 右边缘的X坐标
  4. 上边缘的Y坐标
  5. 我已在此enum中定义了一个绕线顺序:

    enum WindingOrder {
        BOTTOM = 0,
        RIGHT,
        TOP,
        LEFT
    };
    

    我的代码的最小,完整,可验证的示例是,我获得了输出第二个数组:int output[4]和输入WindingOrder edge。我需要按如下方式填充output

    switch(edge) {
    case BOTTOM:
        output[0] = square[0]; output[1] = square[1]; output[2] = square[2]; output[3] = square[1];
        break;
    case RIGHT:
        output[0] = square[2]; output[1] = square[1]; output[2] = square[2]; output[3] = square[3];
        break;
    case TOP:
        output[0] = square[2]; output[1] = square[3]; output[2] = square[0]; output[3] = square[3];
        break;
    case LEFT:
        output[0] = square[0]; output[1] = square[3]; output[2] = square[0]; output[3] = square[1];
        break;
    }
    

    我没有与特定的WindingOrder安排结婚,也不关心ouptut中点数的顺序,所以如果改变那些使得这个可以解决,我就失望了。我想知道的是,我是否可以构建square索引以在output循环中分配给for分配if / {{ 1}} / ternary语句(换句话说,使用逐位操作)?

    所以我想要caseint i = 0对它们进行逐位操作来查找:

    WindingOrder edge

    修改

    我收到了很多静态数组答案(我相信这是解决这个问题的最佳方法,所以我给了+1)。但作为一个逻辑问题,我很奇怪为什么很少有位操作可以动态地找到给定边的元素。因此,例如,如果给定任意do { output[i] = array[???]; } while(++i <= LEFT); edge iint getIndex(int i, int edge)

    ,应如何写入此函数的正文

9 个答案:

答案 0 :(得分:5)

这是一个不同的解决方案。它是静态数组方法的变体,但没有实际数组:索引矩阵内联为计算为常量表达式的32位无符号整数。通过单个移位选择edge参数的列,最后,通过简单的位移和屏蔽选择每个数组元素的各个索引。

这个解决方案有一些优点:

  • 很容易理解
  • 它不使用测试
  • 它不使用静态数组,也不使用任何其他内存位置
  • 它独立于绕线顺序,可以轻松定制任何阵列组件顺序
  • 它不使用C99特定语法,这可能在C ++中不可用。

这与我可以获得按位解决方案的距离非​​常接近。

#include <iostream>

enum WindingOrder { BOTTOM = 0, RIGHT, TOP, LEFT };

void BitwiseWind(int const *input, int *output, enum WindingOrder edge)
{
    unsigned bits = ((0x00010201 << BOTTOM * 2) |
                     (0x02010203 << RIGHT  * 2) |
                     (0x02030003 << TOP    * 2) |
                     (0x00030001 << LEFT   * 2))
                    >> (edge * 2);

    output[0] = input[(bits >> 24) & 3];
    output[1] = input[(bits >> 16) & 3];
    output[2] = input[(bits >>  8) & 3];
    output[3] = input[(bits >>  0) & 3];
}

int main() {
    enum WindingOrder edges[4] = { BOTTOM, RIGHT, TOP, LEFT };
    int rect[4] = { 1, 3, 4, 5 };
    int output[4];

    for (int i = 0; i < 4; i++) {
        BitwiseWind(rect, output, edges[i]);
        std::cout << output[0] << output[1] << output[2] << output[3] << std::endl;
    }
    return 0;
}

使用BitwiseWindx86-64编译clang -O3会生成21条指令,比静态数组版本多6条,但没有任何内存引用。这有点令人失望,但我希望它可以为ARM目标生成更少的指令,利用位字段提取操作码。顺便提一下,使用output[i] = array[(i+(i==winding)*2)&3];的内联版本会生成25条指令而没有任何跳转,gcc -O3会更糟糕:它会生成更多代码,包含4次测试和跳转。

下面的通用getIndex函数只编译了6个x86指令:

int getIndex(int i, int edge) {
    return (((0x00010201 << BOTTOM * 2) |
             (0x02010203 << RIGHT  * 2) |
             (0x02030003 << TOP    * 2) |
             (0x00030001 << LEFT   * 2))
            >> (edge * 2 + 24 - i * 8)) & 3;
}

答案 1 :(得分:3)

是否有特殊原因需要使用大量按位操作?这似乎是解决问题的一种复杂方法吗?

您似乎非常担心速度,例如,您不想使用模数,因为它很昂贵。在这种情况下,为什么不只是使用一个非常简单的查找并展开循环? Example on ideone as well.

编辑:感谢 chqrlie 输入。相应地更新了答案。

#include <iostream>

using namespace std;

enum WindingOrder {
    BOTTOM = 0,
    RIGHT,
    TOP,
    LEFT
};

void DoWinding1(unsigned int const *const in, unsigned int *const out, const enum WindingOrder ord)
{
    static const unsigned int order[4][4] = { [BOTTOM] = {0,1,2,1},
                                              [RIGHT]  = {2,1,2,3},
                                              [TOP]    = {2,3,0,3},
                                              [LEFT]   = {0,3,0,1} };
    out[0] = in[order[ord][0]]; 
    out[1] = in[order[ord][1]];
    out[2] = in[order[ord][2]];
    out[3] = in[order[ord][3]];
}


int main() {
    unsigned int idx;
    unsigned int rect[4] = {1, 3, 4, 5};
    unsigned int out[4] = {0};

    DoWinding1(rect, out, BOTTOM);

    std::cout << out[0] << out[1] << out[2] << out[3] << std::endl;

    return 0;
}

答案 2 :(得分:2)

这是未经测试的,某些细节可能会有一个小错误,但一般的想法应该有效。

将数组复制到输出将使用索引{0,1,2,3}。要获得特定边缘,您必须对索引进行一些转换:

                    changed_pos  changed_to
RIGHT : {2,1,2,3}       0           2
TOP   : {0,3,2,3}       1           3
LEFT  : {0,1,0,3}       2           0
BOTTOM: {0,1,2,1}       3           1

所以基本上你必须为你的绕组的特定位置添加2 mod 4。 所以(就像我说未经测试的)剪断可能看起来像这样

for (size_t i=0; i<4; ++i) {
    output[i] = array[(i+(i==edge)*2)%4];
}

如果比较结果为真,则向索引添加1*2=2,否则为0*2=0,并mod 4保持在该范围内。

你的enum必须看起来像这样(但我想你自己想出来了):

enum WindingOrder {
    RIGHT,
    TOP,
    LEFT,
    BOTTOM
};

MWE

#include <iostream>
#include <string>
#include <vector>

enum WindingOrder {
    RIGHT=0,
    TOP,
    LEFT,
    BOTTOM
};

int main()
{
    std::vector<int> array = {2,4,8,9};
    std::vector<int> output(4);

    std::vector<WindingOrder> test = {LEFT,RIGHT,BOTTOM,TOP};
    for (auto winding : test) {
        for (size_t i=0; i<4; ++i) {
            output[i] = array[(i+(i==winding)*2)%4];
        }
        std::cout << "winding " << winding << ": " << output[0] << output[1] << output[2] << output[3] << std::endl;
    }
}

答案 3 :(得分:2)

可以重新定义WindingOrder的值集吗?如果可能的话,这是我的解决方案,它尝试在WindingOrder的值集中编码选择索引,然后只需通过移位和屏蔽解码input[]的选择索引,只要output[]索引迭代。

[感谢 chqrlie 提供代码库]:

    #include <iostream>

enum WindingOrder {
    // the RIGHT most 4-bits indicate the selection index from input[] to output[0]
    // the LEFT most 4-bits indicate the selection index from input[] to output[3]
    BOTTOM = 0x1210,
    RIGHT = 0x3212,
    TOP = 0x3230,
    LEFT = 0x3010
};

void BitwiseWind(int const *input, int *output, unsigned short edge)
{
    for (size_t i = 0; i < 4; i++)
        output[i] = input[(edge >> (i*4)) & 0x000F];    // decode
}

int main() {
    enum WindingOrder edges[4] = { BOTTOM, RIGHT, TOP, LEFT };
    int rect[4] = { 1, 3, 4, 5 };
    int output[4];

    for (int i = 0; i < 4; i++) {
        BitwiseWind(rect, output, edges[i]);
        std::cout << output[0] << output[1] << output[2] << output[3] << std::endl;
    }
    return 0;
}

通用getIndex(int i,枚举WindingOrder边缘)将是:

int getIndex(int i,enum WindingOrder edge)
{
   return ((edge >> (i*4)) & 0x000F);
}

我没有计算它使用了多少指令,但我相信它会很安静。并且很容易想象它是如何工作的。 :)

答案 4 :(得分:1)

从你自己的答案来看,你已经接近解决方案了。我认为你需要的是Karnaugh map,这是大多数布尔代数问题的通用方法。

假设

  

然后,数组的元素分别定义:

input[0]: Left edge's X coordinate
input[0]: Bottom edge's Y coordinate
input[0]: Right edge's X coordinate
input[0]: Top edge's Y coordinate
     

我已在此枚举中定义了一个绕线顺序:

enum WindingOrder {
    BOTTOM = 0,
    RIGHT,
    TOP,
    LEFT
};

因为for循环可能看起来像

for (int k = 0; k != 4; ++k) {
    int i = getIndex(k, edge); // calculate i from k and edge
    output[k] = square[i];
}

然后输入为koutput[k])和edge,输出为isquare[i])。由于i有2位,因此需要两个逻辑函数。

在这里,我们使用P = F1(A, B, C, D)Q = F2(A, B, C, D)来表示逻辑函数,其中ABCD,{ {1}}和P都是单位,

Q

那么我们需要做的就是从给定的条件中推导出两个逻辑函数k = (A << 1) + B; edge = (C << 1) + D; i = (P << 1) + Q; F1

从您提供的switch case语句中,我们可以轻松获得真值表。

F2

然后将其分为两个真值表,分别为k\edge 0 1 3 2 0 0 2 0 2 1 1 1 3 3 3 1 3 1 3 2 2 2 0 0 P

Q

这些是我在开头提到的卡诺图。我们可以轻松获得这些功能。

P   edge    0   1   3   2
k   AB\CD   00  01  11  10
0      00   0   1   0   1
1      01   0   0   1   1
3      11   0   1   0   1
2      10   1   1   0   0

Q   edge    0   1   3   2
k   AB\CD   00  01  11  10
0      00   0   0   0   0
1      01   1   1   1   1
3      11   1   1   1   1
2      10   0   0   0   0

然后程序将

F1(A, B, C, D) = A~B~C + A~CD + ~B~CD + ~ABC + ~AC~D + BC~D
F2(A, B, C, D) = B

通过了考试here。当然,您可以使用XOR简化功能。

修改

int getIndex(int k, int edge) { int A = (k >> 1) & 1; int B = k & 1; int C = (edge >> 1) & 1; int D = edge & 1; int P = A&~B&~C | A&~C&D | ~B&~C&D | ~A&B&C | ~A&C&~D | B&C&~D; int Q = B; return (P << 1) + Q; } 开始,大部分时间都可以使用XOR来简化表达式。但这可能不是你想要的东西。首先,我认为Sum of Products(SoP)表达式和更简化的XOR版本之间的性能差异很小。其次,没有通用的方法(据我所知)用XOR简化表达式,所以你必须依靠自己的经验来完成这项工作。

两个变量有16种可能的逻辑功能,但在数字逻辑硬件中,最简单的门电路只实现其中的四种:AND,OR,以及那些(NAND和NOR)的补充。卡诺图用于简化实际逻辑要求,以便使用最少数量的物理逻辑门实现它们。

此处使用了两个常用表达式,产品总和 Sums产品表达式。这两个表达式可以使用 AND和OR逻辑运算符直接实现。它们可以直接用卡诺图来推断出来。

答案 5 :(得分:1)

如果您从左侧开始按顺时针顺序定义坐标和方向,

#define  LEFT   0
#define  TOP    1
#define  RIGHT  2
#define  BOTTOM 3

你可以使用

void edge_line(int line[4], const int rect[4], const int edge)
{
    line[0] = rect[   edge      & 2      ];
    line[1] = rect[ ((edge + 3) & 2) + 1 ];
    line[2] = rect[ ((edge + 1) & 2)     ];
    line[3] = rect[  (edge      & 2) + 1 ];
}

复制边线坐标(每个线段按顺时针顺序排列)。它看起来不是最理想的,但使用-O2,GCC-4.8,你基本上得到了

edge_line:
        pushl   %esi
        pushl   %ebx
        movl    20(%esp), %ecx
        movl    16(%esp), %edx
        movl    12(%esp), %eax
        movl    %ecx, %esi
        andl    $2, %esi
        movl    (%edx,%esi,4), %ebx
        movl    %ebx, (%eax)
        leal    3(%ecx), %ebx
        addl    $1, %ecx
        andl    $2, %ebx
        andl    $2, %ecx
        addl    $1, %ebx
        movl    (%edx,%ebx,4), %ebx
        movl    %ebx, 4(%eax)
        movl    (%edx,%ecx,4), %ecx
        movl    %ecx, 8(%eax)
        movl    4(%edx,%esi,4), %edx
        movl    %edx, 12(%eax)
        popl    %ebx
        popl    %esi
        ret

但是在64位上,甚至更好

edge_line:
        movl    %edx, %ecx
        andl    $2, %ecx
        movslq  %ecx, %rcx
        movl    (%rsi,%rcx,4), %eax
        movl    %eax, (%rdi)
        leal    3(%rdx), %eax
        addl    $1, %edx
        andl    $2, %edx
        andl    $2, %eax
        movslq  %edx, %rdx
        cltq
        movl    4(%rsi,%rax,4), %eax
        movl    %eax, 4(%rdi)
        movl    (%rsi,%rdx,4), %eax
        movl    %eax, 8(%rdi)
        movl    4(%rsi,%rcx,4), %eax
        movl    %eax, 12(%rdi)
        ret

正如您所看到的,没有条件,二元运算符结合并优化到非常少的指令。

编辑添加:

如果我们定义一个getIndex(i, edge)函数,使用三个二进制AND,一个位移(右一个),三个加法和一个减法,

int getIndex(const int i, const int edge)
{
    return (i & 1) + ((edge + 4 - (i & 1) + (i >> 1)) & 2);
}

可以将edge_line()实现为

void edge_line(int line[4], const int rect[4], const int edge)
{
    line[0] = rect[ getIndex(0, edge) ];
    line[1] = rect[ getIndex(1, edge) ];
    line[2] = rect[ getIndex(2, edge) ];
    line[3] = rect[ getIndex(3, edge) ];
}

我们得到与以前完全相同的结果。在AMD64 / x86-64上使用GCC-4.8.4和-O2编译为

getIndex:
        movl    %edi, %edx
        sarl    %edi
        andl    $1, %edx
        subl    %edx, %esi
        leal    4(%rsi,%rdi), %eax
        andl    $2, %eax
        addl    %edx, %eax
        ret

getIndex:
        movl    4(%esp), %eax
        movl    8(%esp), %edx
        movl    %eax, %ecx
        andl    $1, %ecx
        subl    %ecx, %edx
        sarl    %eax
        leal    4(%edx,%eax), %eax
        andl    $2, %eax
        addl    %ecx, %eax
        ret
在i686上。请注意,我使用四乘四结果表到达上面的表格;还有其他更严格的方法来构建它,甚至可能有更优化的形式。因此,我认真地建议在函数上方添加一个巨大的注释,解释意图,最好还显示结果表。像

这样的东西
/* This function returns an array index:
 *    0  for left
 *    1  for top
 *    2  for right
 *    3  for bottom
 * given edge:
 *    0  for left
 *    1  for top
 *    2  for right
 *    3  for bottom
 * and i:
 *    0  for initial x
 *    1  for initial y
 *    2  for final x
 *    3  for final y
 *
 * The result table is
 *     |  edge
 *     | 0 1 2 3
 * ----+-------
 * i=0 | 0 0 2 2
 * i=1 | 3 1 1 3
 * i=2 | 0 2 2 0
 * i=3 | 1 1 3 3
 *
 * Apologies for the write-only code.
*/

或类似的东西。

答案 6 :(得分:0)

让我们调用我们的目标变量用于索引squaredint index

现在,我们将为indexedge创建一个所需i的表格,并在整行中edgei向下柱:

 ║0│1│2│3
═╬═╪═╪═╪═
0║0│1│2│1
─╫─┼─┼─┼─
1║2│1│2│3
─╫─┼─┼─┼─
2║2│3│0│3
─╫─┼─┼─┼─
3║0│3│0│1

由此可见,index的最低有效位对于奇数i来说总是奇数,甚至对于偶数i也是如此。因此,如果我们能够找到index的最重要部分,那么我们只能使用i & 1,或者我们index。因此,让我们为同一indexedge表格制作另一个仅有i位的表格:

 ║0│1│2│3
═╬═╪═╪═╪═
0║0│0│1│0
─╫─┼─┼─┼─
1║1│0│1│1
─╫─┼─┼─┼─
2║1│1│0│1
─╫─┼─┼─┼─
3║0│1│0│0

我们可以在这里看到几件事:

  1. i03时,列相同,仅取决于edge
    • edge12
    • 时会设置这些列
  2. i12时,列彼此相反
    • 仅在edge最高位或仅i的最高位设置时设置这些列
  3. 首先让我们将edgei分为最不重要和最重要的位:

    const int ib0 = i & 1;
    const int ib1 = (i & 2) >> 1;
    const int eb0 = edge & 1;
    const int eb1 = (edge & 2) >> 1;
    

    从这里我们可以轻松找到i0还是3

    const int iXor = ib0 ^ ib1;
    

    对于0 / 3条件:

    const int iXorCondition = ib1 ^ eb1;
    

    1 / 2条件:

    const int iNXorCondition = eb0 ^ eb1;
    

    现在我们只需要将这些内容与各自的iXor合并,并放回index最不重要的位:

    const int index = ((iNXorCondition & ~iXor | iXorCondition & iXor) << 1) | ib0;
    

    将这些全部放在一个方便的功能中我们得到:

    int getIndex(int i, int edge) {
        const int ib0 = i & 1;
        const int ib1 = (i & 2) >> 1;
        const int eb0 = edge & 1;
        const int eb1 = (edge & 2) >> 1;
    
        const int iXor = ib0 ^ ib1;
    
        const int iNXorCondition = eb0 ^ eb1;
        const int iXorCondition = ib1 ^ eb1;
    
        return ((iNXorCondition & ~iXor | iXorCondition & iXor) << 1) | ib0;
    }
    

    我已经写了一个检查实例here

答案 7 :(得分:0)

  

我想知道的是,我是否可以构造方形索引以在for循环中分配输出,而不使用if / case / ternary语句(换句话说使用逐位操作)?

我会问你在做这件事时期望达到的目标吗?

我的观点是switch-case构造通常会被编译器的优化代码完全重组。最好的,IMO,让代码单独留下并让编译器这样做。

只有两个条件,其中Id更改了该视图;

  • 您正在使用OpenCL(而不是C)编写,并希望优化代码,其中决策分支逻辑可能会对性能造成问题。

  • 您想使用显式编码进行SIMD矢量化。有一些可能帮助的特殊操作,但它是一个编码选项,可以锁定在没有SIMD指令集的情况下在硬件上可能无法正常工作的事情(或者在不同的硬件上执行方式完全不同) 。值得注意的是,一些编译器可以使用正确的编码进行自动矢量化。

除了switch-case之外,我认为用这些操作编码这些操作很少或没有优势。

答案 8 :(得分:0)

这是实现这一目标的一种方式:

function popWindow() {
   // Fixes dual-screen position Most browsers Firefox

   var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left;
   var dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top;
   width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
   height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
   var left = width - 0.1;
   var top = height - 0.1;
   var newWindow = window.open('HtmlPage1.html', 'SessionKill', 'scrollbars=0,toolbar=0,menubar=0,directories=0,titlebar=no,addressbar=0,status=0,resizable=0,location=0,directories=0, width=10, height=10, top=' + top + ', left=' + left, "_blank");
   newWindow.blur();
   window.focus();

}

为了帮助您理解我缩进代码,您显然可以删除所有空格。我已经把一个标签放在哪里,我想分开两个案例。顺便说一下,你看到计算分为两个部分,分别是两个不同的对称情况,但我用不同的算法解决了每个情况,这样你就可以看到各种方法来实现。