kitplummer

kitplummer

Refactoring help needed - Enum.map and Map.update?

I’ve got a bit of a real world problem that I’m trying to teach myself through and am straight stuck. I’ve tried a handful of things to get to where I want to be…but just don’t have enough base knowledge to work through it. Obviously I could put this away and come back to it, but figure asking for a little help/tutoring is better. I’ve started with enumerating over a ‘categories’ list, then piping it into a Map.update, accumulating things but bah, didn’t get close.

Here’s the module:

defmodule Categories do
  def categorize!(directory, categories) do
    result = %{}
    cat1 = find_types_for_cat(directory, categories[:cat1])

    result =
      if Kernel.length(cat1[:cat1]) > 0 do
        Map.merge(result, cat1)
      else
        result
      end

    cat2 = find_types_for_cat(directory, categories[:cat2])

    result =
      if Kernel.length(cat2[:cat2]) > 0 do
        Map.merge(result, cat2)
      else
        result
      end

    cat3 = find_types_for_cat(directory, categories[:cat3])

    result =
      if Kernel.length(cat3[:cat3]) > 0 do
        Map.merge(result, cat3)
      else
        result
      end

    result
  end

  def find_types_for_cat(directory, category) do
    %{category.name => Path.wildcard("#{directory}/**/#{Category.types_to_string(category)}")}
  end
end

The struct:

defmodule Category do
  alias __MODULE__

  defstruct name: nil, types: []

  def types_to_string(category) do
    types = Enum.join(category.types, ",")
    "{" <> types <> "}"
  end
end

And here’s the tests:

defmodule CategoriesTest do
  use ExUnit.Case, async: true
  doctest Categories

  setup_all do
    category_one = %Category{name: :cat1, types: ["type1", "type1.1"]}
    category_two = %Category{name: :cat2, types: ["type2", "type2.1"]}
    category_three = %Category{name: :cat3, types: ["type3"]}

    ## TODO: Make this an extensible list of categories, no keys
    cat_map = %{:cat1 => category_one, :cat2 => category_two, :cat3 => category_three}
    [categories: cat_map]
  end

  describe "greets the world with bad Elixir" do
    test "dir1", %{categories: categories} do
      expected = %{
        cat1: ["test/dirs/dir1/type1"]
      }

      assert Categories.categorize!("test/dirs/dir1", categories) == expected
    end

    test "dir2", %{categories: categories} do
      expected = %{
        cat1: ["test/dirs/dir2/subdir1/type1", "test/dirs/dir2/type1"],
        cat2: ["test/dirs/dir2/subdir2/type2", "test/dirs/dir2/type2.1"]
      }

      assert Categories.categorize!("test/dirs/dir2", categories) == expected
    end

    test "dir3", %{categories: categories} do
      expected = %{
        cat1: ["test/dirs/dir3/subdir2/type1", "test/dirs/dir3/type1", "test/dirs/dir3/type1.1"],
        cat2: ["test/dirs/dir3/subdir3/type2", "test/dirs/dir3/type2"],
        cat3: ["test/dirs/dir3/subdir1/type3", "test/dirs/dir3/type3"]
      }

      assert Categories.categorize!("test/dirs/dir3", categories) == expected
    end
  end
end

I’ve also created a simple project to work on the problem if that’s easier to look at than pasted code blocks here.

Appreciate any help/guidance I can get. TIA.

Kit

Marked As Solved

kitplummer

kitplummer

Got it working.

Categories module:

defmodule Categories do
  @spec categorize!(any, any) :: any
  def categorize!(directory, categories) do
    Enum.reduce(categories, %{}, fn category, acc ->
      search = find_types_for_cat(directory, category)

      if Enum.empty?(search) do
        acc
      else
        Map.put(acc, category.name, search)
      end
    end)
  end

  @spec find_types_for_cat(any, atom | %{types: any}) :: [binary]
  def find_types_for_cat(directory, category) do
    Path.wildcard("#{directory}/**/#{Category.types_to_string(category)}")
  end
end

And the updated test:

defmodule CategoriesTest do
  use ExUnit.Case, async: true
  doctest Categories

  setup_all do
    category_one = %Category{name: :cat1, types: ["type1", "type1.1"]}
    category_two = %Category{name: :cat2, types: ["type2", "type2.1"]}
    category_three = %Category{name: :cat3, types: ["type3"]}
    cat_list = [category_one, category_two, category_three]
    [categories: cat_list]
  end

  describe "greets the world with bad Elixir" do
    test "dir1", %{categories: categories} do
      expected = %{
        cat1: ["test/dirs/dir1/type1"]
      }

      assert Categories.categorize!("test/dirs/dir1", categories) == expected
    end

    # @tag :skip
    test "dir2", %{categories: categories} do
      expected = %{
        cat1: ["test/dirs/dir2/subdir1/type1", "test/dirs/dir2/type1"],
        cat2: ["test/dirs/dir2/subdir2/type2", "test/dirs/dir2/type2.1"]
      }

      assert Categories.categorize!("test/dirs/dir2", categories) == expected
    end

    # @tag :skip
    test "dir3", %{categories: categories} do
      expected = %{
        cat1: ["test/dirs/dir3/subdir2/type1", "test/dirs/dir3/type1", "test/dirs/dir3/type1.1"],
        cat2: ["test/dirs/dir3/subdir3/type2", "test/dirs/dir3/type2"],
        cat3: ["test/dirs/dir3/subdir1/type3", "test/dirs/dir3/type3"]
      }

      assert Categories.categorize!("test/dirs/dir3", categories) == expected
    end
  end
end

Thanks for the pointer @kokolegorille - greatly appreciated. Will dig into Enum.reduce a bit more now.

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement