4
\$\begingroup\$

How can I make this matrix effect more like the matrix movie?

#include<stdlib.h>
#include<stdio.h>
int main()
{
srand((unsigned) time(0));
int i,j;
system("color 0a");

while(1)
{
    i = rand() % 2;
    j= rand() % 2;
    if(j)
    {
       printf("%d ", i);
    }
    else
    {
        printf(" %d", i);
    }

}
return 0;
}

Here is how it looks like:

matrix

Of course it is better if it was moving but that's all what I can show.

Also it takes a notable time to compile, is there anything wrong I'm doing?

\$\endgroup\$
2
  • \$\begingroup\$ How is it not like the movie... what are you trying to do? \$\endgroup\$ – Hogan Apr 5 '14 at 15:18
  • \$\begingroup\$ nb - a google search of "matrix animation javascript" will turn up a number of good examples in javascript. The key is to index the display to place the character at a given x, y to achieve the "rain" effect. \$\endgroup\$ – Hogan Apr 5 '14 at 16:53
2
\$\begingroup\$

What You Have Now:

You code is simple and concise. That in and of itself can be a desirable quality. I think we can make the code a little bit more concise while gaining some runtime efficiency.

Some changes I would make:

Change srand((unsigned) time(0)); to srand(time(NULL));

  • While both are correct, this is how I have seen seeds set more often, hence it is the idiomatic approach

Change return 0 to return EXIT_SUCCESS

  • Exit codes are included in stdlib, since you already using that library, you should also use the provided exit codes.

Change rand()%2 to rand()&1

  • The modulo operator (%) can be very inefficient on some architectures. Since you are only using rand() to get binary values, we can much more efficiently use the bitwise and operator (&) to get a 1 if the number is odd and a 0 if the number is even.

Change your while loop to remove intermediate variables:

while(1) {
 if(rand() & 1)
   printf("%d ", rand() & 1);
 else
   printf(" %d", rand() & 1);
}

All together I think you could simplify your code to this:

#include<stdlib.h>
#include<stdio.h>
int main() {
srand(time(NULL));
system("color 0a");

while(1) {
  if(rand() & 1)
    printf("%d ", rand() & 1);
  else
    printf(" %d", rand() & 1);
}

return EXIT_SUCCESS;
}

This is a pretty small and eloquent code for generating a matrix style output. If your happy with this you can stop right here.


However, it should be pointed out that printf is a little heavy handed for just printing spaces, ones, and zeros. You could utilize putchar which will, unsurprisingly, put a character to STDOUT.

while(1) {
  if(rand() & 1) {
    putchar(48 + (rand()&1))
    putchar(' ');
  }
  else {
    putchar(' ')
    putchar(48 + (rand()&1));
  }
}

Note that now we have a Magic Number 48 in out code, that is no good! Let's let the preprocessor help us make the code a little bit more readable:

#define ASCII_NUMBER_OFFSET 48
while(1) {
  if(rand() & 1) {
    putchar(ASCII_NUMBER_OFFSET + (rand()&1));
    putchar(' ');
  }
  else {
    putchar(' ');
    putchar(ASCII_NUMBER_OFFSET + (rand()&1));
  }
}

Next is a matter of personal preference. I personally don't like 2 line if statements. If the if statement has 3 lines, it should probably be abstracted into a separate method, if it has one line, that’s perfect, but if we have 2 lines... that's murky water.

We could use the comma operator to clean up the line a little:

#define ASCII_NUMBER_OFFSET 48
while(1) {
  if(rand() & 1)
    putchar(ASCII_NUMBER_OFFSET + (rand()&2)), putchar(' ');
  else
    putchar(' '), putchar(ASCII_NUMBER_OFFSET + (rand()&1));
}

Using the comma operator is a matter of preference. I think that there are times that it is perfectly clear & reasonable to use it, others say the comma operator should never be used because it is too opaque. While that code is more compact, I don't think it helps readability or efficiency.

Lets try abstraction that into a method instead and see how that looks:

while(1) {
  if(rand() & 1)
    printLeft();
  else
    printRight();
}
.
.
.
#define ASCII_NUMBER_OFFSET 48
void printLeft() {
   putchar(' ');
   putchar(ASCII_NUMBER_OFFSET + (rand()&1));
}

void printRight() {
   putchar(ASCII_NUMBER_OFFSET + (rand()&1));
   putchar(' ');
}

That makes the while loop look better but the methods might incur some performance cost. Since your tag Optimization implies that you care most about runtime efficiency and not readability/maintainability lets try something else.

You will notice in your all the code examples above, there is some repetition in the while loop. In all the cases we have 2 statements printing the one or zero. Repetition is often a good thing to focus on because it can alert you to things that can be improved. There is a mantra of Don't Repeat Yourself (DRY) in programming which is designed to help programmers locate inefficiencies and refactor their code for better readability & efficency.

Consider this loop:

int b;
while(1) {
  if(b = rand() & 1)
    putchar(' ');
  putchar(ASCII_NUMBER_OFFSET + (rand() & 1));
  if(!b)
    putchar(' ');
}

Here we have a single binary number printing statement but conditionally print a space before or after the number. From a runtime efficiency perspective this is probably optimal (though I have not done any benchmarking).

If we put this all together we have the following:

#include<stdlib.h>
#include<stdio.h>
#define ASCII_NUMBER_OFFSET 48

int main() {
int b;
srand(time(NULL));
system("color 0a");

while(1) {
  if(b = (rand() & 1))
    putchar(' ');
  putchar(ASCII_NUMBER_OFFSET + (rand() & 1));
  if(!b)
    putchar(' ');
}

return EXIT_SUCCESS;
}

I hope this has given you a lot of different angles to view you code from and given you the tools to decide what you value when improving this code, readability or runtime efficiency.

\$\endgroup\$
3
  • \$\begingroup\$ good call, I forgot about that... \$\endgroup\$ – recursion.ninja Apr 5 '14 at 16:52
  • 1
    \$\begingroup\$ "Change return 0 to return EXIT_SUCCESS" - Just get rid of the return completely, thanks to C99 & C11 §5.1.2.2(3); your parameters should be declared as void, if you don't take anything in (for example: main(void)); you should always initialize your variable on creation; and if you only have one statement in a test conditional, you should mimic this style to prevent bugs. \$\endgroup\$ – syb0rg Apr 5 '14 at 21:07
  • \$\begingroup\$ Also, you should make a note to not use system(), and instead use ANSI escape codes. All modern terminals implement them, and they will speed up the code. And you forgot to #include <time.h> when you used time(). \$\endgroup\$ – syb0rg Apr 5 '14 at 21:23
1
\$\begingroup\$

If you are talking optimization (not sure you are) then one clear change is to remove printf -- printf is very slow. You are only displaying 0, 1, and .

putc is much faster -- it does not have to parse for formatting. If you change the code to use putc instead of printf you should see a dramatic speed boost.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ You're wrong. I mean, there is no need to turn print into putchar. Indeed, the compiler will do that automatically if needed, even with no optimization flag turned on. Checked on gcc. \$\endgroup\$ – edmz Apr 5 '14 at 16:57
  • \$\begingroup\$ @no1 - The printf (not print) optimization only happens for string literals, %s and %c. None of which is true for this example which is a %d. This is why awashburn's answer requires the ASCII_NUMBER_OFFSET define. I'm not wrong, you are. More info: ciselant.de/projects/gcc_printf/gcc_printf.html \$\endgroup\$ – Hogan Apr 6 '14 at 3:43
  • \$\begingroup\$ There is no need to use that ugly macro, since you can directly 1) convert to char 2) add 0x30 - '0'. Hence, either you call printf("%c", n) or putchar(n + 0x30) will be equivalent. Good to point out, though. \$\endgroup\$ – edmz Apr 6 '14 at 10:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.