From 56048f88dff0db934ae52ee631382fe348502f1c Mon Sep 17 00:00:00 2001 From: Kah Goh Date: Tue, 15 Jul 2025 22:20:08 +0800 Subject: [PATCH 1/4] Add analyzer for gotta-snatch-em-all --- config/config.exs | 3 + lib/elixir_analyzer/constants.ex | 15 ++ .../test_suite/gotta-snatch-em-all.ex | 139 ++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex diff --git a/config/config.exs b/config/config.exs index 3cbc3c08..f2dc74cc 100644 --- a/config/config.exs +++ b/config/config.exs @@ -63,6 +63,9 @@ config :elixir_analyzer, "german-sysadmin" => %{ analyzer_module: ElixirAnalyzer.TestSuite.GermanSysadmin }, + "gotta-snatch-em-all" => %{ + analyzer_module: ElixirAnalyzer.TestSuite.GottaSnatchEmAll + }, "high-school-sweetheart" => %{ analyzer_module: ElixirAnalyzer.TestSuite.HighSchoolSweetheart }, diff --git a/lib/elixir_analyzer/constants.ex b/lib/elixir_analyzer/constants.ex index 19247b35..3141c63f 100644 --- a/lib/elixir_analyzer/constants.ex +++ b/lib/elixir_analyzer/constants.ex @@ -100,6 +100,21 @@ defmodule ElixirAnalyzer.Constants do german_sysadmin_no_string: "elixir.german-sysadmin.no_string", german_sysadmin_use_case: "elixir.german-sysadmin.use_case", + # Gotta Snatch Em All Comments + gotta_snatch_em_all_add_card_use_mapset_member_and_put: "elixir.gotta-snatch-em-all.add_card_use_mapset_member_and_put", + gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete: "elixir.gotta-snatch-em-all.trade_card_use_mapset_member_put_and_delete", + gotta_snatch_em_all_remove_duplicates_use_mapset_new: "elixir.gotta-snatch-em-all.remove_duplicates_use_mapset_new", + gotta_snatch_em_all_remove_duplicates_use_enum_sort: "elixir.gotta-snatch-em-all.remove_duplicates_use_enum_sort", + gotta_snatch_em_all_remove_duplicates_do_not_use_enum_uniq: "elixir.gotta-snatch-em-all.remove_duplicates_do_not_use_enum_uniq", + gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size: "elixir.gotta-snatch-em-all.extra_cards_use_mapset_difference_and_size", + gotta_snatch_em_all_boring_cards_use_mapset_intersection: "elixir.gotta-snatch-em-all.boring_cards_use_mapset_intersection", + gotta_snatch_em_all_boring_cards_use_enum_sort: "elixir.gotta-snatch-em-all.boring_cards_use_enum_sort", + gotta_snatch_em_all_boring_cards_use_enum_reduce: "elixir.gotta-snatch-em-all.boring_cards_use_enum_reduce", + gotta_snatch_em_all_total_cards_use_mapset_union_and_size: "elixir.gotta-snatch-em-all.total_cards_use_mapset_union_and_size", + gotta_snatch_em_all_total_cards_use_enum_reduce: "elixir.gotta-snatch-em-all.total_cards_use_enum_reduce", + gotta_snatch_em_all_split_shiny_cards_use_enum_sort: "elixir.gotta-snatch-em-all.shiny_cards_use_enum_sort", + gotta_snatch_em_all_split_shiny_cards_use_string_starts_with: "elixir.gotta-snatch-em-all.shiny_cards_use_string_starts_with", + # Guessing Game Comments guessing_game_use_default_argument: "elixir.guessing-game.use_default_argument", guessing_game_use_multiple_clause_functions: diff --git a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex new file mode 100644 index 00000000..ff45e6ce --- /dev/null +++ b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex @@ -0,0 +1,139 @@ +defmodule ElixirAnalyzer.TestSuite.GottaSnatchEmAll do + @moduledoc """ + This is an exercise analyzer extension module for the concept exercise Gotta Snatch Em All + """ + use ElixirAnalyzer.ExerciseTest + alias ElixirAnalyzer.Constants + + assert_call "add_call uses MapSet.number?" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :add_card + called_fn module: MapSet, name: :member? + comment Constants.gotta_snatch_em_all_add_card_use_mapset_member_and_put() + end + + assert_call "add_call uses MapSet.put" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :add_card + called_fn module: MapSet, name: :put + comment Constants.gotta_snatch_em_all_add_card_use_mapset_member_and_put() + suppress_if "add_call uses MapSet.number?", :fail + end + + assert_call "trade_card uses MapSet.member?" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :trade_card + called_fn module: MapSet, name: :member? + comment Constants.gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete() + end + + assert_call "trade_card uses MapSet.put" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :trade_card + called_fn module: MapSet, name: :put + comment Constants.gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete() + suppress_if "trade_card uses MapSet.member?", :fail + end + + assert_call "trade_card uses MapSet.delete" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :trade_card + called_fn module: MapSet, name: :delete + comment Constants.gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete() + suppress_if "trade_card uses MapSet.member?", :fail + suppress_if "trade_card uses MapSet.put", :fail + end + + assert_call "remove_duplicates uses MapSet.new" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :remove_duplicates + called_fn module: MapSet, name: :new + comment Constants.gotta_snatch_em_all_remove_duplicates_use_mapset_new() + end + + assert_call "remove_duplicates uses Enum.sort" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :remove_duplicates + called_fn module: Enum, name: :sort + comment Constants.gotta_snatch_em_all_remove_duplicates_use_enum_sort() + end + + assert_no_call "remove_duplicates does not use Enum.uniq" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :remove_duplicates + called_fn module: Enum, name: :uniq + comment Constants.gotta_snatch_em_all_remove_duplicates_do_not_use_enum_uniq() + end + + assert_call "extra_cards uses MapSet.difference" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :extra_cards + called_fn module: MapSet, name: :difference + comment Constants.gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size() + end + + assert_call "extra_cards uses MapSet.size" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :extra_cards + called_fn module: MapSet, name: :size + comment Constants.gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size() + suppress_if "extra_cards uses MapSet.difference", :fail + end + + assert_call "boring_cards uses MapSet.intersection" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :boring_cards + called_fn module: MapSet, name: :intersection + comment Constants.gotta_snatch_em_all_boring_cards_use_mapset_intersection() + end + + assert_call "boring_cards uses Enum.sort" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :boring_cards + called_fn module: Enum, name: :sort + comment Constants.gotta_snatch_em_all_boring_cards_use_enum_sort() + end + + assert_call "boring_cards uses Enum.reduce" do + type :actionable + calling_fn module: GottaSnatchEmAll, name: :boring_cards + called_fn module: Enum, name: :reduce + comment Constants.gotta_snatch_em_all_boring_cards_use_enum_reduce() + end + + assert_call "total_cards uses MapSet.union" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :total_cards + called_fn module: MapSet, name: :union + comment Constants.gotta_snatch_em_all_total_cards_use_mapset_union_and_size() + end + + assert_call "total_cards uses MapSet.size" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :total_cards + called_fn module: MapSet, name: :size + comment Constants.gotta_snatch_em_all_total_cards_use_mapset_union_and_size() + suppress_if "total_cards uses MapSet.union", :fail + end + + assert_call "total_cards uses Enum.reduce" do + type :actionable + calling_fn module: GottaSnatchEmAll, name: :total_cards + called_fn module: Enum, name: :reduce + comment Constants.gotta_snatch_em_all_total_cards_use_enum_reduce() + end + + assert_call "split_shiny_cards uses Enum.sort" do + type :essential + calling_fn module: GottaSnatchEmAll, name: :split_shiny_cards + called_fn module: Enum, name: :sort + comment Constants.gotta_snatch_em_all_split_shiny_cards_use_enum_sort() + end + + assert_call "split_shiny_cards uses String.starts_with" do + type :actionable + calling_fn module: GottaSnatchEmAll, name: :split_shiny_cards + called_fn module: String, name: :starts_with? + comment Constants.gotta_snatch_em_all_split_shiny_cards_use_string_starts_with() + end +end From 726029679b9e8c86c2d4576717c7d4b8602f4420 Mon Sep 17 00:00:00 2001 From: Kah Goh Date: Tue, 15 Jul 2025 22:29:38 +0800 Subject: [PATCH 2/4] Fix space alignment & formatting --- lib/elixir_analyzer/constants.ex | 39 ++++++++++++------- .../test_suite/gotta-snatch-em-all.ex | 2 +- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/elixir_analyzer/constants.ex b/lib/elixir_analyzer/constants.ex index 3141c63f..6a7b7c30 100644 --- a/lib/elixir_analyzer/constants.ex +++ b/lib/elixir_analyzer/constants.ex @@ -101,19 +101,32 @@ defmodule ElixirAnalyzer.Constants do german_sysadmin_use_case: "elixir.german-sysadmin.use_case", # Gotta Snatch Em All Comments - gotta_snatch_em_all_add_card_use_mapset_member_and_put: "elixir.gotta-snatch-em-all.add_card_use_mapset_member_and_put", - gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete: "elixir.gotta-snatch-em-all.trade_card_use_mapset_member_put_and_delete", - gotta_snatch_em_all_remove_duplicates_use_mapset_new: "elixir.gotta-snatch-em-all.remove_duplicates_use_mapset_new", - gotta_snatch_em_all_remove_duplicates_use_enum_sort: "elixir.gotta-snatch-em-all.remove_duplicates_use_enum_sort", - gotta_snatch_em_all_remove_duplicates_do_not_use_enum_uniq: "elixir.gotta-snatch-em-all.remove_duplicates_do_not_use_enum_uniq", - gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size: "elixir.gotta-snatch-em-all.extra_cards_use_mapset_difference_and_size", - gotta_snatch_em_all_boring_cards_use_mapset_intersection: "elixir.gotta-snatch-em-all.boring_cards_use_mapset_intersection", - gotta_snatch_em_all_boring_cards_use_enum_sort: "elixir.gotta-snatch-em-all.boring_cards_use_enum_sort", - gotta_snatch_em_all_boring_cards_use_enum_reduce: "elixir.gotta-snatch-em-all.boring_cards_use_enum_reduce", - gotta_snatch_em_all_total_cards_use_mapset_union_and_size: "elixir.gotta-snatch-em-all.total_cards_use_mapset_union_and_size", - gotta_snatch_em_all_total_cards_use_enum_reduce: "elixir.gotta-snatch-em-all.total_cards_use_enum_reduce", - gotta_snatch_em_all_split_shiny_cards_use_enum_sort: "elixir.gotta-snatch-em-all.shiny_cards_use_enum_sort", - gotta_snatch_em_all_split_shiny_cards_use_string_starts_with: "elixir.gotta-snatch-em-all.shiny_cards_use_string_starts_with", + gotta_snatch_em_all_add_card_use_mapset_member_and_put: + "elixir.gotta-snatch-em-all.add_card_use_mapset_member_and_put", + gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete: + "elixir.gotta-snatch-em-all.trade_card_use_mapset_member_put_and_delete", + gotta_snatch_em_all_remove_duplicates_use_mapset_new: + "elixir.gotta-snatch-em-all.remove_duplicates_use_mapset_new", + gotta_snatch_em_all_remove_duplicates_use_enum_sort: + "elixir.gotta-snatch-em-all.remove_duplicates_use_enum_sort", + gotta_snatch_em_all_remove_duplicates_do_not_use_enum_uniq: + "elixir.gotta-snatch-em-all.remove_duplicates_do_not_use_enum_uniq", + gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size: + "elixir.gotta-snatch-em-all.extra_cards_use_mapset_difference_and_size", + gotta_snatch_em_all_boring_cards_use_mapset_intersection: + "elixir.gotta-snatch-em-all.boring_cards_use_mapset_intersection", + gotta_snatch_em_all_boring_cards_use_enum_sort: + "elixir.gotta-snatch-em-all.boring_cards_use_enum_sort", + gotta_snatch_em_all_boring_cards_use_enum_reduce: + "elixir.gotta-snatch-em-all.boring_cards_use_enum_reduce", + gotta_snatch_em_all_total_cards_use_mapset_union_and_size: + "elixir.gotta-snatch-em-all.total_cards_use_mapset_union_and_size", + gotta_snatch_em_all_total_cards_use_enum_reduce: + "elixir.gotta-snatch-em-all.total_cards_use_enum_reduce", + gotta_snatch_em_all_split_shiny_cards_use_enum_sort: + "elixir.gotta-snatch-em-all.shiny_cards_use_enum_sort", + gotta_snatch_em_all_split_shiny_cards_use_string_starts_with: + "elixir.gotta-snatch-em-all.shiny_cards_use_string_starts_with", # Guessing Game Comments guessing_game_use_default_argument: "elixir.guessing-game.use_default_argument", diff --git a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex index ff45e6ce..96de42d4 100644 --- a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex +++ b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex @@ -35,7 +35,7 @@ defmodule ElixirAnalyzer.TestSuite.GottaSnatchEmAll do suppress_if "trade_card uses MapSet.member?", :fail end - assert_call "trade_card uses MapSet.delete" do + assert_call "trade_card uses MapSet.delete" do type :essential calling_fn module: GottaSnatchEmAll, name: :trade_card called_fn module: MapSet, name: :delete From 3593bde4403374663623d940c17a8fddcce1784e Mon Sep 17 00:00:00 2001 From: Kah Goh Date: Sun, 7 Sep 2025 18:39:16 +0800 Subject: [PATCH 3/4] Add tests for gotta snatch em all analyzer --- .../test_suite/gotta-snatch-em-all.ex | 4 +- .../test_suite/gotta_snatch_em_all_test.exs | 392 ++++++++++++++++++ 2 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs diff --git a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex index 96de42d4..cbde9baf 100644 --- a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex +++ b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex @@ -5,7 +5,7 @@ defmodule ElixirAnalyzer.TestSuite.GottaSnatchEmAll do use ElixirAnalyzer.ExerciseTest alias ElixirAnalyzer.Constants - assert_call "add_call uses MapSet.number?" do + assert_call "add_call uses MapSet.member?" do type :essential calling_fn module: GottaSnatchEmAll, name: :add_card called_fn module: MapSet, name: :member? @@ -17,7 +17,7 @@ defmodule ElixirAnalyzer.TestSuite.GottaSnatchEmAll do calling_fn module: GottaSnatchEmAll, name: :add_card called_fn module: MapSet, name: :put comment Constants.gotta_snatch_em_all_add_card_use_mapset_member_and_put() - suppress_if "add_call uses MapSet.number?", :fail + suppress_if "add_call uses MapSet.member?", :fail end assert_call "trade_card uses MapSet.member?" do diff --git a/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs b/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs new file mode 100644 index 00000000..6b7a33b1 --- /dev/null +++ b/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs @@ -0,0 +1,392 @@ +defmodule ElixirAnalyzer.ExerciseTest.GottSnatchEmAllTest do + use ElixirAnalyzer.ExerciseTestCase, + exercise_test_module: ElixirAnalyzer.TestSuite.GottaSnatchEmAll + + test_exercise_analysis "example solution", + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do + defmodule GottaSnatchEmAll do + @type card :: String.t() + @type collection :: MapSet.t(card()) + + @spec new_collection(card()) :: collection() + def new_collection(card) do + MapSet.new([card]) + end + + @spec add_card(card(), collection()) :: {boolean(), collection()} + def add_card(card, collection) do + {MapSet.member?(collection, card), MapSet.put(collection, card)} + end + + @spec trade_card(card(), card(), collection()) :: {boolean(), collection()} + def trade_card(your_card, their_card, collection) do + can_trade? = + MapSet.member?(collection, your_card) and not MapSet.member?(collection, their_card) + + updated_collection = + collection + |> MapSet.delete(your_card) + |> MapSet.put(their_card) + + {can_trade?, updated_collection} + end + + @spec remove_duplicates([card()]) :: [card()] + def remove_duplicates(cards) do + cards + |> MapSet.new() + |> MapSet.to_list() + |> Enum.sort() + end + + @spec extra_cards(collection(), collection()) :: non_neg_integer() + def extra_cards(your_collection, their_collection) do + your_collection + |> MapSet.difference(their_collection) + |> MapSet.size() + end + + @spec boring_cards([collection()]) :: [card()] + def boring_cards(collections) do + case collections do + [] -> + [] + + [collection | rest] -> + Enum.reduce(rest, collection, &MapSet.intersection/2) + |> MapSet.to_list() + |> Enum.sort() + end + end + + @spec total_cards([collection()]) :: non_neg_integer() + def total_cards(collections) do + collections + |> Enum.reduce(MapSet.new(), &MapSet.union/2) + |> MapSet.size() + end + + @spec split_shiny_cards(collection()) :: {[card()], [card()]} + def split_shiny_cards(collection) do + {shiny, not_shiny} = split_with(collection, &String.starts_with?(&1, "Shiny")) + + shiny_list = shiny |> MapSet.to_list() |> Enum.sort() + not_shiny_list = not_shiny |> MapSet.to_list() |> Enum.sort() + + {shiny_list, not_shiny_list} + end + + defp split_with(mapset, predicate) do + init = {MapSet.new(), MapSet.new()} + + Enum.reduce(mapset, init, fn item, {passes, fails} -> + if predicate.(item) do + {MapSet.put(passes, item), fails} + else + {passes, MapSet.put(fails, item)} + end + end) + end + end + end + + test_exercise_analysis "does not use use MapSet.member or MapSet.put in add_card", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_add_card_use_mapset_member_and_put() + ] do + [ + defmodule GottaSnatchEmAll do + def add_card(card, collection) when is_binary(card) and is_map(collection) do + case MapSet.put(collection, card) do + ^collection -> {true, collection} + new_collection -> {false, new_collection} + end + end + end, + defmodule GottaSnatchEmAll do + def add_card(card, collection) when is_binary(card) and is_map(collection) do + {Enum.member?(collection, card), MapSet.put(collection, card)} + end + end, + defmodule GottaSnatchEmAll do + def add_card(card, collection) when is_binary(card) and is_map(collection) do + {Enum.any?(collection, &(&1 === card)), MapSet.put(collection, card)} + end + end, + defmodule GottaSnatchEmAll do + def add_card(card, collection) when is_binary(card) and is_map(collection) do + {MapSet.member?(collection, card), Enum.into([card], collection)} + end + end + ] + end + + test_exercise_analysis "does not use MapSet.member, MapSet.put or MapSet.delete in trade_card", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_trade_card_use_mapset_member_put_and_delete() + ] do + [ + defmodule GottaSnatchEmAll do + def trade_card(your_card, their_card, collection) do + can_trade? = + Enum.member?(collection, your_card) and not Enum.member?(collection, their_card) + + updated_collection = + collection + |> MapSet.delete(your_card) + |> MapSet.put(their_card) + + {can_trade?, updated_collection} + end + end, + defmodule GottaSnatchEmAll do + def trade_card(your_card, their_card, collection) do + can_trade? = + MapSet.member?(collection, your_card) and not MapSet.member?(collection, their_card) + + updated_collection = + collection + |> MapSet.reject(&(&1 === your_card)) + |> MapSet.put(their_card) + + {can_trade?, updated_collection} + end + end, + defmodule GottaSnatchEmAll do + def trade_card(your_card, their_card, collection) do + can_trade? = + MapSet.member?(collection, your_card) and not MapSet.member?(collection, their_card) + + updated_collection = + collection + |> MapSet.delete(your_card) + + {can_trade?, Enum.into([their_card], updated_collection)} + end + end + ] + end + + test_exercise_analysis "does not use MapSet.new in remove_duplicates", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_remove_duplicates_use_mapset_new() + ] do + [ + defmodule GottaSnatchEmAll do + def remove_duplicates(cards) do + cards + |> Enum.into(%{}, &{&1, true}) + |> Map.keys() + |> Enum.sort() + end + end + ] + end + + test_exercise_analysis "detect unsorted output from remove_duplicates", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_remove_duplicates_use_enum_sort() + ] do + [ + defmodule GottaSnatchEmAll do + def remove_duplicates(cards) do + cards + |> MapSet.new() + |> MapSet.to_list() + end + end + ] + end + + test_exercise_analysis "detect Enum.uniq usage in remove_duplicates", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_remove_duplicates_use_mapset_new(), + ElixirAnalyzer.Constants.gotta_snatch_em_all_remove_duplicates_do_not_use_enum_uniq() + ] do + [ + defmodule GottaSnatchEmAll do + def remove_duplicates(cards) do + cards + |> Enum.uniq() + |> Enum.sort() + end + end + ] + end + + test_exercise_analysis "does not use MapSet.difference in extra_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size() + ] do + [ + defmodule GottaSnatchEmAll do + def extra_cards(your_collection, their_collection) do + your_collection + |> MapSet.reject(fn card -> MapSet.member?(their_collection, card) end) + |> MapSet.size() + end + end + ] + end + + test_exercise_analysis "does not use MapSet.size in extra_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_extra_cards_use_mapset_difference_and_size() + ] do + [ + defmodule GottaSnatchEmAll do + def extra_cards(your_collection, their_collection) do + MapSet.difference(your_collection, their_collection) + |> Enum.count() + end + end, + defmodule GottaSnatchEmAll do + def extra_cards(your_collection, their_collection) do + MapSet.difference(your_collection, their_collection) + |> length + end + end + ] + end + + test_exercise_analysis "does not use MapSet.intersection in boring_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_boring_cards_use_mapset_intersection() + ] do + [ + defmodule GottaSnatchEmAll do + def boring_cards([]), do: [] + def boring_cards([first]), do: MapSet.to_list(first) |> Enum.sort() + + def boring_cards(collections) do + rest + |> Enum.reduce(first, fn set1, set2 -> + MapSet.filter(set1, fn item -> MapSet.member?(set2, item) end) + end) + |> Enum.sort() + end + end, + defmodule GottaSnatchEmAll do + def boring_cards([]), do: [] + def boring_cards([first]), do: MapSet.to_list(first) |> Enum.sort() + + def boring_cards(cards) do + rest + |> Enum.reduce(first, fn set1, set2 -> for item <- set1, item in set2, do: item end) + |> Enum.sort() + end + end + ] + end + + test_exercise_analysis "detect unsorted output from boring_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_boring_cards_use_enum_sort() + ] do + [ + def boring_cards(collections) do + case collections do + [] -> + [] + + [collection | rest] -> + Enum.reduce(rest, collection, &MapSet.intersection/2) + |> MapSet.to_list() + end + end + ] + end + + test_exercise_analysis "does not use Enum.reduce in boring_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_boring_cards_use_enum_reduce() + ] do + [ + defmodule GottaSnatchEmAll do + def boring_cards([]), do: [] + def boring_cards([first]), do: MapSet.to_list(first) |> Enum.sort() + def boring_cards([first | rest]), do: do_boring_cards(rest, first) + + defp do_boring_cards([], acc), do: MapSet.to_list(acc) |> Enum.sort() + + defp do_boring_cards([next | rest], acc), + do: do_boring_cards(rest, MapSet.intersection(acc, next)) + end + ] + end + + test_exercise_analysis "does not use MapSet.union in total_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_total_cards_use_mapset_union_and_size() + ] do + [ + defmodule GottaSnatchEmAll do + def total_cards(collections) do + collections + |> Enum.reduce([], &Enum.concat/2) + |> Enum.uniq() + |> Enum.count() + end + end, + defmodule GottaSnatchEmAll do + def total_cards(collections) do + collections + |> Enum.reduce(MapSet.new([]), fn set, acc -> + Enum.reduce(set, acc, fn i, acc2 -> MapSet.put(acc2, i) end) + end) + |> MapSet.size() + end + end + ] + end + + test_exercise_analysis "does not use MapSet.size in total_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_total_cards_use_mapset_union_and_size() + ] do + [ + defmodule GottaSnatchEmAll do + def total_cards(collections) do + collections + |> Enum.reduce(MapSet.new(), &MapSet.union/2) + |> Enum.count() + end + end, + defmodule GottaSnatchEmAll do + def total_cards(collections) do + collections + |> Enum.reduce(MapSet.new(), &MapSet.union/2) + |> length + end + end + ] + end + + test_exercise_analysis "does not use String.starts_with in split_shiny_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_split_shiny_cards_use_string_starts_with() + ] do + [ + defmodule GottaSnatchEmAll do + def split_shiny_cards(collections) do + {shiny, others} = MapSet.split_with(collection, &String.match?(&1, ~r/^Shiny/)) + {MapSet.to_list(shiny) |> Enum.sort(), MapSet.to_list(others) |> Enum.sort()} + end + end + ] + end + + test_exercise_analysis "detect unsorterd output from split_shiny_cards", + comments_include: [ + ElixirAnalyzer.Constants.gotta_snatch_em_all_split_shiny_cards_use_enum_sort() + ] do + [ + defmodule GottaSnatchEmAll do + def split_shiny_cards(collections) do + {shiny, others} = MapSet.split_with(collection, &String.starts_with?(&1, "Shiny")) + {MapSet.to_list(shiny), MapSet.to_list(others)} + end + end + ] + end +end From cbb6ce33167ecf8d48c80ab71b24a9fda8dc0871 Mon Sep 17 00:00:00 2001 From: Kah Goh Date: Sun, 28 Sep 2025 20:39:58 +0800 Subject: [PATCH 4/4] Fix wording & spelling mistakes Co-authored-by: Jie --- lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex | 6 +++--- .../elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex index cbde9baf..e11a9a65 100644 --- a/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex +++ b/lib/elixir_analyzer/test_suite/gotta-snatch-em-all.ex @@ -5,19 +5,19 @@ defmodule ElixirAnalyzer.TestSuite.GottaSnatchEmAll do use ElixirAnalyzer.ExerciseTest alias ElixirAnalyzer.Constants - assert_call "add_call uses MapSet.member?" do + assert_call "add_card uses MapSet.member?" do type :essential calling_fn module: GottaSnatchEmAll, name: :add_card called_fn module: MapSet, name: :member? comment Constants.gotta_snatch_em_all_add_card_use_mapset_member_and_put() end - assert_call "add_call uses MapSet.put" do + assert_call "add_card uses MapSet.put" do type :essential calling_fn module: GottaSnatchEmAll, name: :add_card called_fn module: MapSet, name: :put comment Constants.gotta_snatch_em_all_add_card_use_mapset_member_and_put() - suppress_if "add_call uses MapSet.member?", :fail + suppress_if "add_card uses MapSet.member?", :fail end assert_call "trade_card uses MapSet.member?" do diff --git a/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs b/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs index 6b7a33b1..a83abed2 100644 --- a/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs +++ b/test/elixir_analyzer/test_suite/gotta_snatch_em_all_test.exs @@ -362,7 +362,7 @@ defmodule ElixirAnalyzer.ExerciseTest.GottSnatchEmAllTest do ] end - test_exercise_analysis "does not use String.starts_with in split_shiny_cards", + test_exercise_analysis "does not use String.starts_with? in split_shiny_cards", comments_include: [ ElixirAnalyzer.Constants.gotta_snatch_em_all_split_shiny_cards_use_string_starts_with() ] do @@ -376,7 +376,7 @@ defmodule ElixirAnalyzer.ExerciseTest.GottSnatchEmAllTest do ] end - test_exercise_analysis "detect unsorterd output from split_shiny_cards", + test_exercise_analysis "detect unsorted output from split_shiny_cards", comments_include: [ ElixirAnalyzer.Constants.gotta_snatch_em_all_split_shiny_cards_use_enum_sort() ] do