sarat1669

sarat1669

How to swap elements in a list?

Is there a better way to swap elements in a list?
Any inbuilt function or a library?

defmodule SwapElements do
  def swap(list, first_index, second_index) do
    {a, b} = split(list, first_index + 1)
    {x, y} = split(b, second_index - first_index)

    [h1 | t1] = a
    [h2 | t2] = x

    y
    |> reverse_append([h1])
    |> reverse_append(t2)
    |> reverse_append([h2])
    |> reverse_append(t1)
  end

  def split(list, n) do
    split([], list, n)
  end

  def split(a, b, 0) do
    {a, b}
  end

  def split(list, [h|t], n) do
    split([h | list], t, n - 1)
  end

  def reverse_append(list, []) do
    list
  end

  def reverse_append(list, [h | t]) do
    reverse_append([h | list], t)
  end
end
iex(27)> SwapElements.swap(Enum.to_list(0..10), 1, 4)        
[0, 4, 2, 3, 1, 5, 6, 7, 8, 9, 10]

First Post!

sarat1669

sarat1669

Shower Thought:
If lists in beam were implemented as XOR linked list
They can be be reversed in O(1)

Most Liked

al2o3cr

al2o3cr

I don’t know when this would ever be useful, but here’s a version of swap that even works on infinite streams!

defmodule Swap3 do
  def swap(a, i1, i2) do
    a
    |> Stream.with_index()
    |> Stream.transform(:start, &do_swap(&1, &2, i1, i2))
  end

  defp do_swap({el, idx}, :start, i1, _) when idx < i1 do
    {[el], :start}
  end

  defp do_swap({el, idx}, :start, i1, _) when idx == i1 do
    {[], {el, []}}
  end

  defp do_swap({el, idx}, {first_el, acc}, _, i2) when idx < i2 do
    {[], {first_el, [el | acc]}}
  end

  defp do_swap({second_el, idx}, {first_el, acc}, _, i2) when idx == i2 do
    result = Stream.concat([[second_el], Enum.reverse(acc), [first_el]])
    {result, :end}
  end

  defp do_swap({el, _}, :end, _, _) do
    {[el], :end}
  end
end

This uses Stream.transform with a reducer function that implements a tiny state machine to handle the change in behavior when the two indexes are passed.

It also chains:

Stream.iterate(0, &(&1 + 1))
|> Swap3.swap(5, 12)
|> Swap3.swap(2, 18)
|> Swap3.swap(7, 13)
|> Stream.take(20)
|> Enum.to_list()

# gives
[0, 1, 18, 3, 4, 12, 6, 13, 8, 9, 10, 11, 5, 7, 14, 15, 16, 17, 2, 19]

While it’s a streaming algorithm, it still needs to hold at least i2-i1 intermediate elements in memory since it can’t produce the i1th element until it’s seen the i2th.

Also beware: Swap3.swap does weird things if the supplied indexes aren’t in order (i1 < i2) or are equal.

al2o3cr

al2o3cr

Here are two possible approaches using functions from Enum and List:

defmodule Swap do
  def swap(a, i1, i2) do
    {first, [e1 | middle]} = Enum.split(a, i1)
    {middle, [e2 | rest]} = Enum.split(middle, i2-i1-1)
    List.flatten([first, e2, middle, e1, rest])
  end
end
defmodule Swap2 do
  def swap(a, i1, i2) do
    e1 = Enum.at(a, i1)
    e2 = Enum.at(a, i2)

    a
    |> List.replace_at(i1, e2)
    |> List.replace_at(i2, e1)
  end
end

Beware that both of these (just like the one in your post) have O(N) time-complexity since they have to traverse the entire list.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Swap2 is definitely the most clear imho, nice stuff.

@sarat1669 It’s probably worth noting that if you want to perform a bunch of index based changes to a “list” you are probably better off using a map with the indices as keys rather than a list, which just really isn’t setup for efficient index based access or changes.

Last Post!

dkuku

dkuku

I tough recursion will be the most performant way here and I think it is but implementation is quite long.
It’s still O(n) but probably 2x as performant as twice using replace_at

defmodule ListSwap do
  def swap(list, i, j) when i >= 0 and j >= 0 do
    # Ensure i is smaller than j
    {i, j} = if i <= j, do: {i, j}, else: {j, i}
    swap_pop(list, i, j, 0, [], nil)
  end

  defp swap_pop([], _i, _j, _current, _acc, _temp), do: raise("index out of bounds")

  # When we reach index i, store the element in temp
  defp swap_pop([hd | tl], i, j, i, acc, nil), do: swap_pop(tl, i, j, i + 1, acc, hd)

  # When we reach index j, swap with stored i element and start to go back
  defp swap_pop([hd | tl], i, j, j, acc, tmp), do: swap_push([tmp | tl], i, j, j - 1, acc, hd)

  # just move elements to accumulator
  defp swap_pop([hd | tl], i, j, curr, acc, tmp),
    do: swap_pop(tl, i, j, curr + 1, [hd | acc], tmp)

  # return reversed list
  defp swap_push(list, _i, _j, 0, [], nil), do: list

  # when replacing 0 with 1 edge case 
  defp swap_push(list, i, j, 0, [], tmp), do: swap_push([tmp | list], i, j, 0, [], nil)

  # replace i with stored j
  defp swap_push(tl, i, j, i, [hd | acc], tmp),
    do: swap_push([hd, tmp | tl], i, j, i - 1, acc, nil)

  # just push from acc to list
  defp swap_push(tl, i, j, curr, [hd | acc], tmp),
    do: swap_push([hd | tl], i, j, curr - 1, acc, tmp)
end

Where Next?

Popular in Questions Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

Other popular topics Top

greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement