Laravel Dusk assertSelected()

I’m using Laravel Dusk to do some automated testing. It’s great!

I found a “gotcha” today.

When using $browser->assertSelected($field, $value), if your $value variable is not a string, it will always fail. This is because the MakesAssertions trait’s ->selected($field, $value) function uses the triple equals (===) ensuring the values AND TYPES are the same for the two variables in question. And the value found in the selector will always be a string.

The easy workaround is just to cast your value as a string. Like so:

$value = 1;
$field = ‘selector_id’;
$browser->assertSelected($field, (string)$value);

The Deciding Vote

This week, I attempted the Riddler Classic:

Say you are the only sane voter in a state with two candidates running for Senate. There are N other people in the state, and each of them votes completely randomly! Those voters all act independently and have a 50-50 chance of voting for either candidate. What are the odds that your vote changes the outcome of the election toward your preferred candidate?

My initial hypothesis was 50%. The problem could be reworded this way: given a certain number of coin-flips, what is the likelihood that the count of heads (or tails for that matter) will equal exactly half of the total coin-flips?

I immediately regretted my first hypothesis, because – what if there are an odd number of coin-flips? It’s impossible to have a 50-50 split. So I re-read the question to see if I was thinking about it correctly. “What are the odds that your vote changes the outcome of the election toward your preferred candidate?” So, I assumed that switching a Win for my opponent to a Tie would qualify. Ok. So a result of “behind by 1 vote if there are an odd number of voters” is also a positive outcome.

Next, I drew up a spreadsheet to map out all the outcomes if there were 4 voters. That seemed like a low-enough number to wrap my head around, but also high enough, so as not to be trivial.

20161104-4voters

Huh. 6/16 or 3/8.

Let try more voters. How about 5. So there are 32 possible outcomes of the other 5 voters. But what happens when the vote is 3-2? Well, then it matters which color you’d vote for! In fact, that’s the only case in which your vote would matter. So, that adds another column to our table – which looks a lot like what you’d expect the table for 6 voters to look like!

20161104-5voters

8 Voters!

20161104-8voters

Well, the denominator is easy enough to calculate: 2n. But I still wasn’t catching on to the pattern for the numerator.

10 Voters!

20161104-10voters

Ok. Fine! It had to hit me over the head with a hammer, but I finally got it. Pascal’s Triangle. Sheesh, it’s embarrassing that it took so long to recognize it. Oh well.

So for any given number of voters, find that row in Pascal’s Triangle (for odd rows, go to row+1), and find the middle number, and that’s the number of scenarios in which your vote will decide the outcome. This number can also be calculated by: “n choose (n/2)” or “n!/(n/2)!*(n/2)!” or “n!/((n/2)!)2 (See Combination for more info).

As I could see (and assume), the more people who vote, the less likely it is that your vote will decide the outcome. That just makes sense. But now we have a formula for defining exactly how probable it is that you will matter.

For n = the number of voters…

n!/((n/2)!)2
—————–
2n

All of the screenshots from this post are from this spreadsheet, which also has an “Answers” tab which calculates this formula for even numbers between 4 and 100.

Gerrymandering

This week’s Riddler Express challenged us to rig an election!

Imagine your job is to draw districts and you happen to be a member of the Blue Party. The grid below gives the locations of 25 voters in a region, which you must divide into five districts with five voters each. In each district, the party with the most votes will win. The districts must be non-overlapping and contiguous (that is, each square in a district must share an edge with at least one other square in the district). Can you draw the districts such that the Blue Party wins more districts than the Red Party?

Salamander
Salamander this, Gerry

A quick count reveals that there are 9 Blue voters, which happens to be the minimum number of voters needed to win this election. To win an election with 5 District Votes, you need to secure 3 of them. In each of the Districts, the Blue team must win 3 votes. So the task is to distribute the votes in such a way that there are exactly 3 Blue votes in 3 different Districts. This will necessarily mean that the other 2 Districts will be solid Red.

The “contiguous” rule is the main constraint here, and isn’t it weird? It’s like saying, “You’re allowed to cheat. But try to make it look kinda like you’re not cheating.”

Anyways, there wasn’t much math to my method. I just messed around with an Excel spreadsheet with colored cells and thick borders. Here’s what I came up with:

Gerrymander

Everything is a game. Game theory drives the world!

“When you got skin in the game, you stay in the game.
But you don’t get a win unless you’re playing the game.
You get love for it. You get hate for.
You get nothing if you wait for it”

Building a Scrabble Word, One Letter at a Time

This week’s Riddler Express challenged us to find the longest word that can be built in a game of Scrabble one letter at a time. That is, starting with a valid two-letter word, how long a word can you build by playing one letter at a time on either side to form a valid three-letter word, then a valid four-letter word, and so on? (For example, HE could become THE, then THEM, then THEME, then THEMES, for a six-letter result.)

To solve this, I needed a database table of valid Scrabble words. I googled ‘scrabble dictionary text file’ and found jonbcard‘s github project that included a large text file with a bunch of words. Looks good to me.

I started a new Laravel project to track my work. That was probably overkill for this problem, but I’d like to maintain it as I solve more problems.

Once I got the scrabble_words table seeded with my dictionary file, I knew I could cobble together a query to solve this problem.

My first “gotcha” was a problem seeding the table. I exploded the words file on the new-line character. It was actually delimited by a new-line character AND a carriage-return. Oops. I figured this out when I was running a query like this:

select word, length(word) from scrabble_words limit 10;

And I got a result like this.

word length(word)
AA 3

Phantom character!

Anyways, a quick

update scrabble_words set word = substring(word, 1, length(word) – 1);

solved this problem.

It seemed that I’d need to have a ‘join’ on the scrabble_words table for each unique length. If the table was huge, I’d have added a column to the table which would hold the length of the word, so that I could index it. But we only have 178,691 words. So no biggie.

So, I wanted to see if I was thinking about the query correctly.

select w2.word, w3.word, w4.word
from (select word, id from scrabble_words where length(word) = 4) w4
inner join (select word, id from scrabble_words where length(word) = 3) w3 on w3.word = substring(w4.word, 1, 3) and w3.id <> w4.id
inner join (select word, id from scrabble_words where length(word) = 2) w2 on w2.word = substring(w4.word, 1, 2) and w2.id <> w4.id
limit 10;

That gave me:

word word word
AA AAH AAHS
AA AAL AALS
AB ABA ABAS
AB ABO ABOS
AB ABY ABYE
AB ABY ABYS
AD ADD ADDS
AD ADO ADOS
AD ADZ ADZE
AG AGA AGAR

So it looked like I was on the right track.

A few more iterations landed me on this query. (Sorry, it looks kinda nasty here. But if you copy it into MySQL Workbench, it’s beautiful!)

select w2.word, w3.word, w4.word, w5.word, w6.word, w7.word, w8.word
from (select word, id from scrabble_words where length(word) = 8) w8
inner join (select word, id from scrabble_words where length(word) = 7) w7 on w7.word = substring(w8.word, 1, 7) and w7.id <> w8.id
inner join (select word, id from scrabble_words where length(word) = 6) w6 on w6.word = substring(w8.word, 1, 6) and w6.id <> w8.id
inner join (select word, id from scrabble_words where length(word) = 5) w5 on w5.word = substring(w8.word, 1, 5) and w5.id <> w8.id
inner join (select word, id from scrabble_words where length(word) = 4) w4 on w4.word = substring(w8.word, 1, 4) and w4.id <> w8.id
inner join (select word, id from scrabble_words where length(word) = 3) w3 on w3.word = substring(w8.word, 1, 3) and w3.id <> w8.id
inner join (select word, id from scrabble_words where length(word) = 2) w2 on w2.word = substring(w8.word, 1, 2) and w2.id <> w8.id
limit 10;

Which gave me (what looked to be) a more correct answer: 8

word word word word word word word
BA BAR BARB BARBE BARBEL BARBELL BARBELLS
MA MAX MAXI MAXIM MAXIMA MAXIMAL MAXIMALS
PA PAS PAST PASTE PASTER PASTERN PASTERNS
RE REP REPO REPOS REPOSE REPOSER REPOSERS

I added another join to the query to get it up to 9 (I’ll spare you the even nastier query), and got zero results. So my final answer is…. a 4-way tie at 8!

Glad to see Barb made it in. Long live Barb.

Edit: OOPS! I just realized I was only considering a Scrabble move that adds a letter to the END of the word. Back to the queries!

Okay… I updated the query to account for my discovery

select w2.word, w3.word, w4.word, w5.word, w6.word, w7.word, w8.word, w9.word
from (select word, id from riddler538.scrabble_words where length(word) = 9) w9
inner join (select word, id from riddler538.scrabble_words where length(word) = 8) w8 on instr(w9.word, w8.word) > 0
inner join (select word, id from riddler538.scrabble_words where length(word) = 7) w7 on instr(w8.word, w7.word) > 0
inner join (select word, id from riddler538.scrabble_words where length(word) = 6) w6 on instr(w7.word, w6.word) > 0
inner join (select word, id from riddler538.scrabble_words where length(word) = 5) w5 on instr(w6.word, w5.word) > 0
inner join (select word, id from riddler538.scrabble_words where length(word) = 4) w4 on instr(w5.word, w4.word) > 0
inner join (select word, id from riddler538.scrabble_words where length(word) = 3) w3 on instr(w4.word, w3.word) > 0
inner join (select word, id from riddler538.scrabble_words where length(word) = 2) w2 on instr(w3.word, w2.word) > 0
limit 100;

That query ran for about 175 seconds and yielded these 35 results:

AS ASS LASS LASSI LASSIE GLASSIE GLASSIES GLASSIEST
AS ASS LASS LASSI LASSIE LASSIES GLASSIES GLASSIEST
AS ASS LASS LASSI LASSIS CLASSIS CLASSISM CLASSISMS
AS ASS LASS LASSI LASSIS CLASSIS CLASSIST CLASSISTS
AS LAS LASS LASSI LASSIE GLASSIE GLASSIES GLASSIEST
AS LAS LASS LASSI LASSIE LASSIES GLASSIES GLASSIEST
AS LAS LASS LASSI LASSIS CLASSIS CLASSISM CLASSISMS
AS LAS LASS LASSI LASSIS CLASSIS CLASSIST CLASSISTS
LA LAS LASS LASSI LASSIE GLASSIE GLASSIES GLASSIEST
LA LAS LASS LASSI LASSIE LASSIES GLASSIES GLASSIEST
LA LAS LASS LASSI LASSIS CLASSIS CLASSISM CLASSISMS
LA LAS LASS LASSI LASSIS CLASSIS CLASSIST CLASSISTS
AI AIS RAIS RAISE PRAISE PRAISER PRAISERS UPRAISERS
AI AIS RAIS RAISE PRAISE PRAISER UPRAISER UPRAISERS
AI AIS RAIS RAISE PRAISE UPRAISE UPRAISER UPRAISERS
AI AIS RAIS RAISE RAISER PRAISER PRAISERS UPRAISERS
AI AIS RAIS RAISE RAISER PRAISER UPRAISER UPRAISERS
AI AIS RAIS RAISE RAISER RAISERS PRAISERS UPRAISERS
AI RAI RAIS RAISE PRAISE PRAISER PRAISERS UPRAISERS
AI RAI RAIS RAISE PRAISE PRAISER UPRAISER UPRAISERS
AI RAI RAIS RAISE PRAISE UPRAISE UPRAISER UPRAISERS
AI RAI RAIS RAISE RAISER PRAISER PRAISERS UPRAISERS
AI RAI RAIS RAISE RAISER PRAISER UPRAISER UPRAISERS
AI RAI RAIS RAISE RAISER RAISERS PRAISERS UPRAISERS
AT EAT EATH HEATH SHEATH SHEATHE SHEATHER SHEATHERS
AT EAT HEAT HEATH SHEATH SHEATHE SHEATHER SHEATHERS
IN PIN PING APING RAPING CRAPING SCRAPING SCRAPINGS
IS AIS RAIS RAISE PRAISE PRAISER PRAISERS UPRAISERS
IS AIS RAIS RAISE PRAISE PRAISER UPRAISER UPRAISERS
IS AIS RAIS RAISE PRAISE UPRAISE UPRAISER UPRAISERS
IS AIS RAIS RAISE RAISER PRAISER PRAISERS UPRAISERS
IS AIS RAIS RAISE RAISER PRAISER UPRAISER UPRAISERS
IS AIS RAIS RAISE RAISER RAISERS PRAISERS UPRAISERS
LA LAP LAPS LAPSE ELAPSE RELAPSE RELAPSER RELAPSERS
PI PIN PING APING RAPING CRAPING SCRAPING SCRAPINGS

Now we’re getting closer. Next, I ran the query with words up to 10 characters long. It yielded no results!

So, our answer is a 35-way tie for first place, with 9 characters.

It’s sad to see Barb leave. Again.