Data structure and algorithm

As a computer science lecturer (though I am from cognitive science background, and even labelled as NOT from computer science, 士可杀,不可辱!), I focus on the fundamental knowledge and experience. I focus on C/C++ language on my students, because only have good fundamental knowledge and skills, then they can survive with the evolving technology once they graduated. For example, Flash was popular technology, yet now it is dying. Those who know Flash only, have to learn new technology. Without fundamental skill, how to cope with the new technologies that emerge everyday? Know the root, then you can get any leaf.

So, I gave my students an assignment. The assignment requires them to write a program that can

  • Read a text file as a map, with binary information in the map, block or space. Block does not allow to move; the space allows to move.
  • Find the route from upper left to the lower right (assuming that there is at least one route in the map file.)
  • Show the route that goes from upper left to lower right through the space.

To write a program, the first thing is to design the data structure that can hold the input/output data. Only with the good data structure, then the algorithms will emerge automatically.

I personally spent around 3 hours to write the program using C++ language, but not object oriented and omitted the STL. No object oriented involved, but only struct. The solution involves 2D array, linked list, tree (ADT) and tree traversal, and stack with doubly linked list.

The file is read line by line, and converted into 2D array. Then using the 2D array, convert this 2D map with blocks and spaces into a tree data structure. The spaces are connected as the tree nodes, the blocks are ignored. Each node has an (x, y)-coordinate. Once the tree data structure is completed, then I wrote a depth first search (DFS) as a tree traversal, start from the upper left, and target the lower right. However, because it is a DFS, it will try all branches in the search tree. So, I have to use a stack (using doubly linked list) to store the route that straight to the destination. Finally, the route in the stack is printed.

No GUI, because inside-out is important, just like a pretty apple with rotten content is good for nothing. What I focus is the inner quality (attitude, hardworking, problem solving, etc), not the flattering or boasting. Since I am a Catholic, Chinese, cognitive science student, programmer, and INTJ personality (if you don’t know what is INTJ, please go to google it), when it is true, I say true; when it is false, I say false; when I don’t know, I say I don’t know. All the BS reserved for those BSers. Since I am a lecturer, it is my responsibility to optimise my lecturing function to guide the students in their studies. It is a shame for those academic murderers who exploit the students in order to get their own fame, get the research grant, and plagiarise the students’ work to publish their own research papers.

So, the coding I wrote also demonstrates the usage of pointer (computer science students MUST know), dynamic memory allocation, and recursive function (important in FP).

#include <cstdio>
#include <cstring>
#include <cstdlib>

struct MapNode {
  MapNode *up;
  MapNode *down;
  MapNode *left;
  MapNode *right;
  int data;
  int x;
  int y;
  int visited;
};

/**
 * This is just like the OOP object constructor.
 */
MapNode* mapNodeCreate(int x, int y, int data) {
  MapNode *n = new MapNode;
  n->up = NULL;
  n->down = NULL;
  n->left = NULL;
  n->right = NULL;
  n->x = x;
  n->y = y;
  n->data = data;
  n->visited = 0;
  return n;
}

/**
 * The following stack design is like this,
 * the Stack is a container of the StackNode*.
 * The StackNode contains MapNode*
 * Therefore, using the Stack ADT, we can push and pop the StackNode.
 * Since the StackNode is a doubly linked list, at the same time it holds the MapNode* (pointer)
 */

struct StackNode {
  MapNode *mapNode;
  StackNode *next;
  StackNode *prev;
};


StackNode* stackNode(MapNode *mapNode) {
  StackNode *n = new StackNode;
  n->mapNode = mapNode;
  n->next = NULL;
  n->prev = NULL;
  return n;
}

/**
 * So, a Stack contains several StackNode*.
 */
struct Stack {
  StackNode *head;
  StackNode *tail;
};

Stack* stackCreate() {
  Stack *s = new Stack;
  s->head = NULL;
  s->tail = NULL;
  return s;
}


void stackPush(Stack *s, StackNode *n) {
  if(!s->head) { //First item
    s->head = n;
    s->tail = n;
  }
  else { //If there is a node
    s->tail->next = n;
    n->prev = s->tail;
    s->tail = s->tail->next;
    //Make sure it is the last
    s->tail->next = NULL;
  }
}

int stackSize(Stack *s) {
  int size = 0;
  StackNode *curr = s->head;
  while(curr) {
    size++;
    curr = curr->next;
  }
  return size;
}

StackNode* stackPop(Stack *s) {
  StackNode *n = s->tail;
  s->tail = s->tail->prev;
  s->tail->next = NULL;
  return n;
}

void stackPrint(Stack *s) {
  StackNode *curr = s->head;
  while(curr) {
    printf("(%d, %d)\n", curr->mapNode->x, curr->mapNode->y);
    curr = curr->next;
  }
}

MapNode **list; //For storing the nodes, then delete them at the end
Stack *stack = stackCreate(); //For storing the coordinates, default will head NULL and tail NULL


/**
 * In order to solve this, this is very complicated. Firstly, I used
 * a variable depth to check when is the push and pop are appropriate,
 * As a result, convert the depth++ to the push and depth-- to the pop
 * to make sure that only correct nodes are stored, and incorrect nodes will be popped.
 * So, this function is traversing all the route, and push the correct route to the stack.
 */
void mapTraverse(MapNode *node, int x, int y, bool *stop) {
  if(!*stop) //Push only if still searching
    stackPush(stack, stackNode(node));

  if(*stop) {
    return;
  }
  if(!node) {
    stackPop(stack);
    return;
  }
  if(node->visited) {
    stackPop(stack);
    return;
  }

  if(node->x == x && node->y == y) {
    *stop = true;
    printf("(%d, %d) Found!!\n", node->x, node->y);
    return;
  }
  node->visited = 1; //mark visited
  printf("(%d, %d), ", node->x, node->y);
  mapTraverse(node->left, x, y, stop);
  mapTraverse(node->up, x, y, stop);
  mapTraverse(node->right, x, y, stop);
  mapTraverse(node->down, x, y, stop);
  if(!*stop) { //Do not pop if founded.
    stackPop(stack);
  }
}

void arrPrint(int *arr, int w, int h) {
  for(int i=0;i<h;i++) {
    for(int j=0;j<w;j++) {
      printf("%d ", arr[i*w + j]);
    }
    printf("\n");
  }
  printf("\n");
}


/**
 * The text file is the binary (1 or 0) content.
 * @param w is the pointer to the width (output)
 * @param h is the pointer to the height (output)
 * @return a pointer of integer for the map.
 */
int* readFileToArray(const char* filename, int *w, int *h) {
  FILE *fp = fopen(filename, "r");

  if(!fp) {
    fprintf(stderr, "Failed to open file\n");
    return NULL;
  }

  char buffer[256];

  //This method (supposed) only applicable for the text file with "\n" (Unix file),
  // not the "\r\n" or "\n\r" (Windows or MacOSX)
  //Get the map dimension first.
  fgets(buffer, 255, fp);
  int width = strlen(buffer) -1;

  //Go to the end of the file
  fseek(fp, 0, SEEK_END);
  int height = ftell(fp) / (width+1);

  printf("width: %d\theight: %d\n", width, height);

  //Go back to the beginning of the file
  fseek(fp, 0, SEEK_SET);

  //Create an array
  int *arr = new int[width * height];
  int *cursor = arr;

  while(fgets(buffer, 255, fp)) {
    buffer[strlen(buffer) -1 ] = 0;
    for(int i=0;i<strlen(buffer);i++) {
      *cursor = buffer[i] - '0'; //Convert the character to the value
      cursor++;
    }
  }
  fclose(fp);

  //Get the width and height
  *w = width;
  *h = height;

  return arr;
}

MapNode* arrToMap(int *arr, int width, int height) {
  //Use an array to store the map node
  list = new MapNode*[width * height]; //This is the syntax that creates an array of pointer (with "*") after the data type.

  for(int j=0;j<height;j++) {
    for(int i=0;i<width;i++) {
      //Create the node
      MapNode *node = mapNodeCreate(i, j, arr[j*width +i]);
      list[j*width +i] = node;
    }
  }

  //Check the connectedness horizontally
  for(int i=0;i<height;i++) {
    for(int j=0;j<width -1;j++) {
      if(arr[i*width+j] == 1 && arr[i*width +j+1] ==1) {
        list[i*width+j]->right = list[i*width +j+1];
        list[i*width+j+1]->left = list[i*width+j];
      }
    }
  }

  //Check the connectedness vertically
  for(int i=0;i<height-1;i++) {
    for(int j=0;j<width;j++) {
      if(arr[i*width+j] == 1 && arr[(i+1)*width +j] ==1) {
        list[i*width+j]->down = list[(i+1)*width +j];
        list[(i+1)*width+j]->up = list[i*width+j];
      }
    }
  }
  MapNode *root = list[0];

  return root; //Return the first node
}


int main(int argc, char **argv) {
  if(argc < 2) {
    printf("Usage: %s map.txt\n", argv[0]);
    return 1;
  }

  int width, height;
  int *arrMap;
  arrMap = readFileToArray(argv[1], &width, &height);

  arrPrint(arrMap, width, height);

  MapNode* mapRoot = arrToMap(arrMap, width, height);


  //Traverse the nodes;
  bool stop = false; //use this to control so that once found, need not to perform recursive search
  mapTraverse(mapRoot, width-1, height-1, &stop);

  printf("Stack size %d\n", stackSize(stack));

  stackPrint(stack);

  delete arrMap;

  //Delete all the nodes
  for(int i=0;i<width * height;i++) {
    delete list[i];
  }

  delete stack;

  return 0;
}

The sample input text file,

10000000000000000000
10000000111111111100
10000000100000100000
10000000100000100000
11111111111111111110
00000100000000100000
00000100000000111110
00000100000000000000
11111100000000000000
00000111111111100000
00000100001000000000
00000100001000000000
00000000001111111100
01110000000000000110
01110000000000000011

And this is the output

width: 20	height: 15
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 
1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 
1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 
0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 0 
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 
0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 
0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 
0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 

(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 4), (2, 4), (3, 4), (4, 4), (5, 4), (6, 4), (7, 4), (8, 4), (8, 3), (8, 2), (8, 1), (9, 1), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), (16, 1), (17, 1), (14, 2), (14, 3), (14, 4), (13, 4), (12, 4), (11, 4), (10, 4), (9, 4), (15, 4), (16, 4), (17, 4), (18, 4), (14, 5), (14, 6), (15, 6), (16, 6), (17, 6), (18, 6), (5, 5), (5, 6), (5, 7), (5, 8), (4, 8), (3, 8), (2, 8), (1, 8), (0, 8), (5, 9), (6, 9), (7, 9), (8, 9), (9, 9), (10, 9), (11, 9), (12, 9), (13, 9), (14, 9), (10, 10), (10, 11), (10, 12), (11, 12), (12, 12), (13, 12), (14, 12), (15, 12), (16, 12), (17, 12), (17, 13), (18, 13), (18, 14), (19, 14) Found!!
Stack size 34
(0, 0)
(0, 1)
(0, 2)
(0, 3)
(0, 4)
(1, 4)
(2, 4)
(3, 4)
(4, 4)
(5, 4)
(5, 5)
(5, 6)
(5, 7)
(5, 8)
(5, 9)
(6, 9)
(7, 9)
(8, 9)
(9, 9)
(10, 9)
(10, 10)
(10, 11)
(10, 12)
(11, 12)
(12, 12)
(13, 12)
(14, 12)
(15, 12)
(16, 12)
(17, 12)
(17, 13)
(18, 13)
(18, 14)
(19, 14)

 

Xfce4, LXDE, and Openbox

What I need is performance, eye candy is optional.

My primary desktop environment is Xfce, as it is more lightweight than GNOME, KDE, or Cinnamon (Mate is out of my choice), yet it has more goodies (plugins) than LXDE. But due to my 4-year-old laptop, I found that LXDE has better performance than Xfce significantly. I can run multiple heavy applications at the same time, especially Firefox and Chromium. Xfce performance drops when I run both applications simultaneously. Switching between applications is slower. If running with Skype at the same time, and doing some development testing, the performance drops drastically.

LXDE is even more lightweight than Xfce. That is why I used LXDE. The only drawback of LXDE is that it does not allow me to zoom the screen with Alt+Mouse Scroll. This is a crucial feature I need as a lecturer. In order to gain a better speed, I also have to sacrifice the wallpaper auto changing as in Xfce. Furthermore, configuring the LXDE is a little troublesome. Since LXDE is using Openbox as window manager, a lot of configurations depend on the Openbox.

Openbox configuration

To configure the LXDE Openbox, we can use the obconf to configure. It is actually editing the ~/.config/openbox/lxde-rc.xml.

obconf also works on Openbox (window manager only). The corresponding file is ~/.config/openbox/rc.xml.

Hotkey configuration

LXDE hotkeys are depending on the Openbox too. We can use the obkey to configure the hotkeys. It is also editing the ~/.config/openbox/lxde-rc.xml or rc.xml.

Openbox right-click context menu

LXDE has its own panel (lxpanel). It allows to show (mostly) all the applications in the menu. However, if we right-click Openbox desktop area, the context menu does not show the installed applications. The application menu in the Openbox is actually written in the ~/.config/openbox/menu.xml. The skeleton file contains the hard coded applications.

There are several ways to show the menu items dynamically based on the installed applications. I personally use openbox-xdgmenu by adding something like this to the menu.xml,

<menu execute="openbox-xdgmenu /etc/xdg/menus/lxde-applications.menu" id="desktop-app-menu" label="Applications"/>

menu.xml is also used by Openbox without running the LXDE. That means, if a user login with Openbox window manager instead of LXDE, it will use the same menu.xml.

However, this can be changed by editing the lxde-rc.xml (LXDE) or rc.xml (Openbox window manager only) to load specific menu XML file. As a result, we are allowed to have different menu.xml files, one for LXDE and one for Openbox (window manager only).

Autostart

Autostart is quite complicated. If using LXDE, Xfce, or Openbox, the application desktop files in the ~/.config/autostart will be launched once login. They can also be disabled.

However, LXDE also allows autostart through ~/.config/lxsession/LXDE/autostart, where the LXDE is the default profile name. The autostart file is not exactly a shell script, but we can add the commands in this autostart file.

Though LXDE is based on Openbox, it does not use ~/.config/openbox/autostart file. If we login with Openbox window manager only, then ~/.config/openbox/autostart will be sourced (called) instead. This autostart file is just a shell script. As a result, we can use control structure like if-else. In order to run a batch of commands immediately, we can use the “&” to run the commands in the background.

There is another thing worth to know. If our display manager is LXDM or LightDM, it will source the ~/.xprofile. However, if we write our commands in the .xprofile, (I think) the commands will called before the DE is totally loaded. Therefore, the command like “compton” will not affect the Openbox. (Compton is an X window compositor, so that the Openbox will become compositing window manager.)

Openbox applications

Since Openbox is just a window manager, if we login with Openbox window manager only, there is no panel, wallpaper, volume control, etc. Therefore, we have to install the packages by our own. Besides that, Openbox does not handle graphical logout. However, we can install oblogout and add it to the Openbox menu.

I personally use the tint2 as the panel, feh for the wallpaper for both Openbox and LxDE (since I don’t use PCManFM), volumeicon for controlling the volume, compton to composite the window, and xscreensaver for screen saver. tint2 has the battery indicator and date time indicator. So, this is how my Openbox look.

Openbox with Tint2
Openbox with Tint2