This challenge was first posted on Slack #puzzles channel
This puzzle is based on the results of a Duplicate Bridge session I played in recently. For Bridge afficionados, this was with 7 full tables, Hesitation Mitchell with relay between tables 3 and 4. Essentially, this means that there were 14 pairs (numbered 1 to 14) - 2 pairs per table, playing 24 boards (sets of cards) in 8 rounds of 3-board sets. After playing 3 boards, one of the pairs (usually the East/West pair) move to the next table and the boards (as a set of 3) move in the opposite direction. The details of the movement are not particularly relevant to solving this puzzle (and details can be found through an internet search, if you are interested).
This is a SPL challenge to determine the average percentage score for each pair.
The results from each board are your input and look something like this:
N/S E/W Bid By Tks N/S E/W
1 8 2diamond N 8 90
3 12 4heart E 11 450
4 7 5diamond N 8 150
5 9 3diamond N 10 130
6 11 4heart E 10 420
10 2 4heart E 10 420
14 13 2heart E 11 200
N/S E/W Bid By Tks N/S E/W
1 8 2heart E 10 170
3 12 3heart E 7 100
4 7 2heart E 10 170
5 9 4heart E 8 100
6 11 3NT W 9 400
10 2 4heart E 10 420
14 13 3NT W 9 400The scores for each board starts with a header: This is followed by the results for each time this board was played. Each result shows:
Where no score is present for a pair, it can be assumed that they score the negative of their opponent's score, e.g. if N/S scored 100, it can be assumed the E/W scored -100. (Details of how the scores are derived are not particularly relevant for this puzzle, and indeed insufficient information has been provided for you to be able to check whether they are correct anyway!)
To normalise the scores, each pair's score for a board is compared to all the other scores for pairs playing in the same orientation, N/S vs N/S or E/W vs E/W. Pairs get 1 point for each other pair they do better than, and another point for each pair they equal or do better than. For example:
In this instance:
These can be converted to percentages - given that the maximum possible points in this instance is 6:
At the end of the session, pairs are ranked by their average percentage score for the boards that they played, i.e. if a pair did not have a chance to bid or play a board, this does not affect their average.
The full score sheet can be found here.
This article contains spoilers!
In fact, the whole article is a spoiler as it contains solutions to the puzzle. If you are trying to solve the puzzle yourself and just want some pointers to get you started, stop reading when you have enough, and return if you get stuck again, or just want to compare your solution to mine!
In order to calculate the average percentage score for each pair, we need to break down the data to individual scores.
The first thing to do is to split the large string into separate boards. Depending on how you set your data up, you may have been able to do this when it was ingested, or you could have just pasted it into a field (as I have done), and then split it using rex.
| makeresults
| fields - _time
| eval _raw="N/S E/W Bid By Tks N/S E/W
1 8 2diamond N 8 90
3 12 4heart E 11 450
4 7 5diamond N 8 150
5 9 3diamond N 10 130
6 11 4heart E 10 420
10 2 4heart E 10 420
14 13 2heart E 11 200
N/S E/W Bid By Tks N/S E/W
1 8 2heart E 10 170
3 12 3heart E 7 100
4 7 2heart E 10 170
5 9 4heart E 8 100
6 11 3NT W 9 400
10 2 4heart E 10 420
14 13 3NT W 9 400
...
N/S E/W Bid By Tks N/S E/W
2 11 3NT S 10 430
3 13 3NT S 12 490
4 8 3NT S 11 460
5 10 4heart N 8 100
6 12 3club S 10 130
7 14 4spade N 9 50
9 1 3spade N 10 170 "
| rex max_match=0 "(?ms)(?<board>N\/S\sE\/W\sBid\sBy\sTks(\sN\/S\sE\/W)((\s\d+\s\d+\s(Pass\s{5}|[1-7](club|diamond|heart|spade|NT)\**\s[ENSW]\s\d*\s\d*\s\d*))*)(?=N|$))"
| fields - _raw
| mvexpand boardNote the ellipsis to signify missing data.
This regular expression assume that a "board" is made up of the header followed a number entries for each time the board was played. Note that the expression is also validating the input, i.e. anything that does not match will not be picked up. At this point, it is worth checking that the boards look complete and accurate.
Next, give the boards a number. This could be done a couple of ways:
| streamstats count as board_numberAlternatively, mvexpand has already generated a hidden field with a value for each entry in the multi-value field.
| rename _mkv_child as board_number
| eval board_number=board_number+1Next, we need to create an event for each time the board was played at a table.
| rex max_match=0 field=board "(?ms)(?<table>\d+\s\d+\s(Pass\s|[1-7](club|diamond|heart|spade|NT)\**)\s[ENSW]*\s\d*\s\d*\s\d*)"
| fields - board
| mvexpand table
| rename _mkv_child as table_instance
| eval table_instance=table_instance+1Again, note that the regular expression validates the contents of the entries and mvexpand provides an id for the instance that the board was played.
For each time a board was played, we need to extract which pairs were playing, what the contract was and the score etc.
| rex field=table "(?ms)(?<NS>\d+)\s(?<EW>\d+)\s(?<Contract>(Pass\s|[1-7](club|diamond|heart|spade|NT)\**))\s(?<Declarer>[NEWS])*\s(?<Tricks>\d+)*\s(?<ScoreNS>\d*)\s(?<ScoreEW>\d*)"
| fields - tableYou may have noticed that the data only includes the positive scores, so we need to complete the entries by inserting the negative scores appropriately. Where there is no score, this is scored as zero.
| eval ScoreNS=if(ScoreNS="",-ScoreEW,ScoreNS)
| eval ScoreEW=-ScoreNS
| fillnull value=0 ScoreNS ScoreEWTo find out how each North/South pair did against each other North/South pair playing the same board, we need to sort the N/S scores for the board.
| sort 0 board_number ScoreNS table_instanceSince these are all numerics, we will get a numeric (not lexicographical) sort order. Now we need to know what the other N/S scores were.
| eventstats list(ScoreNS) as BoardNS by board_numberWe can now use the mvfind() function to return an index to the first instance of our score in the list. This effectively is the number of other N/S scores we did better than.
| eval NSPosition=mvfind(BoardNS, "^".ScoreNS."$")Now we can reverse the list and find out how many other N/S scores are better than ours.
| eval BoardNS=mvreverse(BoardNS)
| eval SNPosition=mvfind(BoardNS, "^".ScoreNS."$")This is not quite what we need, since we want to know how many other N/S scores we did better or the same as. This is simply done by subtracting the previous value from one less that the total number of scores for this board.
| eval SNPosition=mvcount(BoardNS)-1-mvfind(BoardNS, "^".ScoreNS."$")Now we can determine the score for N/S for this board.
| eval PointsNSPosition=NSPosition+SNPositionTo normalise this, we need to calculate the percentage score for the N/S pair on each board that they played.
| eval MaximumPoints=2*(mvcount(BoardNS)-1)
| fields - BoardNS NSPosition SNPosition
| eval PercentNSPositioni=round(PointsNSPosition*100/MaximumPoints,2)We need to determine the score for each pair playing the board. So, we double up the results and assign a score to each pair depending on whether they were N/S or E/W.
| eval Orientation=mvrange(0,2)
| mvexpand Orientation
| eval Pair=if(Orientation=0,NS,EW)
| eval Points=if(Orientation=0,PointsNSPosition, MaximumPoints-PointsNSPosition)Now, we can determine average percentage score for each pair.
| stats avg(eval(100*Points/MaximumPoints)) as AveragePercent by Pair
| eval AveragePercent=round(AveragePercent,2)Scores are usually presented at the end of the session in descending order.
| sort 0 -AveragePercentIn summary, the key to solving this puzzle is to recognise that some of the pairs play as N/S on some boards and as E/W on other boards, and that pairs are only scored against other pairs who played the same boards in the same orientation. Both of these factors need to be taken into account when calculating the average percentage score. Your result should look something like this.
| Pair | Average Percent |
| 2 | 72.92 |
| 1 | 67.36 |
| 7 | 61.81 |
| 10 | 56.94 |
| 4 | 56.94 |
| 5 | 52.78 |
| 6 | 52.43 |
| 8 | 47.22 |
| 3 | 46.87 |
| 13 | 43.75 |
| 12 | 42.01 |
| 14 | 34.03 |
| 9 | 32.64 |
| 11 | 32.29 |
Have questions or thoughts? Comment on this article or in Slack #puzzles channel. Whichever you prefer.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.