5

This question is on code for C++ builder 6. The bounty is interested in a standard C++ algorithm to solve the problem given a standardized input (see this for more information.)

The castle

The txt file which also represents the data I have in an array:

1101 0110 1101 0110 1100 0101 0110
1110 1001 0110 1011 1010 1111 1010
1000 0101 0011 1110 1011 1110 1010
1011 1101 0101 0001 0101 0011 1011

Explanation of the txt:
The numbers from the txt file are a 4-bit representation of the walls to a room, with a set bit representing a wall. The wall bits are in clockwise order starting with the most significant bit being the West wall. For example, 1101 represents a room where:

  • The set bit in the most significant position indicates a wall to the West
  • The set bit in the next most significant position indicates a wall to the North
  • The unset bit indicates no wall to the East
  • The set bit in the least significant position indicates a wall to the South

Given:

  1. The exterior walls of rooms will always have a wall
  2. Interior walls will always be expressed in both rooms (in the example since the room at (1, 1) is 1101 it contains a wall to the South, the room at (1, 2) 1110 must contain a wall to the North
  3. There will never be more than numeric_limits<int>::max() rooms

I was asked to post my code so here it is:
I've tried to solve this but I'm getting an EAAccessviolation can someone tell me what I'm doing wrong?

  int rn=0,z=0, global=0,coord[15],c[411],b1[411];

void peruse ( int i, int j,int* bb)
{
bool top=false,bottom=false,right=false,left=false;
//truth checks

if (bb[i*m+j]<1000)  left=true;

if (bb[i*m+j]<100)   top=true; else if (bb[i*m+j]-1000<100)   top=true;

if (bb[i*m+j]<10)    right=true; else
if ( (bb[i*m+j]-100<10) || (bb[i*m+j]-1000<10) || (bb[i*m+j]-100<10) ) right=true;

if (bb[i*m+j]<1)   bottom=true; else
if ( (bb[i*m+j]-10<1) || (bb[i*m+j]-100<1) || (bb[i*m+j]-1000<1) ||(bb[i*m+j]-100<1))
bottom=true;
//marc

if  (left)
{
c[i*m+j]=c[i*m+j]+1000; // EAaccessViolation i dont know why.....
peruse(i,j-1,c);
}
if (top)
{
c[i*m+j]=c[i*m+j]+100;
peruse(i-1,j,c);
}
if (right)
{
c[i*m+j]=c[i*m+j]+10;
peruse(i,j+1,c);
}
if (bottom)
{
c[i*m+j]=c[i*m+j]+1;
peruse(i+1,i,c);
}
 if ( !(left) && !(top) && !(right) && !(bottom) )
 {
  bb[411]++;



 }
}


void __fastcall TForm1::Button7Click(TObject *Sender)
{
b1[411]=0;

 for(int i=0;i<n;i++)
    for (int j=0;j<m;j++)
          {
           b1[i*m+j]=b[i][j];
           c[i*m+j]=b[i][j];
          }
  peruse (1,1,b1);

 ShowMessage("Nr. "+IntToStr(b1[411]) );
}
Community
  • 1
  • 1
  • The thing i need is an algorithm in C++,as long as i udnerstand how to make it i can move on with the assignment,thank you in advance. – Nikita Cebotari Jun 16 '16 at 19:21
  • 3
    "_Write me the code please_" – Khalil Khalaf Jun 16 '16 at 19:32
  • @FirstStep I have to say this is a fascinating problem. I really want to come up with a solution but the truth of the matter is I couldn't justify posting it here even if I did as the OP has put no effort into solving this. OP, please give us some code and tell us where you're having problems so I can take a shot at this. – Jonathan Mee Jun 16 '16 at 19:34
  • 1
    Just give me an idea please,or an example if possible.Thanks. – Nikita Cebotari Jun 16 '16 at 19:34
  • 1
    @JonathanMee I see. See Nikita what Jon is saying? You have a chance to give it a try and post "_**Your Code**_" here along with _Why_ and _When_ you are failing and _What_ are you expecting, and we will help you out. If you don't do it yourself, no one will do it for you – Khalil Khalaf Jun 16 '16 at 19:37
  • 1
    Ok,ill try the thing with checking for zeroes untill it hits ones and tronsforms those ones into zeroes,but its an algorithm used more for the sea battle thing so im not sure if it will work here. Thanks. – Nikita Cebotari Jun 16 '16 at 19:39
  • 1
    @NikitaCebotari You should read about Disjoint Set data structure. That might help. – Shubham Jun 16 '16 at 19:44
  • Need help with the last party where i try to call the function using a loop.Its commented.It seems like whenever i call the cuntion with anything but 0,0, i get an EAAccessviolation as though my array is out of bounds,but i clearly defined it as a c[25][25] globally so i do not udnerstand the problem. – Nikita Cebotari Jun 16 '16 at 23:16
  • In case anyone is wondering about the compiler, it's at C++98 compliance level – M.M Jun 16 '16 at 23:39
  • 1
    @FirstStep Arg I got suckered into solving this and now all I can do is plot improvements on my solution. – Jonathan Mee Jun 17 '16 at 01:46
  • 1
    **DEBUG** ... Use CodeGuard (I think BCB6 IDE has it implemented too like BDS2006 just enable it in project options) It will show you exactly where and why the problems are (even many not obvious ones). Also if you will go for `struct/class` usage look into [bds 2006 C hidden memory manager conflicts](http://stackoverflow.com/a/18016392/2521214) to avoid problems. from first look `bb[411]++;` is out of bounds as arrays are indexed from zero !!! so you are overwriting something that can cause problems later. BTW you forgot to specify what room is ... – Spektre Jun 17 '16 at 07:22
  • 1
    Just a helpful hint for the future. You'll probably find it much easier to read your bit representation (I know I would) if you 0-pad the values. That is, for your second cell you have `110`. It would be easier to read if it was `0110`. And `1` should be `0001`. – Jim Mischel Jun 22 '16 at 15:59
  • I have posted a [C++17 brute force solution](http://stackoverflow.com/a/37868317/2642059) for this problem when given a binary representation of the txt file in the form: const `vector rooms` and given the width of each row of the txt file in the form: `const size_t width`. I'd like to see a solution that is faster and more elegant than mine. I will be using Visual Studio 2015 and [this code](http://coliru.stacked-crooked.com/a/c84ea9a2effb175b) to benchmark. You can test locally by replacing `test2` with your code. – Jonathan Mee Jun 22 '16 at 18:43
  • Over the duration of the bounty I have received only 2 algorithms. I compared them here: http://coliru.stacked-crooked.com/a/5a84c9686ff91879 (That code will only run locally.) [Thomas' solution](http://stackoverflow.com/a/37968155/2642059) is far faster than [Spektre's solution](http://stackoverflow.com/a/37993819/2642059) thus I am awarding the bounty to Thomas. – Jonathan Mee Jun 27 '16 at 13:07
  • If the input format gave the x and y dimension of the problem before giving the room bits, I am tempted to think you could go for a solution where you produce the result in the same pass as reading the data. As the scope for the task is to be able to accommodate for ``numeric_limits::max()`` sized problems and given that sizeof(int) is machine dependent and could as well be 64 bit, reading the whole configuration into memory does not appear viable to me... – BitTickler Jun 27 '16 at 16:38

5 Answers5

11

This is a typical problem of finding the total number of connected components in a graph.

Let me help you visualize the analogy by focusing on following points, keeping in mind that we are dealing with undirected graphs here.

1.In a graph, we have various vertices and two vertices are said to be adjacent to each other, if there is an edge between them. Just like your castle where two cells are adjacent to each other if one cell could lead to another cell.

2. In a graph we have two vertices belong to the same connected component, if there is a path between two vertices using the edges. Just like your castle where two cells belong to the same room number if one cell can by following a path of cells could lead to another.

3. In a graph we have connected components, such that a connected component is made up of vertices such that every two vertices of the connected component have a path between them.Just like your castle where we have rooms, such that every two cells of the same room have a path of cells between them.

Now if you are still wondering how to build the graph, well its easy.

The number of vertices will be NxM(for a castle of size N rows and M columns) which is equal to the number of cells.

Just number the cells sequentially and there is an edge between cell a(meaning vertex a) and cell b(vertex b) if both cells are adjacent.

Now the total number of rooms can be easily counted by applying bfs or dfs algorithm on your graph that you build.

The algorithm is described in the first link I provided.

Sumeet
  • 8,086
  • 3
  • 25
  • 45
  • Your answer makes perfect sense but my mind somehow cannot comprehend how exactly i would make a graph out of what i have as I have never seen graphs before.Thank you. – Nikita Cebotari Jun 16 '16 at 21:44
  • 1
    @NikitaCebotari I just cannot explain the data structure graph in one single answer. Wikipedia and youtube are a good place to start. – Sumeet Jun 16 '16 at 22:00
  • 1
    @NikitaCebotari Also its good that you learn them now, because they are an integral part of most problems that can be easily solved using algorithms. – Sumeet Jun 16 '16 at 22:01
6

So honestly I just really wanted to try to solve this. So I'm going to say you've made a valiant effort at this and just go ahead and show you how to do it. I'm going to assume that you can supply the algorithm the following:

  1. Input numbers read in in binary (so "1101" will be read in as the decimal number "13")
  2. All these numbers in a const vector<char> rooms
  3. The width of each row of "rooms" can be put in size_t width (which must be consistent across all rows, we have to be working with a rectangle of rooms)
  4. All exterior "walls" of "rooms" will have a "wall"
  5. There are less than numeric_limits<int>::max() "rooms"

We'll use vector<int> temp to label each room, we'll construct it of the size of rooms and initialize each label to 0. int result will be used to label rooms, and will be initialized to 0. But because all the room labels will not be decremented when a smaller label is replaced, size(set<int>(cbegin(temp), cend(temp))) will be used to find the final label count.

Our solution will be built around a function taking in 2 "rooms" between which there is no wall; such that either:

  1. One room is not yet labeled, in which case it will take on the other room's label
  2. Both rooms share a label in which case no action will take place
  3. One rooms label is smaller in which case all rooms of the larger label will be assigned the smaller label

An important note about this function, I'm using the unary plus operator to construct an R-Value int from an L-Values int& more information here. A clearer solution would probably be to use static_cast<int> but for some reason Visual Studio 2015 doesn't work as expected more information here.

void generate(vector<int>& temp, int& target, const size_t width, const size_t i) {
    const auto replacement = temp[i];

    if (target > replacement) {
        replace(begin(temp), next(begin(temp), min(size(temp), i + width - 1)), target, replacement);
    } else {
        target = replacement;
    }
}

Using this code we are able to:

for (size_t i = 0U; i < size(rooms); ++i) {
    const auto toWest = (rooms[i] & 0b1000) == 0;
    const auto toNorth = (rooms[i] & 0b100) == 0;
    const auto toEast = (rooms[i] & 0b10) == 0;
    const auto toSouth = (rooms[i] & 0b1) == 0;
    const auto west = toWest && temp[i - 1] != 0 ? temp[i - 1] : numeric_limits<int>::max();
    const auto north = toNorth && temp[i - width] != 0 ? temp[i - width] : numeric_limits<int>::max();
    const auto east = toEast && temp[i + 1] != 0 ? temp[i + 1] : numeric_limits<int>::max();

    temp[i] = min({ temp[i] != 0 ? temp[i] : numeric_limits<int>::max(), result + 1, west, north, east });

    if (temp[i] == result + 1) ++result;

    if (toWest) generate(temp, temp[i - 1], width, i);
    if (toNorth) generate(temp, temp[i - width], width, i);
    if (toEast) generate(temp, temp[i + 1], width, i);
    if (toSouth) temp[i + width] = temp[i];
}

Live Example

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
2

There are few problems with your code prohibiting proper debug form third side like insufficient info about how it works, undefined variables (m,n,b) array overruns (numerous access to [411] while the size is only 411 instead of 412) discouraging anyone from even start to try (making one wonder if the code is really useful and worthy of time spending). I was also curious so here my simple unoptimized attempt in BDS2006 Turbo C++ (successor to BCB6 so this code should work there as well) for this task.

[rooms.h]

//---------------------------------------------------------------------------
#ifndef _rooms_h
#define _rooms_h
//---------------------------------------------------------------------------
class rooms
    {
public:
    // variables
    int xs,ys;          // map resolution
    DWORD **map;        // map[xs][ys]

    enum
        {
        _W=8,
        _N=4,
        _E=2,
        _S=1
        };

    // internals
    rooms();
    ~rooms();
    void _free();                                       // release map memory

    // inteface
    void resize(int _xs,int _ys);                       // realloc map to new resolution
    void set(AnsiString txt);                           // copy txt to map
    void draw(TCanvas *scr,int x,int y,int sz);         // draw map on Canvas at (x,y) with grid size sz
    int  count();                                       // count rooms
    };
//---------------------------------------------------------------------------
     rooms::rooms()     { map=NULL; xs=0; ys=0; }
     rooms::~rooms()    { _free(); }
//---------------------------------------------------------------------------
void rooms::_free()
    {
    if (map)
        {
        for (int x=0;x<xs;x++)
         if (map[x])
          delete[] map[x];
        delete[] map;
        }
    map=NULL; xs=0; ys=0;
    }
//---------------------------------------------------------------------------
void rooms::resize(int _xs,int _ys)
    {
    if ((xs==_xs)&&(ys==_ys)) return;
    _free();
    xs=_xs; ys=_ys;
    map=new DWORD*[xs];
    for (int x=0;x<xs;x++)
     map[x]=new DWORD[ys];
    }
//---------------------------------------------------------------------------
void rooms::set(AnsiString txt)
    {
    int i,l,x,y,n0,n1;
    l=txt.Length(); if (!l) return;
    // count eof lines (ys)
    for (y=0,i=1;i<=l;i++)
     if ((txt[i]==13)||(txt[i]==10))
        {
        y++;
        if (i<l)
         if ((txt[i+1]==13)||(txt[i+1]==10)) i++;
        }
     if ((txt[l]!=13)&&(txt[l]!=10)) y++;   // handle missing last eof
    // count numbers per line (xs)
    for (n1=0,x=0,i=1;i<=l;i++)
        {
        n0=n1; n1=0;
        if ((txt[i]=='0')||(txt[i]=='1')) n1=1;
        if ((txt[i+1]==13)||(txt[i+1]==10)) break;
        if ((!n0)&&(n1)) x++;
        }
    // copy data
    resize(x,y);
    for (x=0,y=0,i=1;i<=l;)
        {
        // skip spaces
        while ((i<=l)&&(txt[i]!='0')&&(txt[i]!='1')) i++;
        // read 4 bit bin number
        n0=  0; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        n0<<=1; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        n0<<=1; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        n0<<=1; if (i>l) break; if (txt[i]=='1') n0|=1; i++;
        map[x][y]=n0;
        x++; if (x>=xs) { x=0; y++; if (y>=ys) break; }
        }
    // clear the rest in case of error in data
    if ((y<ys)&&(x<xs)) for (;;)
        {
        map[x][y]=0;
        x++; if (x>=xs) { x=0; y++; if (y>=ys) break; }
        }
    }
//---------------------------------------------------------------------------
void rooms::draw(TCanvas *scr,int x0,int y0,int sz)
    {
    int x,y,xx,yy;
    DWORD a;

    scr->Brush->Color=clDkGray;
    scr->Brush->Style=bsSolid;
    scr->FillRect(Rect(x0,y0,x0+xs*sz,y0+ys*sz));

    scr->Pen->Color=clBlue;
    scr->Pen->Width=5;
    scr->Font->Color=clBlack;
    scr->Brush->Style=bsClear;
    for (xx=x0,x=0;x<xs;x++,xx+=sz)
     for (yy=y0,y=0;y<ys;y++,yy+=sz)
        {
        a=map[x][y]&15;
        if (DWORD(a&_W)) { scr->MoveTo(xx   ,yy   ); scr->LineTo(xx   ,yy+sz); }
        if (DWORD(a&_N)) { scr->MoveTo(xx   ,yy   ); scr->LineTo(xx+sz,yy   ); }
        if (DWORD(a&_E)) { scr->MoveTo(xx+sz,yy   ); scr->LineTo(xx+sz,yy+sz); }
        if (DWORD(a&_S)) { scr->MoveTo(xx   ,yy+sz); scr->LineTo(xx+sz,yy+sz); }
        scr->TextOutA(xx+(sz>>1),yy+(sz>>1),map[x][y]>>4);
        }
    scr->Brush->Style=bsSolid;
    scr->Pen->Width=1;
    }
//---------------------------------------------------------------------------
int  rooms::count()
    {
    int x,y,i,i0,i1,w0,w1,n,e;
    // each block is a separate room
    for (n=0,x=0;x<xs;x++)
     for (y=0;y<ys;y++,n+=16)
        {
        map[x][y]&=  0x0000000F;    // low 4 bits are walls
        map[x][y]|=n&0xFFFFFFF0;    // rest is room index
        } n>>=4;
    // reindex all indexes i0 to i1
    #define map_reindex(i0,i1)                      \
        for (x=0;x<xs;x++)                          \
         for (y=0;y<ys;y++)                         \
          if (DWORD(map[x][y]&0xFFFFFFF0)==i0)      \
            {                                       \
            map[x][y]&=   0x0000000F;               \
            map[x][y]|=i1&0xFFFFFFF0;               \
            }
    // loop until no change has occured
    for (e=1;e;)
        {
        e=0;
        // merge columns
        for (x=0;x<xs;x++)
         for (y=1;y<ys;y++)
            {
            w0=map[x][y-1]&0x0000000F;
            i0=map[x][y-1]&0xFFFFFFF0;
            w1=map[x][y  ]&0x0000000F;
            i1=map[x][y  ]&0xFFFFFFF0;
            if ((i0!=i1)&&(DWORD(w0&_S)==0)&&(DWORD(w1&_N)==0)) { map_reindex(i0,i1); n--; e=1; }
            }
        // merge rows
        for (y=0;y<ys;y++)
         for (x=1;x<xs;x++)
            {
            w0=map[x-1][y]&0x0000000F;
            i0=map[x-1][y]&0xFFFFFFF0;
            w1=map[x  ][y]&0x0000000F;
            i1=map[x  ][y]&0xFFFFFFF0;
            if ((i0!=i1)&&(DWORD(w0&_E)==0)&&(DWORD(w1&_W)==0)) { map_reindex(i0,i1); n--; e=1; }
            }
        }           

    return n;
    #undef map_reindex
    }
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

[And Single window VCL Form app C++ source without any component]

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "rooms.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *bmp; // back buffer
int xs,ys;              // actual window resolution
rooms map;              // map of rooms
//---------------------------------------------------------------------------
void draw()
    {
    int x,y,sz;
    // clear bachground
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->Brush->Style=bsSolid;
    bmp->Canvas->FillRect(Rect(0,0,xs,ys));
    // compute grid size
    x=(xs-20)/map.xs; sz=x;
    y=(ys-20)/map.ys; if (x>y) sz=y;
    // and map position so it is centered
    x=(xs-(sz*map.xs))>>1;
    y=(ys-(sz*map.ys))>>1;
    // render to backbuffer (avoid flickering)
    map.draw(bmp->Canvas,x,y,sz);
    // render backbuffer to window
    Form1->Canvas->Draw(0,0,bmp);
    }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    // init backbuffer
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    // init map
    map.set("1101 0110 1101 0110 1100 0101 0110\r\n1110 1001 0110 1011 1010 1111 1010\r\n1000 0101 0011 1110 1011 1110 1010\r\n1011 1101 0101 0001 0101 0011 1011\r\n");
    Caption=map.count();    // result count is in the Window Caption
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender) { delete bmp; }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender) { draw(); }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    // get actual window size
    xs=ClientWidth;
    ys=ClientHeight;
    // resize backbufer and force redraw
    bmp->SetSize(xs,ys);
    draw();
    }
//---------------------------------------------------------------------------

The idea behind my solver for this is simple:

  1. ID each grid cell by distinct room number

    remember the cell count as n

  2. merge all neighboring rooms without any wall between them

    so loop through all rooms and if any neighbor cell has no wall in the way and has different room ID re-index its room number so booth rooms has the same one. Also decrement the room counter n.

  3. loop #2 until no re-indexing occurs

example

[Notes]

Do not forget to create appropriate events in your IDE instead of just copying the code otherwise it will not work.

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • This does correctly solve the problem +1. [I don't really care whether you do it in C++ builder 6 for the bounty](http://stackoverflow.com/questions/37867723/counting-rooms-while-knowing-where-walls-are-using-c-builder-6/37993819#comment63398147_37867723), so I've pulled your code into a pure C++ file here: http://ideone.com/ecd0tU The algorithm for this seems similar to my own solution though it could be that the 2-dimensional array is what's killing your speed. – Jonathan Mee Jun 23 '16 at 15:06
  • @JonathanMee The speed is killed by the reindex macro as it loops through whole array instead of flood fill or indexed line merging. And also there are 3 times more array access as is really needed just to make the code more understandable – Spektre Jun 23 '16 at 16:34
  • Yeah but I mean the `replace` in my code also has to go through and relabel, but then I guess I clamp that to only relabeling rooms that I have already labeled, not everything. So you're right that's probably the slowdown here. I'm tinkering with a linked list or similar, something I could use to just assign nodes to a label rather than looping through all the labels. I may put in an answer with that if the speedup is notable. – Jonathan Mee Jun 23 '16 at 17:23
  • @JonathanMee ID lists are not a good way for this... I got much better results with remembering bounding box and use only its area ... after merging you just merge bounding box that will speed up considerably. But for fast image segmentation I use line segment instead individual pixels (cells). It lowers the stack/heap trashing and demands to a very small fraction... For big resolution you can divide and conquer the map recursively lowering complexity... – Spektre Jun 23 '16 at 17:36
  • Again I have no interest in the rendering. I want to know how long it takes to count the rooms, IO would generally overshadow any calculation time, which is all I'm interested in. I'm testing [the extraction of that algorithm](http://ideone.com/ecd0tU) against mine. And *that* is what I'm saying is slower than mine. – Jonathan Mee Jun 23 '16 at 17:59
  • Over the duration of the bounty I have received only 2 algorithms. I compared them here: http://coliru.stacked-crooked.com/a/5a84c9686ff91879 (That code will only run locally.) [Thomas' solution](http://stackoverflow.com/a/37968155/2642059) is far faster than [Spektre's solution](http://stackoverflow.com/a/37993819/2642059) thus I am awarding the bounty to Thomas. – Jonathan Mee Jun 27 '16 at 13:08
1

one easy way would be creating an array with the size of the room and initialize it with "0". After that you should iterate over the array. If you find a "0", you start an BFS from that point and color the array results to the current number.

Information about the BFS

The BFS has to look to the direct neighbor and check if there is a "0" inside this array. If this is true, the BFS has to check if there are no wall between the two boxes. If there is no wall in between, color this box with the current color (at the beginning 1) and call the BFS on the new box.

The BFS stops automaticly when the room is completely colored. then the global color will be increased by 1 and the loop continues and looks for the next "0".

After the loop the count of the rooms is stored inside the global color value.

this algorithm works in O(n)

Small example:

//CountingRooms.h
#include <vector>

class CountingRooms
{
public:
    CountingRooms();
    ~CountingRooms();
    int Count();

    void TestFill();
    void Print();
private:
    struct Point
    {
        int x = 0;
        int y = 0;
    };
    unsigned int* m_arrFieldColors = nullptr;
    unsigned int* m_arrFieldWalls = nullptr;
    int m_nSizeX = 0;
    int m_nSizeY = 0;
    int m_nCurrentColor = 0;

    unsigned int GetValue(unsigned int* field, int x, int y);
    void SetValue(unsigned int* field, int x, int y, unsigned int value);

    bool CanPass(int x1, int y1, int x2, int y2);
    void DFS(int posX, int posY);
    bool IsInsideArray(int x1, int y1); 
};



//CountingRooms.cpp
#include "stdafx.h"
#include "CountingRooms.h"
#include <iostream>

CountingRooms::CountingRooms()
{
}


CountingRooms::~CountingRooms()
{
    if (m_arrFieldColors)
    {
        delete[]m_arrFieldColors;
    }
    if (m_arrFieldWalls)
    {
        delete[]m_arrFieldWalls;
    }
}

bool CountingRooms::IsInsideArray(int x, int y)
{
    return x >= 0 && y >= 0 && x < m_nSizeX && y < m_nSizeY;
}

bool CountingRooms::CanPass(int x1, int y1, int x2, int y2)
{
    if (IsInsideArray(x1, y1) && IsInsideArray(x2, y2)) //inside the array range
    {
        if (x2 - x1 == 1 && y2 - y1 == 0) // right
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 2) && !(GetValue(m_arrFieldWalls, x2, y2) & 8)) { return true; }
        }
        if (x2 - x1 == 0 && y2 - y1 == -1) // up
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 4) && !(GetValue(m_arrFieldWalls, x2, y2) & 1)) { return true; }
        }
        if (x2 - x1 == -1 && y2 - y1 == 0) // left
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 8) && !(GetValue(m_arrFieldWalls, x2, y2) & 2)) { return true; }
        }
        if (x2 - x1 == 0 && y2 - y1 == 1) // down
        {
            if (!(GetValue(m_arrFieldWalls, x1, y1) & 1) && !(GetValue(m_arrFieldWalls, x2, y2) & 4)) { return true; }
        }
    }
    return false;
}

void CountingRooms::DFS(int posX, int posY)
{
    if (GetValue(m_arrFieldColors, posX, posY)) // check if the field is already colored
    {
        return;
    }
    Point sStart;
    sStart.x = posX;
    sStart.y = posY;
    std::vector<Point> vecList;
    vecList.push_back(sStart);

    m_nCurrentColor++;

    while (vecList.size()) // as long as something is inside the list
    {
        Point sTemp = vecList[vecList.size()-1]; //get out the last element
        vecList.pop_back();

        if (IsInsideArray(sTemp.x, sTemp.y))
        {
            if (!GetValue(m_arrFieldColors, sTemp.x, sTemp.y)) // is field not colored
            {
                SetValue(m_arrFieldColors, sTemp.x, sTemp.y, m_nCurrentColor);

                if (CanPass(sTemp.x, sTemp.y, sTemp.x + 1, sTemp.y)) /* right*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x + 1;
                    newPoint.y = sTemp.y;
                    vecList.push_back(newPoint);
                }
                if (CanPass(sTemp.x, sTemp.y, sTemp.x - 1, sTemp.y)) /* left*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x - 1;
                    newPoint.y = sTemp.y;
                    vecList.push_back(newPoint);
                }
                if (CanPass(sTemp.x, sTemp.y, sTemp.x, sTemp.y - 1)) /* up*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x;
                    newPoint.y = sTemp.y - 1;
                    vecList.push_back(newPoint);
                }
                if (CanPass(sTemp.x, sTemp.y, sTemp.x, sTemp.y + 1)) /* down*/
                {
                    Point newPoint;
                    newPoint.x = sTemp.x;
                    newPoint.y = sTemp.y + 1;
                    vecList.push_back(newPoint);
                }

            }
        }
    }
}

int CountingRooms::Count()
{
    m_nCurrentColor = 0;
    for (int i = 0; i < m_nSizeY; ++i)
    {
        for (int j = 0; j < m_nSizeX; ++j)
        {
            DFS(j, i);
        }
    }
    return m_nCurrentColor;
}

void CountingRooms::TestFill()
{
    m_arrFieldWalls = new unsigned int[42]{13, 6,13, 6,12, 5, 6,
                                           14, 9, 6,11,10,15,10,
                                            8, 5, 3,14,11,14,10,
                                           11,13, 5, 1, 5, 3,11};
    m_arrFieldColors = new unsigned int[42];
    for (int i = 0; i < 42;i++)
    {
        m_arrFieldColors[i] = 0;
    }
    m_nSizeX = 7;
    m_nSizeY = 4;

}

unsigned int CountingRooms::GetValue(unsigned int* field, int x, int y)
{
    if (IsInsideArray(x, y))
    {
        return field[x + m_nSizeX*y];
    }
    return -1;
}

void CountingRooms::SetValue(unsigned int* field, int x, int y, unsigned int value)
{
    if (IsInsideArray(x, y))
    {
        field[x + m_nSizeX*y] = value;
    }
}

void CountingRooms::Print()
{
    std::cout << "Walls:" << std::endl;
    for (int j = 0; j < m_nSizeY;++j)
    {
        for (int i = 0; i < m_nSizeX;++i)
        {
            std::cout << GetValue(m_arrFieldWalls, i, j) << "\t";
        }
        std::cout << std::endl;
    }
    std::cout << std::endl<<"Colors:" << std::endl;

    for (int j = 0; j < m_nSizeY;++j)
    {
        for (int i = 0; i < m_nSizeX;++i)
        {
            std::cout << GetValue(m_arrFieldColors, i, j) << "\t";
        }
        std::cout << std::endl;
    }
}







//main.cpp
#include "stdafx.h"
#include <iostream>
#include "CountingRooms.h"

int main()
{
    CountingRooms cr;
    cr.TestFill();
    std::cout<<"There are "<<cr.Count()<<" rooms"<<std::endl;
    cr.Print();

    char key = 0;
    std::cin >> key;
    return 0;
}

btw: the BFS is replaced by a DFS, but both is working.

Output

enter image description here

Thomas
  • 2,093
  • 3
  • 21
  • 40
  • The reason it's not a simple BFS algorithm is that we often have to recolor rooms. Consider the input set: `const vector rooms = {0b1110, 0b1110, 0b1001, 0b0011}` and `const size_t width = 2U` In this case the second room would initially be colored with a separate color than the first room, but should subsequently be recolored with the same color as first room. I don't think there's a way to do this in *O(n)*, but I'd love to see an actual coding attempt! – Jonathan Mee Jun 22 '16 at 18:55
  • 1
    Hey, I appreciate the effort on this... I'm sad to see that you and I seem to be the only ones interested in this problem. As such I'd be happy to give you at least a +1 if you can get code that solves the problem (and the bounty if you can beat my run-time.) Don't worry about not having an IDE though, there are tons of cloud compilers available to you. Feel free to fork [my code](http://ideone.com/93VuBJ) and compare our outputs. – Jonathan Mee Jun 23 '16 at 10:53
  • I'm 100% sure, that the idear is still working. maybe there are some sintax errors, but they should be very easy to fix. The only thing to do is putting the bits into an integer array and run this code. – Thomas Jun 23 '16 at 11:02
  • Yeah there are definitely syntax problems in your code. But for the most part they seem straight forward, so I agree they shouldn't be hard to fix. I think the easiest way to do that would be to fork [my code](http://ideone.com/93VuBJ) and compare. But if you're having problems with that you could always just go to http://www.ideone.com and start from scratch. – Jonathan Mee Jun 23 '16 at 11:09
  • @JonathanMee sorry, I'm now at work... I'll do that when I'm home – Thomas Jun 23 '16 at 11:19
  • @JonathanMee sorry, that this toke so long... but now I edited my post. have fun with the code ;) – Thomas Jun 24 '16 at 07:03
  • @JonathanMee Sorry, I seem to be missing something. I have some algorithm in mind (very similar to mentioned here but still a bit different) and I am willing to write code, but I don't clearly see a point - isn't the problem solved already? What's wrong with your solution? – mvidelgauz Jun 25 '16 at 17:40
  • @mvidelgauz my code is still working, I don't checked the other ones code... But this answer has the full program, from main over header till cpp everything is there – Thomas Jun 25 '16 at 18:36
  • @mvidelgauz The problem is already solved. [I had solved it when I offered the bounty.](http://stackoverflow.com/a/37868317/2642059) What I'm interested in is the actual algorithm to solve the core of the problem faster than mine. You'll find benchmarking code for comparison here: http://stackoverflow.com/questions/37867723/counting-rooms-while-knowing-where-walls-are/37968155?noredirect=1#comment63398147_37867723 – Jonathan Mee Jun 25 '16 at 20:19
  • I've spent what is probably an inordinate amount of time getting `CountingRooms` to accept the required input. I didn't want it to do an additional copy on startup, so I had to improve the design a bit: http://ideone.com/kc7m90 The core of the algorithm in `DFS` remains unchanged though (which is the reason it's so slow: The recreation of `m_arrFieldWalls` in `vecList` and the tremendous number of unnecessary calls to `IsInsideArray` hamper what would otherwise be a brute force design.) None the less, I am giving a +1 because in-spite of excessive overhead it does correctly solve the problem. – Jonathan Mee Jun 27 '16 at 12:16
  • Over the duration of the bounty I have received only 2 algorithms. I compared them here: http://coliru.stacked-crooked.com/a/5a84c9686ff91879 (That code will only run locally.) [Thomas' solution](http://stackoverflow.com/a/37968155/2642059) is far faster than [Spektre's solution](http://stackoverflow.com/a/37993819/2642059) thus I am awarding the bounty to Thomas. – Jonathan Mee Jun 27 '16 at 13:08
  • @JonathanMee Thanks a lot! – Thomas Jun 27 '16 at 13:20
1

Disjoint Sets along with Union-Find are well matched to problems of connected components. Instead of a graph, I keep track of different disjoint sets of floor tiles. Each set has its own representative tile which uniquely identifies the set.

You can read more about Union-Find here.

The algorithm is simple:

  1. For each tile, process its neighbouring tiles, if the common wall is absent. This is done using a simple union() of the two tiles. When we are done, each tile will belong to one of the disjoint sets (each set representing a connected space or room). In the attached code, parent[] stores the representative element.

    • When done, normalize each tile's representative element.
  2. Count the number of unique representative elements. This is the number of disjoint sets (and hence the number of connected spaces or rooms).

Some additional observations:

  • At any time, you only need to process the North and West walls. (Why? The proof is left to the reader.)

    That's because for a tile a[i][j], its South and East walls can be processed by tiles   a[i+1][j] and a[i][j+1] respectively.

  • Why do we need to normalize each tile's representative? The reason is that some sets may end up with two or more representative elements. We recursively find the parent element to get a unique representative for the entire set. (If you want to test this, comment out the following line from the attached code:

    // normalise each tile
    parent[i] = findSet(parent[i]);
    
  • The attached code uses a special version of Union-Find with two heuristics:

    • Union by rank (as evidenced by ranks[])
    • Path compression

Attached code: Live Example

Quirk
  • 1,305
  • 4
  • 15
  • 29