Last active
April 5, 2019 21:36
-
-
Save kieraneglin/867b1105ea87b0742a8609695671ec31 to your computer and use it in GitHub Desktop.
B99 islander problem in Elixir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule IslanderProblem do | |
@total_islanders 12 | |
@group_size 4 | |
# Weighs islanders 3 times, finding which islander is the outlier | |
# | |
# Returns an integer representing the weight of the outlier | |
def start(islanders) when is_list(islanders) do | |
if length(islanders) != @total_islanders do | |
raise ArgumentError, "There must be exactly #{@total_islanders} islanders" | |
end | |
[first_group, second_group, third_group] = split_into_groups(islanders) | |
case check_balance(first_group, second_group) do | |
-1 -> second_weighing_of_initial_groups(first_group, second_group, third_group) # Issue is in first group | |
1 -> second_weighing_of_initial_groups(second_group, first_group, third_group) # Issue is in second group | |
0 -> weigh_and_divide_third_group(first_group, third_group) # Add first_group (arbitrarily) as a control | |
end | |
end | |
# Splits islanders into 3 groups of 4 (assumung 12 islanders total) | |
# | |
# Returns a list of 4-element lists | |
defp split_into_groups(islanders) do | |
Enum.chunk_every(islanders, @group_size) | |
end | |
# Takes two lists of weights and compares them. | |
# | |
# Returns -1 when first list is heavier | |
# Returns 1 when second list is heavier | |
# Returns 0 when lists are equal | |
defp check_balance(group_a, group_b) when is_list(group_a) do | |
case Enum.sum(group_b) - Enum.sum(group_a) do | |
x when x < 0 -> -1 | |
x when x > 0 -> 1 | |
_ -> 0 | |
end | |
end | |
# Takes two integer weights and compares them. | |
# | |
# Returns -1 when first number is heavier | |
# Returns 1 when second number is heavier | |
# Returns 0 when numbers are equal | |
defp check_balance(number_a, number_b) do | |
case number_b - number_a do | |
x when x < 0 -> -1 | |
x when x > 0 -> 1 | |
_ -> 0 | |
end | |
end | |
# Takes a light, heavy, and control group then shuffles it before comparing | |
# it further, allowing us to determine which sub-group an outlier belongs to | |
defp second_weighing_of_initial_groups(lighter_group, heavier_group, control_group) do | |
# lighter group will be referred to as 1, 2, 3, 4 | |
# heavier group will be referred to as 5, 6, 7, 8 | |
# control group will be referred to as a, b, c, d | |
[single_lighter | remaining_lighter] = lighter_group # Create groups of 1 | [2, 3, 4] | |
[single_heavier | remaining_heavier] = heavier_group # Create groups of 5 | [6, 7, 8] | |
[single_control | remaining_control] = control_group # Create groups of a | [b, c, d] | |
lighter_and_heavier = [single_lighter] ++ remaining_heavier # Create a list of [1, 6, 7, 8] | |
control_and_heavier = [single_heavier] ++ remaining_control # Create a list of [5, b, c, d] | |
case check_balance(control_and_heavier, lighter_and_heavier) do | |
# If the scale stays the same (-1 -> -1), the outlier must be in the remaining heavier group | |
-1 -> weigh_initial_group_remainders(remaining_heavier) | |
# If the scale shifts from the first weighing (-1 -> +1), weigh the shuffled members against a control | |
1 -> weigh_initial_group_singletons(single_lighter, single_heavier, single_control) | |
# If the scale balances (-1 -> 0), the outlier must be in the remaining lighter group | |
0 -> weigh_initial_group_remainders(remaining_lighter) | |
end | |
end | |
# Given a list of 3 users, weighs them once to finally determine the outlier | |
defp weigh_initial_group_remainders(remaining_users) do | |
[user_one, user_two, user_three] = remaining_users | |
# Since we're just passing the already remaining lighter group, | |
# The lighter outlier from this comparison is guaranteed to be the outlier | |
case check_balance(user_one, user_two) do | |
-1 -> user_one # user_one is lighter | |
1 -> user_two # user_two is lighter | |
0 -> user_three # user_one and user_two are equal, so user_three must be the outlier | |
end | |
end | |
# Given a single light, heavy, and control user, weighs them once to finally determine the outlier | |
defp weigh_initial_group_singletons(lighter_user, heavier_user, control_user) do | |
case check_balance(lighter_user, control_user) do | |
x when x != 0 -> lighter_user # If lighter_user weighs more or less than the control, it is the outlier | |
0 -> heavier_user # If the scale balances, heavier_user is the outlier | |
end | |
end | |
# Given a control group and the third group, splits and weighs them to find which sub-group an outlier is in | |
defp weigh_and_divide_third_group(control_group, third_group) do | |
control_subgroup = Enum.take(control_group, 2) | |
[subfirst_group, subsecond_group] = Enum.chunk_every(third_group, 2) | |
case check_balance(control_subgroup, subfirst_group) do | |
x when x != 0 -> second_weighing_of_third_group(control_subgroup, subfirst_group) # Someone from subfirst_group is the culprit | |
0 -> second_weighing_of_third_group(control_subgroup, subsecond_group) # Someone from subsecond_group is the cuplrit | |
end | |
end | |
# Given a control subgroup and a variable subgroup, weighs them once to finally determine the outlier | |
defp second_weighing_of_third_group(control_subgroup, variable_subgroup) do | |
[first_control_user | _rest] = control_subgroup | |
[first_variable_user, second_variable_user] = variable_subgroup | |
case check_balance(first_control_user, first_variable_user) do | |
x when x != 0 -> first_variable_user # Scale is still unbalanced, user on scale is culprit | |
0 -> second_variable_user # Scale is balanced, user that got off scale is culprit | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule IslanderProblemTest do | |
use ExUnit.Case | |
@initial_array [0,0,0,0,0,0,0,0,0,0,0,0] | |
test "It only accepts a list" do | |
assert_raise FunctionClauseError, fn -> | |
IslanderProblem.start(0) | |
end | |
end | |
test "It only allows 12 islanders to be specified" do | |
assert_raise ArgumentError, fn -> | |
IslanderProblem.start([]) | |
end | |
end | |
test "It picks the outlier in any position" do | |
for index <- 0..length(@initial_array) - 1 do | |
outlier_weight = :rand.uniform(100) | |
array_with_outlier = List.replace_at(@initial_array, index, outlier_weight) | |
assert outlier_weight == IslanderProblem.start(array_with_outlier) | |
end | |
end | |
test "It can pick negative outliers" do | |
outlier_weight = -1 | |
array_with_outlier = List.replace_at(@initial_array, 0, outlier_weight) | |
assert outlier_weight == IslanderProblem.start(array_with_outlier) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment