Эликсир: рекурсивные генераторы - PullRequest
0 голосов
/ 04 апреля 2019

Можно ли построить рекурсивный генератор в стиле Python с помощью Elixir? Примерно так:

def traverse(parent_dir):
    dirs, files = get_dirs_and_files_as_lists(parent_dir)

    for d in dirs:
        yield from traverse(d)

    for f in files:
        yield f

Для всех файлов, которые будут обрабатываться линейно, без накладных расходов, вытекающих из нетерпеливого списка неопределенной длины:

for f in traverse(dir):
    process(f)

Это, или какой-то рабочий эквивалент, должно быть возможно с использованием потоков; к сожалению, я понятия не имею, как.

Я хочу что-то вроде этого, просто ленивый:

def traverse_eagerly(parent_dir) do
  {dirs, files} = get_dirs_and_files_as_lists(parent_dir)

  for x <- dirs do
    traverse_eagerly(x)
  end
  |> Enum.concat()
  |> Enum.concat(files)
end

Ответы [ 2 ]

1 голос
/ 04 апреля 2019

Решение кажется тривиальным: замените Enum на Stream.

def traverse_lazily(parent_dir) do
  {dirs, files} = get_dirs_and_files_as_lists(parent_dir)

  for x <- dirs do
    traverse_lazily(x)
  end
  |> Stream.concat()
  |> Stream.concat(files)
end

Следующее работает, как и ожидалось:

s = traverse_lazily(a_dir_of_choice)

for x <- s, do: whatever_you_please(x)

Очень хороший язык.Как идеальное решение, как вы хотели бы.Если я что-то упустил, то естьКомментарии приветствуются!

1 голос
/ 04 апреля 2019

Вам не нужно Stream здесь, но если хотите, вот оно:

defmodule Traverse do
  @spec traverse(root :: binary(), yielder :: (binary() -> any())) ::
     :ok | {:error, posix()}
  def traverse(root, yielder) do
    # https://hexdocs.pm/elixir/master/File.html?#ls/1
    with {:ok, list} <- File.ls(root) do
      list
      |> Stream.each(fn file_or_dir ->
        if File.dir?(file_or_dir),
          do: traverse(file_or_dir, yielder), # TCO
          else: yielder.(file_or_dir)
      end)
      |> Stream.run()
    end
  end
end

И назовите это как:

Traverse.traverse(".", &IO.inspect/1)
...