square
- 4 minutes read - 795 wordsI probably should have been working on my thesis, but I had a phone interview with Square. I wanted to write a bit of code to get myself “warmed up” but wanted it to be kinda fun. So I decided to write some code that drew a square.
#include <iostream>
using namespace std;
int main() {
cout << "+";
for (int i=0; i<8; i++) cout << "-";
cout << "+" << endl;
for (int j=0; j<5; j++) {
cout << "|";
for (int i=0; i<8; i++)
cout << " ";
cout << "|";
cout << endl;
}
cout << "+";
for (int i=0; i<8; i++) cout << "-";
cout << "+" << endl;
return 0;
}
which yields a nice little square:
+--------+
| |
| |
| |
| |
| |
+--------+
(Well, okay, it’s not a square, but pipes are taller than they are wide, so I guess we made a square-ish rectangle. Which would not have been as good of a name for a company. But I digress.)
Fine and good, but there’s so much repetition! We can do better–Programming languages have functions!
#include <iostream>
using namespace std;
void top() {
cout << "+";
for (int i=0; i<8; i++) cout << "-";
cout << "+" << endl;
}
void middles() {
cout << "|";
for (int i=0; i<8; i++)
cout << " ";
cout << "|";
cout << endl;
}
int main() {
top();
for (int j=0; j<5; j++) {
middles();
}
top();
return 0;
}
Now we’ve abstracted out some of the logic into functions. This looks a bit better. But even the top and middles functions seem pretty similar; why not just make the first and middle arguments to a function?
#include <iostream>
using namespace std;
void row(string first_last, string middle) {
cout << first_last;
for (int i=0; i<8; i++) cout << middle;
cout << first_last << endl;
}
int main() {
row("+", "-");
for (int j=0; j<5; j++) {
row("|", " ");
}
row("+", "-");
return 0;
}
Much better! But wait. We have this weird mix of functions and constants, with functions calling constants. Why do these even need to be functions at all? Why not just encode it all in the data?
One way to pull that off would be to have a tiny little running environment, which could count, print the current element, or do a newline. That might look something like this:
#include <iostream>
using namespace std;
void interp(string program) {
unsigned int program_counter = 0;
while (program_counter < program.size()) {
if ( '0' <= program[program_counter] && program[program_counter] <= '9') {
if (program[program_counter] == '0') {
// nop.
} else {
program[program_counter] = program[program_counter]-1;
program_counter -= 2;
}
} else if (program[program_counter]=='n') {
cout << endl;
} else {
cout << program[program_counter];
}
program_counter += 1;
}
}
int main() {
string program = "+-8+n| 8|n| 8|n| 8|n| 8|n| 8|n+-8+n";
interp(program);
return 0;
}
Fine. But now our data seems a bit redundant again, and that’s just inexcusable. One way we could deal with this is to permit recursion. I’ll handle this in a dumb way: just extracting the substrings between parenthesis and pass that to another interp instance.
#include <iostream>
using namespace std;
void interp(string program) {
// current index into string. (think of as a program counter!)
unsigned int program_counter=0;
string last_recurse="";
while (program_counter<program.length()) {
if ('0' < program[program_counter] && program[program_counter] <= '9') {
// 0-9 control looping:
--program[program_counter];
if ('0' != program[program_counter]) --program_counter;
else ++program_counter;
} else if ('(' == program[program_counter]) {
unsigned int openparen = 1, count = 0;
while (0 != openparen) {
++program_counter;
if (')' == program[program_counter]) {
--openparen;
if (0==openparen) break;
}
++count;
}
last_recurse = program.substr(program_counter-count, count);
}
if (')'==program[program_counter]) interp(last_recurse);
else if ('n'==program[program_counter]) cout << endl;
else cout << program[program_counter];
++program_counter;
}
}
int main() {
string sorig = "+-7+n(| 7|n)5+-7+nnn";
interp(sorig);
return 0;
}
Okay, but now we realize that we did the standard engineering thing and made a really ugly square, which looks nothing like the Square logo. Keeping the same interp function, we can simply change the data strings to generate something a bit different:
int main() {
// square in positive space
string spos="#8n# 6#n(# 2#2 2#n)2# 6#n#8n";
interp(spos);
// square in negative space
string sneg=" 8n #6 n( #2 2#2 n)2 #6 n 8n";
interp(sneg);
return 0;
}
yields
########
# #
# ## #
# ## #
# #
########
######
## ##
## ##
######
and that’s awesome.
There’s one more clever/silly thing we can try. Why have comments when you can write self-documenting code?
int main() {
string spos="square, >0 space:n#8n# 6#n(# 2#2 2#n)2# 6#n#8nnn";
interp(spos);
string sneg="square, <0 space:n 8n #6 n( #2 2#2 n)2 #6 n 8n";
interp(sneg);
return 0;
}
Code on Github: square-lang.