heves

heves

How to generate permutations from different sets of elements?

I’d like to generate a list from a variable length list of lists, choosing one element from each one. Like this:

list = [[:a, :b], [1, 2, 3], [{4, 5}, {5, 6}]]

magic_function(list)

Output:
[:a, 1, {4, 5}]
[:a, 1, {5, 6}]
[:a, 2, {4, 5}]
[:a, 2, {5, 6}]
[:a, 3, {4, 5}]
[:a, 3, {5, 6}]
[:b, 1, {4, 5}]
[:b, 1, {5, 6}]
[:b, 2, {4, 5}]
...

Is something like this possible in Elixir? I feel lost because of the variable length of the 2d list

Marked As Solved

Eiji

Eiji

I believe this code is the best:

defmodule Example do
  # a function head declaring defaults
  def sample(list, data \\ [])

  # in case where empty list is passed as an input
  def sample([], []), do: []

  # data here is finished element of output list
  # due to prepending the data is reversed
  # which is fixed by `:lists.reverse/1`
  # we need to wrap the output element into one element list
  # otherwise our data is improperly concatenated
  # i.e. instead of list of lists we have just a list
  # for example: [:a, 1, {4, 5}, :a, 1, {5, 4}, :a, …]
  def sample([], data), do: [:lists.reverse(data)]

  # when heads i.e. elements of first list ends
  # all we need to do is to return an empty list
  # to make concatenation work
  def sample([[] | _tail], _data), do: []

  # collecting data and recursion part
  def sample([[sub_head | head] | tail], data) do
    # using ++ operator we concatenates two sides each returning a list of lists
    # left side returns an output result for first element (nested recursion)
    # right side returns an output result for rest elements (tail recursion)
    # collecting data is really simple
    # we are prepending a first element of head into data
    # until we reach end of nested levels
    sample(tail, [sub_head | data]) ++ sample([head | tail], data)
  end
end

Example.sample([[:a, :b], [1, 2, 3], [{4, 5}, {5, 6}]])

This code should be fastest and work as long as the input is list contains 1 or more lists.

The other solutions are limited to 3-element list and one of them have also other problem which was already mentioned by its author:

In my case all of below calls work:

# empty list
Example.sample([])
# one element list with no sub elements
Example.sample([[]])
# one element list
Example.sample([[:a, :b]])
# two element list
Example.sample([[:a, :b], [1, 2, 3]])
# three element list (author's example input)
Example.sample([[:a, :b], [1, 2, 3], [{4, 5}, {5, 6}]])
# three element list with lists as sub elements instead tuple
Example.sample([[:a, :b], [1, 2, 3], [[4, 5], [5, 6]]])
# four element list
Example.sample([[:a, :b], [1, 2, 3], [{4, 5}, {5, 6}], ["abc", "def"]])

Note: If one of root list is empty, for example:

[[:a, :b], [1, 2, 3], [], [{4, 5}, {5, 6}]]

then then my example would properly return empty list. If you want to simply skit empty elements use this code:

def sample([head | [[] | tail]], data), do: sample([head | tail], data)

right before “collecting data and recursion part” comment.

However the above does not work if empty list is a first element. To fix that you need an extra helper function to avoid conflicts in pattern-matching, for example:

defmodule Example do
  # if empty list is a first element
  def before_sample([[] | tail]), do: before_sample(tail)
  # in any other case
  def before_sample(list), do: list

  # definition of sample function goes here…
end

input = [[], [:a, :b], [1, 2, 3], [{4, 5}, {5, 6}]]

input
|> Example.before_sample()
|> Example.sample()

Also Liked

trisolaran

trisolaran

This seems to be doing the trick:

Enum.reduce(list, fn acc, sublist ->
  for a <- sublist, b <- acc do
    List.flatten([a, b])
  end
end)

[
  [:a, 1, {4, 5}],
  [:a, 1, {5, 6}],
  [:a, 2, {4, 5}],
  [:a, 2, {5, 6}],
  [:a, 3, {4, 5}],
  [:a, 3, {5, 6}],
  [:b, 1, {4, 5}],
  [:b, 1, {5, 6}],
  [:b, 2, {4, 5}],
  [:b, 2, {5, 6}],
  [:b, 3, {4, 5}],
  [:b, 3, {5, 6}]
]

Unless the elements of some of the sublists are lists themselves, cause in that case they would be flattened.

UPDATE: This one below should cover all cases, just like @Eiji’s solution

def magic_function([]), do: []

def magic_function(list) do
  list
  |> Enum.reduce(fn sublist, acc ->
    for a <- acc, b <- sublist do
      [b | List.wrap(a)]
    end
  end)
  |> Enum.map(&Enum.reverse/1)
end
trisolaran

trisolaran

Ah, good catch! Thanks.

I beg to differ. What about this one:

def magic_function([]), do: []

def magic_function(list) do
  list
  |> Enum.reduce([nil], fn sublist, acc ->
    for a <- acc, b <- sublist do
      [b | List.wrap(a)]
    end
  end)
  |> Enum.map(&Enum.reverse/1)
end

I think it covers all cases now. The initial accumulator of [nil] for the reduce handles the case when there’s only one sublist: the sublist’s elements are prepended to List.wrap(nil), which is an empty list.

So it appears recursion is not necessary after all

Eiji

Eiji

Did you challenged a senior developer? :smiling_imp:

defmodule Example do
  def sample(list, opts \\ [skip: true])

  # uncomment if empty list is could be passed as an input
  def sample([], _opts), do: []

  # uncomment if empty list could be a first element
  def sample([[] | tail], opts), do: sample(tail, opts)

  # uncomment if list could contain only one element list
  def sample([list], _opts), do: Enum.map(list, &List.wrap/1)

  # in any other case
  def sample(list, opts) do
    if opts[:skip], do: sample_with_skip(list), else: sample_without_skip(list)
  end

  # skip empty elements in root list
  def sample_with_skip(list) do
    for sub_list <- list, reduce: [] do
      # skip empty elements in root list
      data when sub_list == [] ->
        data

      # passing a first element of root list as data
      [] ->
        sub_list

      data ->
        for sub_data <- data, element <- sub_list do
          [element | List.wrap(sub_data)]
        end
    end
    |> Enum.map(&Enum.reverse/1)
  end

  # alternatively do not skip empty elements in root list
  def sample_without_skip(list) do
    for sub_list <- list, reduce: [] do
      # when element is empty list set data to nil
      _data when sub_list == [] ->
        nil

      # no matter what happens later if data is nil keep it as is
      nil ->
        nil

      # passing a first element of root list as data
      [] ->
        sub_list

      data ->
        for sub_data <- data, element <- sub_list do
          [element | List.wrap(sub_data)]
        end
    end
    |> case do
      nil -> []
      list -> Enum.map(list, &Enum.reverse/1)
    end
  end
end

Pretty much the same as Enum.reduce/3 version. Simply pattern-matching here is not only in function clause, but also in for …, reduce: … clauseend notation.

However this looks like an art for art's sake. Just look how simple is raw pattern-matching comparing to 2 other versions of my solution. :smiley:

Last Post!

gregvaughn

gregvaughn

Oh, no! I suppose I’m building a reputation.

I was asked a variant of this at a job interview about 5 years ago and tried to make it work with a comprehension and failed. That was before the reduce option was added. I ultimately solved it with a recursive call to a comprehension. It needs a recursive/reduce style to handle the unknown count of lists.

However, now that I think about it, I wonder if a macro could generate the right count of generators in the for comprehension? Nah, only if the input is known at compile time.

Where Next?

Trending in Questions Top

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
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
tj0
I’ve been following the steps here for the upgrade from 1.6 to 1.7 and it has gone relatively smoothly all the way till the phoenix_view ...
New
cgraham
Hi! What is currently the best library/method for parsing text and tabular data out of PDF files in Elixir or Erlang?
New
stefanchrobot
Hi, I need a way to handle data migrations in my application. I found an article by @wojtekmach about manual migrations: Automatic and ma...
New
stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
webofbits
Squid Mesh is an open source workflow automation runtime for Elixir applications. It is aimed at Phoenix and OTP apps that want to defin...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New

We're in Beta

About us Mission Statement