James_E

James_E

Is reduce_while/3 the correct function for this use-case?

In my code here, work_loop uses Enum.reduce_while/3, but as you can see it’s just discarding the accumulator.

I’m wondering if that’s a sign that there’s a more appropriate function for this?

import Bitwise
require Logger

# TS3H.demo("MEkDAgcAAgEgAh9n5rQmt3zJukGmwwvEVwfbZFS3NJ9oW7hgpJ0Db5ORAh8mOLmbP3+DfEQvEFbOcqymWoF6s9GHVcv8UegJBcWz", 1102362)


defmodule TS3H do

  defp work_loop(intermed_state, goal, ctr_start, ctr_end) do
    Enum.reduce_while(
      ctr_start..ctr_end,
      {nil, -1, -1},
      fn ctr, _ ->
        score = phase2(intermed_state, ctr)
        if score < goal do
          {:cont, {nil, ctr, score}}
        else
          {:halt, {:ok, ctr, score}}
        end
      end
    )
  end

  def demo(pk_str, ctr_best) do
    demo(pk_str, ctr_best, ctr_best)
  end
  def demo(pk_str, ctr_best, ctr_cur) do
    intermed_state = phase1(pk_str)
    # FIXME add a warning about bad keys / slow phase
    record = phase2(intermed_state, ctr_best)
    Logger.info("Current score: #{record}")
    Logger.info("Skipping already checked: #{ctr_cur - ctr_best + 1}")
    # Skip ctr_cur since that's *already been* checked
    {outcome, ctr_last, last_score} = work_loop(intermed_state, record, ctr_cur + 1, ctr_cur + 2**24)
    case outcome do
      nil ->
        Logger.notice("Got to #{ctr_last} without any progress.")
        {record, ctr_best, ctr_last}
      :ok ->
        if last_score > record do
          Logger.notice("Improved score!! #{ctr_last} -> #{last_score}")
          {last_score, ctr_last, ctr_last}
        else
          Logger.notice("Found new head. #{ctr_last} -> #{record}")
          {record, ctr_best, ctr_last}
        end
    end
  end

  def phase1(pk_str) do
    :crypto.hash_init(:sha)
    |> :crypto.hash_update(pk_str)
  end

  def phase2(state, ctr) do
    :crypto.hash_update(state, Integer.to_string(ctr))
    |> :crypto.hash_final()
    |> ts3_binary_lzcnt()
  end

  def ts3_binary_lzcnt(hash) do  # FIXME performance :(
    # Octets are consumed in network order.
    # Bits are consumed starting from the least significant bit.
    # Examples:
    #     <<255,   0,   0,   0>> -> 0
    #     <<  1,   0,   0,   0>> -> 0
    #     <<  0,   1,   0,   0>> -> 8
    #     <<  0, 255,   0,   0>> -> 8 
    #     <<  0, 254,   0,   0>> -> 9
    Stream.unfold(hash, fn
      # https://forum.elixirforum.net/t/trouble-understanding-octet-string-binary-iteration/62304
      <<i::8, rem::binary>> -> {i, rem}
      <<>> -> nil
    end)
    |> Enum.reduce_while(
      {:cont, 0},
      fn
        0, {:cont, acc_val} -> {:cont, {:cont, acc_val + 8}}
        x, {:cont, acc_val} -> {:cont, {:halt, acc_val + tzcnt(x, nil)}}
        _, {:halt, acc_val} -> {:halt, {:halt, acc_val}}
      end
    )
    |> elem(1)
  end

  defp tzcnt(i, nil, count) when i !== 0 do
    # h/t https://programming-idioms.org/idiom/262/count-trailing-zero-bits/5157/erlang
    if (i &&& 1) === 0 do
      tzcnt(i >>> 1, nil, count + 1)
    else
      count
    end
  end
  defp tzcnt(i, max) do
    if i === 0 do
      max
    else
      tzcnt(i, nil, 0)
    end
  end

end

The forum software correctly suggested that I take a look at thread #34178, but the suggestion there of using Enum.find_value/2 is an awkward fit because I like being able to return a single “semantically uniform” datatype rather than having special-cased shaped matches on the caller’s end.

I looked around the Enum documentation, but it’s really sprawling; there are so many unique functions, each filling a clear niche, and it’s possible I overlooked one that would fit my use-case.

Most Liked

nicocirio

nicocirio

Hi!, I think recursion may be a good approach.

  defp work_loop(intermed_state, goal, ctr_start, ctr_end) do
    loop(ctr_start..ctr_end, intermed_state, goal)
  end

  defp loop(ctrs, intermed_state, goal, result \\ nil)

  defp loop([current_ctr | rest], intermed_state, goal, nil) do
    score = phase2(intermed_state, current_ctr)

    if score < goal do
      loop(rest, intermed_state, goal)
    else
      loop(rest, intermed_state, goal, {:ok, current_ctr, score})
    end
  end

  defp loop(_ctrs, _intermed_state, _goal, result), do: result

I haven’t tested it, but I think it works fine.
If you implement this approach, I suggest refactoring the args of work_loop for it to directly be the recursive function.

Eiji

Eiji

It have “awkward” returns because the intention was to return them like that. You can return same type or different types based on pattern-matching - it’s up to you and your use case. find_value/3 simply discards nil and finishes as soon as it finds a value.

Here goes a simplified example:

defmodule Example do
  def sample(first, last, goal) do
    Enum.find_value(first..last, fn element ->
      # replace this with your own function call
      result = double(element)
      # this line returns {element, result} when the result of expression on the left side
      # is truthy i.e. it's not nil or false
      result >= goal && {element, result}
    end)
  end

  # replace this with your own function
  defp double(integer), do: integer * 2
end

{element, result} = Example.sample(1, 10, 11)
IO.inspect {element, result}
# 1 * 2 = 2;  2 >= 11 -> false (skipped)
# 2 * 2 = 4;  4 >= 11 -> false (skipped)
# 3 * 2 = 6;  6 >= 11 -> false (skipped)
# 4 * 2 = 8;  8 >= 11 -> false (skipped)
# 5 * 2 = 10; 10 >= 11 -> false (skipped)
# 6 * 2 = 12; 12 >= 11 -> true
# rest is skipped because value is already found
# {6, 12} is returned

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
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
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
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
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement