Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Sorting with Quicksort and Merge Sort

sorting.livemd

Sorting with Quicksort and Merge Sort

Mix.install([
  {:kino, "~> 0.12.3"},
  {:vega_lite, "~> 0.1.5"},
  {:kino_vega_lite, "~> 0.1.7"}
])

Introduction

Sorting has long been one of the classic disciplines of algorithmics. In fact, it is so important that it takes up half of the title of the [rd volume of “The Art of Computer Programming”!

This workbook implements two sorting algorithms, namely Quicksort and Merge Sort.

Cases

cases = %{
  "small" => [2, 6, 3, 1, 5, 9, 7, 8, 0, 4],
  "100_000 uniform(1024)" => 1..100_000 |> Enum.map(fn _ -> :rand.uniform(1024) end),
  "100_000 normal(1024)" => 1..100_000 |> Enum.map(fn _ -> :rand.uniform(1024) end)
}

Algorithms

Merge-sort:

defmodule MergeSort do
  def sort([]), do: []
  def sort([v]), do: [v]

  def sort(input) do
    l = trunc(length(input) / 2)
    {left, right} = Enum.split(input, l)
    merge(sort(left), sort(right))
  end

  defp merge([], v), do: v
  defp merge(v, []), do: v
  defp merge([x | xs], [y | ys]) when x <= y, do: [x | merge(xs, [y | ys])]
  defp merge([x | xs], [y | ys]), do: [y | merge([x | xs], ys)]
end

Quicksort:

defmodule QuickSort do
  def sort([]) do
    []
  end

  def sort([pivot | input]) do
    {pre, post} = Enum.split_with(input, fn value -> value < pivot end)
    Enum.concat(sort(pre), [pivot | sort(post)])
  end
end
algorithms = %{
  "Merge-Sort" => &amp;MergeSort.sort/1,
  "Quicksort" => &amp;QuickSort.sort/1
}

Interface

elements = [
  Kino.Input.select("Case:", Enum.map(cases, fn {k, _v} -> {k, k} end)),
  Kino.Input.select("Algorithm:", Enum.map(algorithms, fn {k, _v} -> {k, k} end))
]

Kino.Layout.grid(elements)
[case_name, algorithm_name] =
  Enum.map(elements, fn element -> Kino.Input.read(element) end)
{case, algorithm} = {
  Map.get(cases, case_name),
  Map.get(algorithms, algorithm_name)
}

Execution

t0 = :os.system_time(:millisecond)
sorted = algorithm.(case)
t1 = :os.system_time(:millisecond)
sorted
"Execution took #{t1 - t0}ms"

Verification

Visual check:

alias VegaLite, as: Vl

data = Enum.with_index(sorted, fn value, i -> %{"i" => i, "value" => value} end)

Vl.new(width: 600, height: 512)
|> Vl.data_from_values(data)
|> Vl.transform(
  filter: [
    and: [
      [field: "i", valid: true],
      [field: "value", valid: true]
    ]
  ]
)
|> Vl.mark(:rect)
|> Vl.encode_field(:x, "i", type: :quantitative, bin: [maxbins: 256])
|> Vl.encode_field(:y, "value", type: :quantitative, bin: [maxbins: 256])
|> Vl.encode(:color, aggregate: :count)
|> Vl.config(view: [stroke: nil])
|> Kino.VegaLite.new()

Proper verification:

sorted
|> List.foldl({0, true}, fn value, {last, success} -> {value, success and last <= value} end)