idi527

idi527

Is there a way to compare iolists for equality without converting them to binary?

Or is a custom function needed?

Something like (currently incorrect, doesn’t account for nested lists):

defmodule Comparer do
  @moduledoc """
  Equality comparison for iolists consisting of binaries.
  """

  @spec equal?(iolist, iolist) :: boolean
  def equal?(iolist1, iolist2)

  def equal?(iolist1, iolist2) do
    equal?(iolist1, iolist2, [])
  end

  defp equal?(
         [<<char, rest_bin1::bytes>> | rest_iolist1],
         [<<char, rest_bin2::bytes>> | rest_iolist2],
         []
       ) do
    equal?([<<rest_bin1::bytes>> | rest_iolist1], [<<rest_bin2::bytes>> | rest_iolist2], [])
  end

  defp equal?(
         [<<>> | rest_iolist1],
         [<<>> | rest_iolist2],
         []
       ) do
    equal?(rest_iolist1, rest_iolist2, [])
  end

  defp equal?(
         [<<>> | rest_iolist1],
         iolist2,
         []
       ) do
    equal?(rest_iolist1, iolist2, [])
  end

  defp equal?(
         iolist1,
         [<<>> | rest_iolist2],
         []
       ) do
    equal?(iolist1, rest_iolist2, [])
  end

  defp equal?(
         [<<char, rest_bin1::bytes>> | rest_iolist1],
         rest2,
         [<<char, rest_bin3::bytes>> | rest_buffer2]
       ) do
    equal?([<<rest_bin1::bytes>> | rest_iolist1], rest2, [<<rest_bin3::bytes>> | rest_buffer2])
  end

  defp equal?(
         [<<>> | rest_iolist1],
         rest2,
         [<<>> | rest_buffer2]
       ) do
    equal?(rest_iolist1, rest2, rest_buffer2)
  end

  defp equal?(
         [<<>> | rest_iolist1],
         rest2,
         buffer2
       ) do
    equal?(rest_iolist1, rest2, buffer2)
  end

  defp equal?([], [], []) do
    true
  end

  defp equal?(_, _, _) do
    false
  end
end

Marked As Solved

NobbZ

NobbZ

Equality is easy, just use ==/2. But I fear it’s equivalency you want to check. And to be honest, I think flushing them into binary and compare equality on those should be the easiest thing.

Also Liked

hauleth

hauleth

@NobbZ already told you that equality != equivalence, but let see why you cannot compare iolists and why your comparator is terribly wrong.

See what Typespecs say about iolist type:

iolist() maybe_improper_list(byte() | binary() | iolist(), binary() | [])

But what is improper list?

So all of us get used to it that list in Erlang is head and tail or an empty list (yes empty list is special case of list). So when we do [a | b] = list then is_list(b) == true. Simple. But what when it isn’t? Nothing in Erlang prevents you from doing [1 | 2]. Yes this is proper syntax, yes it will compile, yes it is used. This is called improper list and your Comparer will fail on that.

Second thing is that iolist can be:

  • infinitely nested, so [[[[[["a"]]]]]] is also proper iolist that is equivalent to ["a"]
  • can contain raw bytes, so ["a", 97] is equivalent to ["aa"]
  • can contain Erlang strings, which are lists ['a'] == [[97]] == [[?a]] which is equivalent to ["a"]

So some hard examples for you to compare:

  • ["foo", 'bar' | ?z] vs [["foo" | "bar"], "z"]
  • ['a' | "ą"] vs ["aą"]
  • ["aa", ?a, 'aa'] vs ["a", ?a, 'aa' | "a"]
rvirding

rvirding

Creator of Erlang

None that I know of. The thing to be aware of is that there is no real iolist data type, it is just a nested recursive structure which happens to be interpreted in a special way in some cases. The main one being that on output the BEAM will automagically flatten it and output the bytes. This means that in many case you don’t have to pay the price of flattening it.

That should be all now. :smile:

rvirding

rvirding

Creator of Erlang

The reason converting the iolists to binaries and then doing and equals test is that these are both implemented in the VM in C while your code is in Elixir. No, will not go faster if you were to write it in Erlang. [*]

You can probably make your code more efficient when comparing binaries by directly comparing the longest leading chunk in one go instead of doing it byte by byte. And it is fun code to write as well. Another fun function is one that takes an iolist and returns the Nth byte.

[*] Of course the Erlang would be more beautiful. :wink:

idi527

idi527

Just benchmarked my incomplete implementation from above against IO.iodata_to_binary followed by ==, and my implementation was 40 times slower.

iolist1 = ["SELECT ", "a,", "b,", "c", " FROM somewhere", " WHERE a = 5"]
iolist2 = ["SELECT ", "a,", "b,", "c", " FROM somewhere WHERE a = 5"]

Benchee.run(%{
  "Comparer.equal?/2" => fn -> Comparer.equal?(iolist1, iolist2) end,
  "to binary and then compare" => fn ->
    IO.iodata_to_binary(iolist1) == IO.iodata_to_binary(iolist2)
  end
})
Benchmarking Comparer.equal?/2...
Benchmarking to binary and then compare...

Name                                 ips        average  deviation         median         99th %
to binary and then compare        1.40 M        0.71 μs ±10280.07%           1 μs           1 μs
Comparer.equal?/2               0.0342 M       29.28 μs   ±107.85%          23 μs         112 μs

Comparison:
to binary and then compare        1.40 M
Comparer.equal?/2               0.0342 M - 40.97x slower

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
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
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
977 41022 311
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

We're in Beta

About us Mission Statement