Please I am trying to create nested maps from file path.
Home/Folder 1/ Folder 2/ Folder 3/ File.ex
How do i do that.
Thanks!
Please I am trying to create nested maps from file path.
Home/Folder 1/ Folder 2/ Folder 3/ File.ex
How do i do that.
Thanks!
Hi @lilsaintdenzel welcome!
Can you provide example input and output data structures?
Also, what have you tried so far?
type or paste code here
defmodule MapDirStructure do
@moduledoc """
trying to map a directory structure
"""
def parallel(p, f) when is_function(f) do
# IO.inspect(p)
# IO.inspect(f)
p
|> Enum.map(fn ele -> f.(ele) end)
end
def dir2obj(path \\ ".") do
IO.inspect(path)
path
|> File.stat!()
|> case do
%File.Stat{type: :regular} ->
%{String.to_charlist(path) => Path.basename(path)}
_ ->
File.ls!(path)
|> parallel(fn p -> dir2obj(Path.join([path, p])) end)
|> Enum.reduce(%{}, fn map, acc -> Map.merge(acc, map) end)
end
end
end
MapDirStructure.dir2obj(“Home/Folder 1/ Folder 2/ Folder 3”)
The chunk of code above prints. this
%{ 'Home/Folder 1/ Folder 2/ Folder 3/ => "File.ex"}
I want to print something like this
%{“Home” => %{“Folder 1” => %{“Folder 2”} =>%{“Folder 3” : [“File.ex”] } } },
All you need to do is to change a String
path into List
and transform it like:
defmodule Example do
def sample(path) when is_binary(path), do: path |> Path.split() |> sample()
def sample([file]), do: file
def sample([directory | list]), do: %{directory => sample(list)}
end
iex> Example.sample("Home/Folder 1/Folder 2/Folder 3/File.ex")
%{"Home" => %{"Folder 1" => %{"Folder 2" => %{"Folder 3" => "File.ex"}}}}
Helpful resources:
Thank You @Eiji
I have played a bit with “this and that” and you may find this code interesting as well:
defmodule Example do
def print(contents, parent_path \\ "/")
def print(directory, parent_path) when is_map(directory) do
Enum.each(directory, fn {path, contents} ->
print(path, parent_path)
full_path = Path.join(parent_path, path)
print(contents, full_path)
end)
end
def print(contents, parent_path) when is_list(contents) do
Enum.each(contents, &print(&1, parent_path))
end
def print(path, parent_path) when is_binary(path) do
parent_path |> Path.join(path) |> IO.puts()
end
def sample(path \\ "."), do: path |> Path.expand() |> do_sample() |> IO.inspect()
def do_sample(path, tree_root \\ true) do
if File.regular?(path) do
Path.basename(path)
else
contents =
path
|> File.ls!()
|> Enum.map(&(path |> Path.join(&1) |> do_sample(false)))
|> merge_contents()
key = if tree_root, do: path, else: Path.basename(path)
%{key => contents}
end
end
defp merge_contents(list) do
list
|> Enum.reduce({%{}, []}, fn
file, {directories, files} when is_binary(file) ->
{directories, [file | files]}
directory, {directories, files} when is_map(directory) ->
{Map.merge(directories, directory), files}
end)
|> case do
# empty directory - empty list
{directories, []} when directories == %{} -> []
# one file in directory - file string
{directories, [file]} when directories == %{} -> file
# multiple files in directory - files list
{directories, files} when directories == %{} -> files
# sub directories in directory and no files
# merged maps of all sub directories
{directories, []} -> directories
# sub directories in directory and any number files
# merged maps of all sub directories as list's head item and files as list's tail items
{directories, files} -> [directories | files]
end
end
end
tree = Example.sample()
Example.print(tree)
Please I have been trying to traverse these nested maps to delete files and that are in different folders but same files. In other words removing duplicates but not in the same file path and leaving the most recent file.
Am stucked on how to approach these problem.
I don’t think that tree
is good for this. Personally I would build a flat list of tuples. The first element would be a full path to file and the second its mtime
.
Here you go:
defmodule Example do
def find_old_duplicates(files, acc \\ [])
def find_old_duplicates([], acc), do: acc
def find_old_duplicates([file | files], acc) do
{duplicates, rest} = Enum.split_with(files, &duplicate?(&1, file))
[_ | duplicates] = Enum.sort_by([file | duplicates], &elem(&1, 1), {:desc, DateTime})
find_old_duplicates(rest, acc ++ Enum.map(duplicates, &elem(&1, 0)))
end
defp duplicate?({first_path, _first_mtime}, {second_path, _second_mtime}) do
{_output, status} = System.cmd("cmp", ["-s", first_path, second_path])
status == 0
end
def recursive_list_files(path \\ ".", acc \\ []) do
if File.regular?(path) do
expanded_path = Path.expand(path)
mtime = File.stat!(path, time: :posix).mtime
[{expanded_path, DateTime.from_unix!(mtime)} | acc]
else
path
|> File.ls!()
|> Enum.reduce(acc, &(path |> Path.join(&1) |> recursive_list_files(&2)))
end
end
end
iex> files = Example.recursive_list_files()
# returns a list of {path, mtime} tuples
iex> Example.find_old_duplicates(files)
# returns list of paths
Note: Here I have used cmp
tool. You may use other tool or Elixir
code for comparing files depending on your OS and requirements.
You can also read File.Stat documentation to know more about information gathered from files.
Thank You
I tried this approach the issue I encountered some file duplicates in different folders have same timestamps. I am trying to find timestamps for folders in elixir. In order words the goal is to delete the file duplicate found in different folder with older timestamp( when the file timestamps are the same).
I searched for how to check for Folder timestamp in elixir couldn’t find anything, please do you have any idea how to get timestamps for folders in elixir
(Edit: sorry, thought this was a duplicate but it’s just similar).
You can do it in exactly same way that I was doing it with file in last code example.