Skip to content

Instantly share code, notes, and snippets.

@kieraneglin
Last active April 5, 2019 21:36
Show Gist options
  • Save kieraneglin/867b1105ea87b0742a8609695671ec31 to your computer and use it in GitHub Desktop.
Save kieraneglin/867b1105ea87b0742a8609695671ec31 to your computer and use it in GitHub Desktop.
B99 islander problem in Elixir
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
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