Friday, November 28, 2008

Dice

I was sitting in a hotel room without any Internet access and without much to do... and I made this simple script, to roll die. It uses the date to generate a random number, and then limits it to the specified range, from 0 to N.

Here's the code:
 
1 #!/bin/bash
2 #
3 # Die roll script.
4 #
5 # Quick and dirty way to generate random numbers between one and N,
6 # just like a die roll simulation.
7 #
8 # Parameters:
9 # Optionally, a number of sides for the die,
10 # but if this is not supplied, 6 is used by default;
11 # if a series of parameters is provided, the script
12 # rolls a die for each of the parameters.
13 # Author:
14 # Konrad Siek
15
16 # Generates the random number from a date.
17 function roll {
18 expr \( $(date +%N) % "$1" \) + 1
19 }
20
21 # Do the actual die rolls.
22 if [ "$1" == "" ]
23 then
24 # If no argument is given, then roll a six-sider.
25 roll 6
26 else
27 # Roll a die for each of the arguments.
28 while [ "$1" != "" ]
29 do
30 roll $1
31 shift
32 done
33
34 fi
35


As you can see, the script is easy and fun. It's also quite useful, if you happen to find yourself wanting to play some war games or something.

I didn't know though, if it's reliable, so I wrote another short script - this one testing the previous one, by rolling a whole lot of rolls and checking the distribution of the results.

The fun thing though, was to create a short function inside, which divides some numbers and presents the result in a, more or less, human readable form. It's a neat little trick, even if I say so myself.

Here's the code:
 
1 #!/bin/bash
2 #
3 # Die roll testing script.
4 #
5 # Roll a die for a number of times and check the distribution
6 # that comes out.
7 #
8 # Potential issues:
9 # Can take quite a long time to finish.
10 # Parameters:
11 # 1. Number of sides, or six, if not specified,
12 # 2. Number of tests, or 100, if not specified.
13 # Author:
14 # Konrad Siek
15
16 # Human readable division function.
17 # Takes two arguments and returns a pretty fraction.
18 function divide {
19 d=`expr $1 / $2`
20 m=`expr $1 % $2`
21 if [ $m != 0 ]
22 then
23 echo "$d + ( $m / $2 )"
24 else
25 echo "$d"
26 fi
27 }
28
29 # Establish number of sides for the die.
30 if [ "$1" == '' ]
31 then
32 sides=6
33 else
34 sides=$1
35 fi
36
37 # Establish number of tosses.
38 if [ "$2" == '' ]
39 then
40 rolls=100
41 else
42 rolls=$2
43 fi
44
45 # Command to use for testing.
46 command='./d'
47
48 # Maybe it's a global command, you never know.
49 $command > /dev/null
50 if [ $? != '0' ]
51 then
52 command='d'
53 fi
54
55 # Roll the die, and check results.
56 $command > /dev/null
57 if [ $? != '0' ]
58 then
59 # Exit if the command is not found.
60 echo "Could not run commnd 'd' or './d'. Exiting..."
61 exit 1
62 fi
63
64 # Instantiate the result array.
65 for i in $(seq 1 $sides)
66 do
67 control[$i]=0;
68 done
69
70 # Evaluate the results.
71 sum='0';
72 count='0';
73
74 # Evaluate expected value in each 'class'.
75 expected=`divide $(expr $sides + 1) 2`
76
77 # Roll the die, count the results, and display it at each step.
78 for i in $(seq 1 $rolls)
79 do
80 component=`$command $sides`
81 sum=`expr $sum + $component`
82 count=`expr $count + 1`
83 control[$component]=`expr ${control[$component]} + 1`
84 echo -e "$i\t$component\t$sum\t$(divide $sum $count)"
85 done
86
87 # Evaluate the global.
88 value=`divide $sum $count`
89
90 # Display results.
91 echo
92 echo -e "expected value:\t$expected"
93 echo -e "actual value:\t$value"
94 echo -en "rolled:\t"
95
96 for i in $(seq 1 $sides)
97 do
98 echo -en "$i: ${control[$i]}\t";
99 done
100 echo


The code is also available at GitHub as bash/die.

Tuesday, November 11, 2008

Scripting in gedit

Hey. I didn't know this before, but you can run your own scripts out of gedit. Well, obviously, I was vaguely aware that this was possible, but I didn't know it was so gorram easy! So I've made some scripts that would help me script in the future.

All these scripts are installed by going to Tools > Extrenal Tools, then pressing New and then filling out the fields - most importantly, the Command(s) field.

Ok, here's what I've concocted.

First of all, I didn't want to go to the command line every time I want to make something executable, so there's this:
 
1 #!/bin/bash
2
3 chmod a+x $GEDIT_CURRENT_DOCUMENT_PATH


It's pretty self explanatory, methinks.

Then, I wanted to run a script from gedit, so I did this script:
 
1 #!/bin/bash
2
3 # Open the document's directory (just in case)
4 cd $GEDIT_CURRENT_DOCUMENT_DIR;
5
6 # Display information about script
7 echo -e "Running script: $GEDIT_CURRENT_DOCUMENT_NAME";
8 echo -e "\tin directory: $GEDIT_CURRENT_DOCUMENT_PATH";
9 echo -e "\n";
10
11 # Run script
12 $GEDIT_CURRENT_DOCUMENT_PATH;


It's mostly straightforward (just follow the comments). It displays a lot of info, because I'm the kind of guy who forgets what he ran just a second ago and needs constant reminding.

Finally, I added the ability to insert parameters with a GUI. I stole this concept from one of those default scripts that are put into gedit.
 
1 #!/bin/bash
2
3 # Get parameters for command from user.
4 params=`zenity --entry --title="Run $GEDIT_CURRENT_DOCUMENT_NAME" --text="with these parameters"`
5
6 # Open the document's directory (just in case)
7 cd $GEDIT_CURRENT_DOCUMENT_DIR;
8
9 # Display information about script
10 echo -e "Running script: $GEDIT_CURRENT_DOCUMENT_NAME";
11 echo -e "\tin directory: $GEDIT_CURRENT_DOCUMENT_PATH";
12 echo -e "\twith params: $params";
13 echo -e "\n";
14
15 # Run script
16 $GEDIT_CURRENT_DOCUMENT_PATH $params;


Here, in line 4 I call zenity to gather the parameters from the user and then just stick them to the end of the name of the script. Simple!

Hope this comes in handy.

The code is also available at GitHub in the directory gedit.

Monday, November 10, 2008

Word count

Hello. Long time, no see.

Well, I had no script idead and I've been busy. Alas, here's a short script for people enjoying the challenge of NaNoWriMo.

There's of course a couple of ways to count words. If you're using some sort of office suite it's probably built in, so no problem. If you're using LaTeX, like me (because I have a LaTeX fetish) you might have it in the tool you're using too, but it's less likely.

But you want to count words anyway, so what do you do?

Well, first of all, use the Linux wc command. It does well and there are no problems. Also, to get rid of the LaTeX code from the file you can use the untex tool, which has a ton of options to choose from to have a personalized and accurate experience of removing TeX tags from the code. You just read the tex files, and save the output somewhere...

So, most of what I did was to put it all together, like so:
 
1 #!/bin/bash
2 output=raw.txt
3 rm -f $output
4 for file in `ls chapter-*.tex`
5 do
6 untex -e $file >> $output
7 done
8 echo -e "Word count: \n wc\t$(cat $output | wc -w ) \n awk\t$(./count.awk $output)";


I set it up, so it only reads in files, whose names start with 'chapter-' and end with '.tex', because that is just the structure I use. However, the change to any other convention can easily be applied in line number 4 by parameterizing the ls command differently.

Additionally, it produces a raw.txt file as a side effect, which contains the actual text, which got the words counted, so if you want to verify untex or any of the counting mechanisms, you can do that easily.

Also, if you look closely, you will see that in line 8 there's something extra. I call an AWK script to provide some other word count. Here's how the script looks like inside:

#!/usr/bin/awk -f
{
    for (= 1; i <= NF; i++) {
        word = $i;
        #insert punctuation here, between the square brackets.
        n = split(word, a, /[-,.?!~`';:"'|\/@#$%^&*_-+={}\[\]<>()]+/); 
        for (= 1 ; j <= n; j++) {
            if (a[j] !~ /^[ \t\n]*$/) {                
                words++;
10            }
11        }
12    }
13}
14
15BEGIN {
16    words = 0;
17}
18
19END {
20    print words;
21}


What it actually does, is count the words, but unlike the wc command, it tries to recognize punctuation, and split words by the punctuation as well, so that hyphenated words are split. Also, it finds out stuff like long hyphens (LaTeX: '--') and removes them, so they are no longer counted as words.

I don't know which one is more accurate, but between the two, I can always have an optimistic and a pessimistic assumption about how many words I wrote.

The code is also available at GitHub as awk/count.awk
and bash/word_count.