Personal Solutions to David Barber’s Bayesian Reasoning and Machine Learning (Chapter 1)
I’ve decided to post my solutions to the problems in David Barber’s Bayesian Reasoning and Machine Learning book. My hope is that people who are in self-study (such as myself) can compare answers with other people who are self-studying. These are my own solutions to the problems and should not be taken as reference. If you find any errors or would like to debate my answers, please reach out to me via email in the meantime (until I add a comments section). These will be updated regularly.
Chapter 1
Exercise 1.1
Prove \(p(x,y \mid z) = p(x\mid z)p(y\mid x,z)\)
Prove \(p(x\mid y,z) = \frac{p(y\mid x,z)p(x\mid z)}{p(y\mid z)}\)
Exercise 1.2
Prove the Bonferroni inequality \(p(a,b) \geq p(a) + p(b) - 1\)
By definition, probabilities are always less than or equal to 1. Therefore the statement is true.
Exercise 1.3
There are two boxes. Box 1 contains three red and five white balls and box 2 contains two red and five white balls. A box is chosen at random \(p(box = 1) = p(box = 2) = 0.5\) and a ball chosen at random from this box turns out to be red. What is the posterior probability that the red ball came from box 1?
We list the given information:
We use Bayes’ Rule to figure out the probability that the box is 1 given that the picked ball is red:
We calculate the numerator:
We find the marginal distribution of the color of the ball being red, so that we can substitute it in the denominator:
Finally, we substitute:
Exercise 1.4a
Two balls are placed in a box as follows: A fair coin is tossed and a white ball is placed in the box if a head occurs, otherwise a red ball is placed in the box. The coin is tossed again and a red ball is placed in the box if a tail occurs, otherwise a white ball is placed in the box. Balls are drawn from the box three times in succession (always with replacing the drawn ball back in the box). It is found that on all three occasions a red ball is drawn. What is the probability that both balls in the box are red?)
Because of the fair coin assumption:
We calculate the probabilities of the ball being of a certain color, given the flip of a coin:
We marginalize over the coin values to get the overall probability that a ball is white:
We do the same thing to get the overall probability that a ball is red:
We calculate the probability that a ball picked is red, taking into consideration the balls’ colors:
We use Bayes’ Rule to figure out the posterior probability that both balls are actually red, given that the three picks have been all red:
The probability of the color of Ball 1 is independent of the probability of the color of Ball 2:
Since there is replacement of the ball after picking one, we can also assume conditional independence:
In the denominator, we marginalize out the ball values over the joint distribution:
We substitute \(p(B_1=b_1, B_2=b_2)\) into the equation right above:
We make the summation explicit:
Again, the event of picking a ball is independent of picking another ball because of replacement. We already calculated the case when someone picks three balls with replacement and both balls are actually red, so we’ll just calculate the rest of the combinations:
Substitute in the values:
Finally, we substitute the values into Bayes’ Rule:
Exercise 1.4b
A secret government agency has developed a scanner which determines whether a person is a terrorist. The scanner is fairly reliable; 95% of all scanned terrorists are identified as terrorists, and 95% of all upstanding citizens are identified as such. An informant tells the agency that exactly one passenger of 100 aboard an aeroplane in which you are seated is a terrorist. The police haul off the plane the first person for which the scanner tests positive. What is the probability that this person is a terrorist?
“95% of all terrorists are identified as terrorists”:
We can infer from the former that only 5% of terrorists got mislabeled as good people (false negative):
“95% of all upstanding citizens are identified as such”:
We can infer, based on the previous equation, that 5% of good citizens get misclassified as terrorists (false positive):
Assuming that the informant is correct:
We can then infer that \(\frac{99}{100}\) are not terrorists:
We can find out the probability that the person picked is a terrorist using Bayes’ Rule:
To find out the denominator, we can marginalize over the joint distribution of \(p(label=true)\) and \(p(terrorist)\):
We make the summation explicit:
Substituting all the values into Bayes’ Rule, we find out that even though the detector is “reliable,” we still get a low posterior probability that the person suspected of being a terrorist is actually a terrorist:
Exercise 1.7
Repeat the Inspector Clouseau scenario, example(\(1.3\)), but with the restriction that either the maid or the butler is the murderer, but not both. Explicitly, the probability of the maid being the murderer and not the butler is \(0.04\), the probability of the butler being the murderer and not the maid is \(0.64\). Modify demoClouseau.m to implement this.)
Results:
p(butler\mid knife=used):
butler =murderer 0.755906
butler =not murderer 0.244094
Exercise 1.8
Prove \(p(a,(b\ or\ c)) = p(a,b)+p(a,c)−p(a,b,c)\)
Exercise 1.9
Prove:
Exercise 1.10
As a young man Mr Gott visits Berlin in \(1969\). He’s surprised that he cannot cross into East Berlin since there is a wall separating the two halves of the city. He’s told that the wall was erected \(8\) years previously. He reasons that : The wall will have a finite lifespan; his ignorance means that he arrives uniformly at random at some time in the lifespan of the wall. Since only \(5\%\) of the time one would arrive in the first or last \(2.5\%\) of the lifespan of the wall he asserts that with \(95\%\) confidence the wall will survive between \(8/0.975 \approx 8.2\) and \(8/0.025 = 320\) years. In \(1989\) the now Professor Gott is pleased to find that his prediction was correct and promotes his prediction method in prestigious journals. This ‘delta-t’ method is widely adopted and used to form predictions in a range of scenarios about which researchers are ‘totally ignorant’. Would you ‘buy’ a prediction from Prof. Gott? Explain carefully your reasoning.
I would think that it would be hard to be totally ignorant about something if we are really trying hard to create a good model that predicts the lifespan of something. In this specific case of the Berlin Wall, I believe that knowing about tensions between East Germany and West Germany would seriously change someone’s prior belief that it will fall in a certain period of time. Therefore, I would not ‘buy’ a prediction from Prof. Gott.
Exercise 1.11
Implement the soft XOR gate, example(\(1.7\)) using BRMLtoolbox. You may find condpot.m of use.)
Results:
octave:6> demoSoftXOR
p(a|c=0):
a =on 0.843585
a =off 0.156415
Exercise 1.12
Implement the hamburgers, example(1.2) (both scenarios) using BRMLtoolbox. To do so you will need to define the joint distribution \(p(hamburgers,KJ)\) in which \(dom(hamburgers) = dom(KJ) = \{tr, fa\}.\)
TODO
Exercise 1.13
Implement the two-dice example, section(\(1.3.1\)) using BRMLtoolbox.
Results:
octave:27> demoTwoDice
p(s1,s2|t=9):
s1 =one s2 =one 0.000000
s1 =two s2 =one 0.000000
s1 =three s2 =one 0.000000
s1 =four s2 =one 0.000000
s1 =five s2 =one 0.000000
s1 =six s2 =one 0.000000
s1 =one s2 =two 0.000000
s1 =two s2 =two 0.000000
s1 =three s2 =two 0.000000
s1 =four s2 =two 0.000000
s1 =five s2 =two 0.000000
s1 =six s2 =two 0.000000
s1 =one s2 =three 0.000000
s1 =two s2 =three 0.000000
s1 =three s2 =three 0.000000
s1 =four s2 =three 0.000000
s1 =five s2 =three 0.000000
s1 =six s2 =three 0.250000
s1 =one s2 =four 0.000000
s1 =two s2 =four 0.000000
s1 =three s2 =four 0.000000
s1 =four s2 =four 0.000000
s1 =five s2 =four 0.250000
s1 =six s2 =four 0.000000
s1 =one s2 =five 0.000000
s1 =two s2 =five 0.000000
s1 =three s2 =five 0.000000
s1 =four s2 =five 0.250000
s1 =five s2 =five 0.000000
s1 =six s2 =five 0.000000
s1 =one s2 =six 0.000000
s1 =two s2 =six 0.000000
s1 =three s2 =six 0.250000
s1 =four s2 =six 0.000000
s1 =five s2 =six 0.000000
s1 =six s2 =six 0.000000
Exercise 1.14
A redistribution lottery involves picking the correct four numbers from \(1\) to \(9\) (without replacement, so \(3,4,4,1\) for example is not possible). The order of the picked numbers is irrelevant. Every week a million people play this game, each paying \(\unicode{163} 1\) enter, with the numbers \(3,5,7,9\) being the most popular (\(1\) in every \(100\) people chooses these numbers). Given that the million pounds prize money is split equally between winners, and that any four (different) numbers come up at random, what is the expected amount of money each of the players choosing \(3,5,7,9\) will win each week? The least popular set of numbers is \(1,2,3,4\) with only \(1\) in \(10,000\) people choosing this. How much do they profit each week, on average? Do you think there is any ‘skill’ involved in playing this lottery?
One out of a hundred people pick the set \(\{3, 5, 7, 9\}\):
One out of a ten-thousand people pick the set \(\{1, 2, 3, 4\}\):
There are \(9\) options in the first pick, \(8\) in the second, \(7\) in the third, and \(6\) in the fourth, giving us \(3,024\) ordered ways.
One can arrange the numbers of any set of four numbers in \(24\) ways:
For the visual learners out there, there are the all the \(24\) possible ways of ordering a set with four numbers. Let’s use \(\{3,5,7,9\}\) for example:
Therefore, the probability of winning, without order mattering, is \(\frac{1}{126}\):
Amount of money to be distributed:
Number of people choosing the set \(\{3,5,7,9\}\):
Amount that someone wins, given that that person picked the set\(\{3,5,7,9\}\) and that those sets of numbers are the winning set:
Average amount that a person picking \(\{3,5,7,9\}\) wins, considering the likelihood of winning:
Amount that someone wins, given that that person picked the set\(\{1,2,3,4\}\) and that those sets of numbers are the winning set:
Average amount that a person picking \(\{1,2,3,4\}\) wins, considering the likelihood of winning:
Since the numbers are picked randomly and there is nothing that suggests that one set of four (unique) numbers is more likely to be picked than another, the only difference really is the number of people betting on a set of numbers. Someone cannot increase their chances of winning. However, by somehow figuring out which sets of numbers are the least popular and choosing the least popular one, this person can increase the possible amount of money won in the case that that person does win.
Exercise 1.15
In a test of ‘psychometry’ the car keys and wrist watches of \(5\) people are given to a medium. The medium then attempts to match the wrist watch with the car key of each person. What is the expected number of correct matches that the medium will make (by chance)? What is the probability that the medium will obtain at least \(1\) correct match?
There are \(120 \) ways to match the car keys with the wrist watches:
I automated the process of finding out the absolute frequency of each number of matches occuring (\(0\) matches, \(1\) matches, etc.):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
require 'spec_helper'
describe Psychometry do
describe 'when there are five pairs to be matched' do
describe '#absolute_frequencies_of_matches' do
it 'should return an array of absolute frequencies' do
# 0 matches occurs 44 times, 1 match occurs 45 times, etc.
psychometry = Psychometry.new
abs_freq = [44, 45, 20, 10, 0, 1]
expect(psychometry.absolute_frequencies_of_matches).
to eq abs_freq
end
end
describe '#permutations' do
before {@psychometry = Psychometry.new}
it 'should include ABCDE, EBACD' do
expect( @psychometry.permutations).
to include(['A', 'B', 'C', 'D', 'E'])
expect( @psychometry.permutations).
to include(['E', 'B', 'A', 'C', 'D'])
end
it 'should not include AAAAA' do
expect( @psychometry.permutations).
not_to include(['A', 'A', 'A', 'A', 'A'])
end
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Psychometry
def initialize
@num_pairs = 5
end
def absolute_frequencies_of_matches
permutations.inject(init_hash) do |accum, permutation|
accum[num_matches_in_one(permutation)] =
accum[num_matches_in_one(permutation)] + 1
accum
end
end
def permutations
combinations.inject([]) do |accum, combination|
permutation?(combination) ? (accum << combination) : accum
end
end
private
def num_matches
permutations.inject(0) do |accum, permutation|
accum += num_matches_in_one(permutation)
end
end
def num_matches_in_one(permutation)
(0..@num_pairs-1).inject(0) do |accum_2, index|
if permutation[index] == characters[index]
accum_2 += 1
else
accum_2
end
end
end
def init_hash
(0..@num_pairs).inject([]) { |accum, val| accum << 0 }
end
def combinations
characters.inject([]) do |accum_1, char_1|
accum_1 | characters.inject([]) do |accum_2, char_2|
accum_2 | characters.inject([]) do |accum_3, char_3|
accum_3 | characters.inject([]) do |accum_4, char_4|
accum_4 | characters.map do |char_5|
[char_1, char_2, char_3, char_4, char_5]
end
end
end
end
end
end
def permutation?(combination)
combination.uniq.length == combination.length
end
def characters
('A'..'Z').to_a[0..@num_pairs-1]
end
end
I found out through my program that the most likely number of matches to occur is one; there are \(45\) ways to get one match.
I also found out through my program that having no matches occurs \(44\) times:
We know that the probabilities of all states (nothing matching, one matching, …, all matching) must sum up to \(1\):
In short:
Therefore, the probability of getting at least one match is:
Just to increase my confidence in the correctness of my program, I want to see that the answers it gives regarding the number of times 5 matches, 4 matches, and 3 matches occurs make sense.
Let’s say we have the keys set up as follows:
Since there is only one way to get all \(5\) correctly:
We also know that getting \(4\) correctly actually is the same event as getting \(5\) correctly, since if we have \(4\) correctly matched, then the last one must match. If one does not match, then we know another set of watch-to-key will be a mismatch. In other words, \(4\) matching pairs cannot exist without the last one also matching. Therefore:
There are exactly \(10\) ways to getting only 3 correct.
Therefore:
These numbers are in line with the output of the program. Therefore, I am more confident that the program is correct.
Exercise 1.16.1
Show that for any function \(f\)
Exercise 1.16.2
Explain why, in general,
Exercise 1.17
Part I
(Inspired by singingbanana.com). Seven friends decide to order pizzas by telephone from Pizza4U based on a flyer pushed through their letterbox. Pizza4U has only \(4\) kinds of pizza, and each person chooses a pizza independently. Bob phones Pizza4U and places the combined pizza order, simply stating how many pizzas of each kind are required. Unfortunately, the precise order is lost, so the chef makes seven randomly chosen pizzas and then passes them to the delivery boy.
How many different combined orders are possible?
I got all the combinations and filtered out the duplicates. There seems to be \(120\) different combined orders:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
require 'spec_helper'
describe Pizza4U do
describe '#unique_combinations' do
it 'should not return any duplicates' do
pizza_4_u = Pizza4U.new
uniq_combs = pizza_4_u.unique_combinations
orig = ['A', 'A', 'A', 'A', 'A', 'A', 'B']
rearranged_1 = ['A', 'A', 'A', 'A', 'A', 'B', 'A']
rearranged_2 = ['A', 'B', 'A', 'A', 'A', 'A', 'A']
orig_1 = ['A', 'A', 'A', 'A', 'A', 'B', 'B']
rearranged_3 = ['A', 'B', 'A', 'B', 'A', 'A', 'A']
rearranged_4 = ['B', 'B', 'A', 'A', 'A', 'A', 'A']
orig_2 = [ 'B', 'B', 'D', 'D', 'D', 'D', 'D']
# should not be included because there are only four
# pizzas to choose from, not 5.
orig_3 = [ 'E','E','E','E','E','E','E' ]
expect(uniq_combs).to include(orig)
expect(uniq_combs).to include(orig_1)
expect(uniq_combs).to include(orig_2)
expect(uniq_combs).not_to include(orig_3)
expect(uniq_combs).not_to include(rearranged_1)
expect(uniq_combs).not_to include(rearranged_2)
expect(uniq_combs).not_to include(rearranged_3)
expect(uniq_combs).not_to include(rearranged_4)
expect(uniq_combs.count).to eq 120
end
end
describe '#accuracy(num_times)' do
it 'picks a random combination of pizzas (ordered by people) and compares it with another randomly-picked combination of pizzas (picked by the chef) and gives us a score' do
pizza_4_u = Pizza4U.new
accuracy = pizza_4_u.accuracy(1000000)
expect(accuracy).to eq 1/120.0
end
end
describe '#unique_absolute_freqs' do
it 'should return a dictionary with unique combinations as keys and absolute freqs as values' do
pizza_4_u = Pizza4U.new
unique_absolute_freqs = pizza_4_u.unique_absolute_freqs
total_count = unique_absolute_freqs.inject(0){|accum,(k,v)| accum = accum + v; accum}
expect(total_count).to eq 16384
expect(unique_absolute_freqs['AAAAAAA']).to be 1
expect(unique_absolute_freqs['AAAAAAB']).to be 7
end
end
describe '#prob_correct' do
it 'gives us the chance that the chef was correct' do
pizza_4_u = Pizza4U.new
expect(pizza_4_u.prob_correct.round(3)).to eq 0.018383.round(3)
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
require 'utils'
class Pizza4U
attr_reader :combinations, :unique_combinations, :combinations_size, :unique_absolute_freqs
def initialize
@utils = Utils.new(4)
@combinations = @utils.combinations(7)
@unique_combinations = combinations.map {|combination| combination.sort}.uniq
@combinations_size = combinations.count
@unique_absolute_freqs = unique_combinations.inject({}) do |accum, uniq_combo|
accum[uniq_combo.join] = combinations.select{|combination| same_unique_combo(combination, uniq_combo)}.count
accum
end
end
def accuracy(num_times)
score(num_times) / num_times.to_f
end
def prob_correct
corrects_of_joint_distribution.inject(0) do |accum, hash|
accum = accum + hash[:posterior_prob]
accum
end
end
private
def corrects_of_joint_distribution
joint_distribution.select{|item| item[:correct] == true}
end
def joint_distribution
unique_absolute_freqs.inject([]) do |accum_1, (chef_k,chef_v)|
accum_1 + unique_absolute_freqs.inject([]) do |accum_2, (customers_k,customers_v)|
posterior_prob = chef_v / combinations_size.to_f * customers_v / combinations_size.to_f
hash = { correct: false, posterior_prob: posterior_prob}
if chef_k == customers_k
hash[:correct] = true
end
accum_2 << hash
end
end
end
def score(num_times)
r = Random.new
(0..num_times-1).inject(0) do |accum, count|
customers_pick = pick(r)
chefs_pick = pick(r)
if same_unique_combo(customers_pick, chefs_pick)
accum = accum + 1
else
accum
end
end
end
def same_unique_combo(pick1, pick2)
pick1.sort == pick2.sort
end
def pick(r)
pick_index = r.rand * combinations.length - 1
combinations[pick_index]
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Utils
def initialize(num_pairs)
@num_pairs = num_pairs
end
def combinations(num_iteration)
combine(characters, num_iteration)
end
def combine(accum, num_iteration)
return accum if num_iteration <= 1
new_accum = characters.inject([]) do |accum_1, char|
accum_1 | accum.map do |item|
[char, item].flatten
end
end
combine(new_accum, num_iteration - 1)
end
def characters
('A'..'Z').to_a[0..@num_pairs-1]
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'spec_helper'
describe Utils do
describe '#combinations' do
it 'should return the right amount of items' do
num_items_to_choose_from = 4
num_times_to_combine = 7
utils = Utils.new(num_items_to_choose_from)
combinations = utils.combinations(num_times_to_combine)
expect(combinations.count).to eq 16384
expect(combinations).to include(['A', 'A', 'A', 'A', 'A', 'A', 'A'])
end
end
end
Part II
What is the probability that the delivery boy has the right order?
Let \(p(p_{x_y}=z)\) where \(dom(x)=\{chef, customer\}\), \(dom(y)=\{1,2,3,4\}\), and \(dom(z)=\{1,2,…,6,7\}\). \(y\) corresponds to the pizza options available to each customer, and \(z\) corresponds to the seven customers.
The overall correctness of the delivery person, regardless of what the chef or customers have picked can be represented as follows:
Calculating the probability that the delivery person has the correct order, regardless of what the customers and the chef actually chose, gives us \(1.84\%\).
Exercise 1.18
Sally is new to the area and listens to some friends discussing about another female friend. Sally knows that they are talking about either Alice or Bella but doesn’t know which. From previous conversations Sally knows some independent pieces of information: She’s \(90\%\) sure that Alice has a white car, but doesn’t know if Bella’s car is white or black. Similarly, she’s \(90\%\) sure that Bella likes sushi, but doesn’t know if Alice likes sushi. Sally hears from the conversation that the person being discussed hates sushi and drives a white car. What is the probability that the friends are talking about Alice? Assume maximal uncertainty in the absence of any knowledge of the probabilities.
“Sally knows that they are talking about either Alice or Bella but doesn’t know which.”:
“She’s \(90\%\) sure that Alice has a white car.” In other words, if the person is Alice, then the probability of the car being white is \(90\%\), etc:
From this we can infer that Sally thinks that if the person is Alice, then there’s only a \(10\%\) chance that the car is not white:
“…but doesn’t know if Bella’s car is white or black.”:
“Similarly, she’s \(90\%\) sure that Bella likes sushi”:
From this we can infer that Sally thinks that if the person is Bella then there’s only a \(10\%\) chance that the person hates sushi:
“… but doesn’t know if Alice likes sushi.”:
“Sally hears from the conversation that the person being discussed hates sushi and drives a white car.”:
“What is the probability that the friends are talking about Alice?”:
Therefore, Sally should be quite certain that the person her friends are talking about is Alice (\(90\%\)):
Exercise 1.19
The weather in London can be summarised as: if it rains one day there’s a \(70\%\) chance it will rain the following day; if it’s sunny one day there’s a \(40\%\) chance it will be sunny the following day.
From these likelihoods, I infer the following:
Part I
Assuming that the prior probability it rained yesterday is \(0.5\), what is the probability that it was raining yesterday given that it’s sunny today?
I infer from this that the probability of being sunny yesterday is also \(50\%\):
Thus, the probability of raining yesterday given that today is sunny:
Part 2
If the weather follows the same pattern as above, day after day, what is the probability that it will rain on any day (based on an effectively infinite number of days of observing the weather)?
On any day, not considering whether it rained or not the day before, the probability of raining is:
Part 3
Use the result from part \(2\) above as a new prior probability of rain yesterday and recompute the probability that it was raining yesterday given that it’s sunny today.)
Therefore:
Very interesting. The probability of raining has increased a little bit after updating the prior probabilities. Since it was likely that it rained yesterday, it’s slightly more likely that it will rain today.
Exercise 1.20
A game of Battleships is played on a \(10 \times 10\) pixel grid. There are two \(5\)-pixel length ships placed uniformly at random on the grid, subject to the constraints that (i) the ships cannot overlap and (ii) one ship is vertical and the other horizontal. After 10 unsuccessful ‘misses’ in locations \((1,10)\),\((2,2)\),\((3,8)\),\((4,4)\),\((5,6)\),\((6,5)\),\((7,4)\),\((7,7)\),\((9,2)\),\((9,9)\) calculate which pixel has the highest probability of containing a ship. State this pixel and the value of the highest probability.
Here’s a diagram:
The way I computed the probability of a given point being a hit is by using the available ships that I have (\(1\) horizontal and \(1\) vertical) and sliding them across the board; if the ship occupies valid points (points on the board and no point being labeled as “miss”), then we update the absolute frequencies of those points by one. The points with the highest (absolute and relative) frequencies are good candidates to be pegged next.
Here are the computed absolute frequencies:
The points with the most likely hits are the following \((1,5)\), \((2,7)\), \((3,3)\), \((4,9)\), \((5,1)\), \((5,3)\),\((6,10)\),\((8,3)\),\((8,6)\),\((8,8)\), \((10,6)\). The probability of the ship being in one of these points is \(0.02\).
Here’s the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
module Battleship
class Board
attr_reader :table
def initialize(init_hash)
@misses = init_hash[:misses]
@ships = init_hash[:ships]
@table = Battleship::Table.new(row_length:row_length,
col_length: col_length,
misses: @misses,
ships: @ships)
# table should listen to changes in ship status
# if hit, then recalculate frequencies...
@table.recalculate_abs_freq!
end
def miss?(point)
@misses.select {|miss| miss.same_as?(point) }.count >= 1
end
def row_length
10
end
def col_length
10
end
def best_targets
@table.select {|point| point.abs_freq == @table.max_abs_freq}
end
def abs_freqs
@table.rows.map do |row|
row.map do |point|
point.abs_freq
end
end
end
def rel_freqs
abs_freqs.map do |row|
row.map do |abs_freq|
abs_freq / @table.sum_of_abs_freqs.to_f
end
end
end
def abs_freq_at(point)
point_at(point).abs_freq
end
def point_at(*args)
@table.point_at(args)
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
require 'spec_helper'
describe Battleship::Board do
before do
@miss_1 = Battleship::Point.new(1, 10, :missed)
@miss_2 = Battleship::Point.new(2, 2, :missed)
@miss_3 = Battleship::Point.new(3, 8, :missed)
@miss_4 = Battleship::Point.new(4, 4, :missed)
@miss_5 = Battleship::Point.new(5, 6, :missed)
@miss_6 = Battleship::Point.new(6, 5, :missed)
@miss_7 = Battleship::Point.new(7, 4, :missed)
@miss_8 = Battleship::Point.new(7, 7, :missed)
@miss_9 = Battleship::Point.new(9, 2, :missed)
@miss_10 = Battleship::Point.new(9, 9, :missed)
@misses = [@miss_1, @miss_2, @miss_3, @miss_4, @miss_5,
@miss_6, @miss_7, @miss_8, @miss_9, @miss_10 ]
@ship_1 = Battleship::VerticalShip.new(length: 5)
@ship_2 = Battleship::HorizontalShip.new(length: 5)
@ships = [@ship_1, @ship_2]
@board = Battleship::Board.new(misses: @misses, ships: @ships)
end
describe '#row_length' do
it 'should return 10' do
expect(@board.row_length).to eq 10
end
end
describe '#col_length' do
it 'should return 10' do
expect(@board.col_length).to eq 10
end
end
describe '#miss?(point)' do
it 'should return true if the point was a miss' do
miss_point = Battleship::Point.new(1, 10 , :missed)
expect( @board.miss?(miss_point)).to eq true
end
it 'should return false if the point was not a miss' do
miss_point = Battleship::Point.new(1, 9 , :missed)
expect( @board.miss?(miss_point)).to eq false
end
end
describe '#point_at(row, col)' do
it 'should return the point with those attributes' do
row = 1
col = 1
point = @board.point_at(row, col)
expect(point.row).to eq row
expect(point.col).to eq col
end
end
describe '#abs_freq_at(point)' do
it'should return the number of possible hits' do
point_of_interest = Battleship::Point.new(1, 1, :missed)
expect(@board.abs_freq_at(point_of_interest)).to eq 2
end
end
describe '#best_targets' do
it 'returns the points with the highest likelihood of overlapping with a ship' do
def has_point(some_point, occupied_points)
occupied_points.any? {|point| point.row == some_point.row && point.col == some_point.col}
end
expect(has_point(Battleship::Point.new(1,5), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(2,7), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(3,3), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(4,9), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(5,1), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(5,3), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(6,10), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(8,3), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(8,6), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(8,8), @board.best_targets)).to eq true
expect(has_point(Battleship::Point.new(10,6), @board.best_targets)).to eq true
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
module Battleship
class Table
include Enumerable
attr_reader :row_length, :col_length
def initialize(hash)
@row_length = hash.fetch(:row_length)
@col_length = hash.fetch(:col_length)
@ships = hash.fetch(:ships)
@misses = hash.fetch(:misses)
recreate!
end
def max_abs_freq
self.max {|point1, point2| point1.abs_freq <=> point2.abs_freq}.abs_freq
end
def rows
@table
end
def recreate!
@table = (1..row_length).inject([]) do |accum1, item1 |
accum1 << (1..col_length).inject([]) do |accum2, item2|
accum2 << Battleship::Point.new(item1, item2)
end
end
@misses.each {|miss| point_at(miss).miss!}
end
def each(&block)
(1..row_length).each do |row|
(1..col_length).each do |col|
block.call(point_at([row,col]))
end
end
end
def sum_of_abs_freqs
self.inject(0) {|accum, point| accum = accum + point.abs_freq }
end
def recalculate_abs_freq!
recreate!
@ships.each do |ship|
ship.table = self
self.each do |point|
ship.start_at(point)
ship.abs_freq!
end
end
end
def point_at(*args)
args.flatten!
row, col = 0,0
if args.length == 1
point = args.first
row, col = point.row, point.col
else
row = args[0]
col = args[1]
end
@table[row-1][col-1]
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'spec_helper'
describe Battleship::Table do
describe '#rows' do
it 'should return the rows' do
ships = []
misses = []
hash = {row_length: 2, col_length: 1, ships: ships, misses: misses}
table = Battleship::Table.new(hash)
table.recreate!
expect(table.rows[0][0].row).to eq 1
expect(table.rows[0][0].col).to eq 1
expect(table.rows[1][0].row).to eq 2
expect(table.rows[1][0].col).to eq 1
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Battleship
class VerticalShip < Battleship::Ship
def occupies_point?(row, col)
starting_point.col == col &&
(starting_point.row...starting_point.row + @length).any? {|item| item == row}
end
def fully_onboard?
@table.row_length >= @starting_point.row - 1 + @length
end
def occupied_points
(@starting_point.row...@starting_point.row + @length).map do |row|
@table.point_at(row, @starting_point.col)
end
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
require 'spec_helper'
describe Battleship::VerticalShip do
before do
@starting_point = Battleship::Point.new(1,1)
@table = (1..3).map do |row|
(1..3).map do |col|
Battleship::Point.new(row,col)
end
end
def @table.row_length; 3; end
def @table.col_length; 3; end
def @table.point_at(row, col)
self[row-1][col-1]
end
@table[2][2].miss!
def has_point(some_point, occupied_points)
occupied_points.any? {|point| point.row == some_point.row && point.col == some_point.col}
end
end
describe '#occupied_points' do
it 'returns a list of occupied points' do
def @table.point_at(row, col)
self[row-1][col-1]
end
vertical_ship = Battleship::VerticalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
occupied_points = vertical_ship.occupied_points
expect(has_point(Battleship::Point.new(1,1), occupied_points)).to eq true
expect(has_point(Battleship::Point.new(2,1), occupied_points)).to eq true
expect(has_point(Battleship::Point.new(3,1), occupied_points)).to eq false
end
end
describe '#occupies_a_missed_point?' do
it 'returns true if ship occupies a missed point' do
starting_point = Battleship::Point.new(2,3)
vertical_ship = Battleship::VerticalShip.new(length: 2,
table: @table,
starting_point: starting_point)
expect(vertical_ship).to be_occupies_a_missed_point
end
it 'returns false if ship does not occupy a missed point' do
starting_point = Battleship::Point.new(1,1)
vertical_ship = Battleship::VerticalShip.new(length: 2,
table: @table,
starting_point: starting_point)
expect(vertical_ship).not_to be_occupies_a_missed_point
end
end
describe '#occupies?(point)' do
it 'returns true if occupies the point' do
vertical_ship = Battleship::VerticalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
expect(vertical_ship.occupies_point?(1,1)).to eq true
expect(vertical_ship.occupies_point?(2,1)).to eq true
expect(vertical_ship.occupies_point?(3,1)).to eq false
end
end
describe '#fully_onboard?' do
it 'returns true if no part of the ship is off the board' do
vertical_ship = Battleship::VerticalShip.new(length: 2,
orientation: :vertical,
table: @table,
starting_point: @starting_point)
expect(vertical_ship).to be_fully_onboard
vertical_ship.start_at(Battleship::Point.new(2,1))
expect(vertical_ship).to be_fully_onboard
end
it 'returns false if part of the ship is off the board' do
vertical_ship = Battleship::VerticalShip.new(length: 2,
orientation: :vertical,
table: @table,
starting_point: @starting_point)
vertical_ship.start_at(Battleship::Point.new(3,1))
expect(vertical_ship).not_to be_fully_onboard
end
end
describe '#abs_freq!' do
it 'should update the values of occupied points' do
vertical_ship = Battleship::VerticalShip.new(length: 2,
orientation: :vertical,
table: @table,
starting_point: @starting_point)
vertical_ship.abs_freq!
expect(@table.point_at(1,1).abs_freq).to eq 1
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Battleship
class HorizontalShip < Battleship::Ship
def fully_onboard?
@table.col_length >= @starting_point.col - 1 + @length
end
def occupies_point?(row, col)
starting_point.row == row &&
(starting_point.col...starting_point.col + @length).any? {|item| item == col}
end
def occupied_points
(@starting_point.col...@starting_point.col + @length).map do |col|
@table.point_at(@starting_point.row, col)
end
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
require 'spec_helper'
describe Battleship::HorizontalShip do
before do
@starting_point = Battleship::Point.new(1,1)
@table = (1..3).map do |row|
(1..3).map do |col|
Battleship::Point.new(row,col)
end
end
def @table.row_length; 3; end
def @table.col_length; 3; end
def has_point(some_point, occupied_points)
occupied_points.any? {|point| point.row == some_point.row && point.col == some_point.col}
end
@table[2][2].miss!
def @table.point_at(row, col)
self[row-1][col-1]
end
end
describe '#occupies?(point)' do
it 'returns true if occupies the point' do
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
expect(horizontal_ship.occupies_point?(1,1)).to eq true
expect(horizontal_ship.occupies_point?(1,2)).to eq true
expect(horizontal_ship.occupies_point?(1,3)).to eq false
end
end
describe '#occupied_points' do
it 'returns a list of occupied points' do
def @table.point_at(row, col)
self[row-1][col-1]
end
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
occupied_points = horizontal_ship.occupied_points
expect(has_point(Battleship::Point.new(1,1), occupied_points)).to eq true
expect(has_point(Battleship::Point.new(1,2), occupied_points)).to eq true
expect(has_point(Battleship::Point.new(1,3), occupied_points)).to eq false
end
end
describe '#occupies_a_missed_point?' do
it 'returns true if ship occupies a missed point' do
starting_point = Battleship::Point.new(3,2)
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: starting_point)
expect(horizontal_ship).to be_occupies_a_missed_point
end
it 'returns false if ship does not occupy a missed point' do
starting_point = Battleship::Point.new(2,2)
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: starting_point)
expect(horizontal_ship).not_to be_occupies_a_missed_point
end
end
describe '#occupies_valid_points?' do
it 'returns true if all points are valid' do
starting_point = Battleship::Point.new(1,1)
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: starting_point)
expect(horizontal_ship).to be_occupies_valid_points
end
it 'returns false if some points are not valid' do
starting_point = Battleship::Point.new(3,3)
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: starting_point)
expect(horizontal_ship).not_to be_occupies_valid_points
end
end
describe '#fully_onboard?' do
it 'returns true if no part of the ship is off the board' do
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
expect(horizontal_ship).to be_fully_onboard
horizontal_ship.start_at(Battleship::Point.new(1,2))
expect(horizontal_ship).to be_fully_onboard
end
it 'returns false if part of the ship is off the board' do
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
horizontal_ship.start_at(Battleship::Point.new(1,3))
expect(horizontal_ship).not_to be_fully_onboard
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module Battleship
class Ship
attr_reader :orientation, :starting_point
attr_accessor :table
def initialize(hash)
@length = hash.fetch(:length)
@table = hash.fetch(:table) { :no_table_initialized }
@starting_point = hash.fetch(:starting_point) { :no_starting_point }
end
def start_at(point)
@starting_point = point
end
def occupies_valid_points?
fully_onboard? && !occupies_a_missed_point?
end
def occupies_a_missed_point?
occupied_points.any? {|point| point.missed? }
end
def abs_freq!
occupied_points.each {|point| point.abs_freq += 1} if self.occupies_valid_points?
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
require 'spec_helper'
describe Battleship::Ship do
before do
@starting_point = Battleship::Point.new(1,1)
@table = (1..3).map do |row|
(1..3).map do |col|
Battleship::Point.new(row,col)
end
end
def @table.row_length; 3; end
def @table.col_length; 3; end
end
describe '#occupies?(point)' do
it 'returns true if occupies the point' do
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
expect(horizontal_ship.occupies_point?(1,1)).to eq true
expect(horizontal_ship.occupies_point?(1,2)).to eq true
expect(horizontal_ship.occupies_point?(1,3)).to eq false
end
end
describe '#occupied_points' do
it 'returns a list of occupied points' do
def @table.point_at(row, col)
self[row-1][col-1]
end
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
occupied_points = horizontal_ship.occupied_points
def has_point(some_point, occupied_points)
occupied_points.any? {|point| point.row == some_point.row && point.col == some_point.col}
end
expect(has_point(Battleship::Point.new(1,1), occupied_points)).to eq true
expect(has_point(Battleship::Point.new(1,2), occupied_points)).to eq true
expect(has_point(Battleship::Point.new(1,3), occupied_points)).to eq false
end
end
describe '#fully_onboard?' do
it 'returns true if no part of the ship is off the board' do
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
expect(horizontal_ship).to be_fully_onboard
horizontal_ship.start_at(Battleship::Point.new(1,2))
expect(horizontal_ship).to be_fully_onboard
end
it 'returns false if part of the ship is off the board' do
horizontal_ship = Battleship::HorizontalShip.new(length: 2,
table: @table,
starting_point: @starting_point)
horizontal_ship.start_at(Battleship::Point.new(1,3))
expect(horizontal_ship).not_to be_fully_onboard
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module Battleship
class Point
attr_reader :row, :col
attr_accessor :abs_freq
def initialize(row, col, *args)
@row = row
@col = col
@abs_freq = 0
@state = args.first || :untried
end
def hit!
@state = :hit
end
def hit?
@state == :hit
end
def miss!
@state = :missed
end
def missed?
@state == :missed
end
def untried?
@state == :untried
end
def same_as?(point)
row == point.row && col == point.col
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
require 'spec_helper'
describe Battleship::Point do
describe '#row' do
it 'should return the row' do
bp = Battleship::Point.new(1,2)
expect(bp.row).to eq 1
end
end
describe '#col' do
it 'should return the col' do
bp = Battleship::Point.new(1,2)
expect(bp.col).to eq 2
end
end
describe '#miss!' do
it 'sets the state of the point to :miss' do
bp = Battleship::Point.new(1,2)
bp.miss!
expect(bp).to be_missed
end
end
describe '#same_as?(point)' do
it 'should be true if point has the same coordinates as the given point' do
bp = Battleship::Point.new(1,2)
expect(bp.same_as?(bp)).to eq true
end
end
describe '#hit!' do
it 'sets the state of the point to :hit' do
bp = Battleship::Point.new(1,2)
bp.hit!
expect(bp).to be_hit
end
end
it 'should be untried at first' do
bp = Battleship::Point.new(1,2)
expect(bp).to be_untried
end
it 'can be initialized to being missed' do
bp = Battleship::Point.new(1,2, :missed)
expect(bp).to be_missed
end
end