【问题标题】:Elixir - modifying values external to an anonymous functionElixir - 修改匿名函数外部的值
【发布时间】:2016-08-31 17:36:21
【问题描述】:

首先,我绝对确定我的做法是错误的,因为我仍在学习来自 Ruby 的 Elixir...

我从 youtube 获取搜索结果列表,并尝试提取观看次数最多的视频。

# html is the contents of the search results page
metas = html |> Floki.find(".yt-lockup-meta-info > li")

counter = -1
index = -1
high_views = 0

Enum.each(metas, fn(li) ->
  counter = counter + 1
  text = Floki.text(li)
  case String.split(text, " ") do
    [count, "views"] ->
      views = String.to_integer(String.replace(count, ",", ""))
      IO.puts(">>> #{counter} - #{to_string(views)} views")
      if views > high_views do
        high_views = views
        index = counter
      end
    [age, time_measurement, "ago"] ->
      nil
  end
end)

metasli 元组的列表,如下所示:

[{"li", [], ["2 years ago"]}, {"li", [], ["5,669,783 views"]},
 {"li", [], ["9 years ago"]}, {"li", [], ["17,136,804 views"]},
 ...
 {"li", [], ["1 year ago"]}, {"li", [], ["15,217 views"]},
 {"li", [], ["8 years ago"]}, {"li", [], ["909,053 views"]}]

这不起作用,因为传递给 Enum.each 的匿名函数有自己的范围,并且不会设置 indexhigh_views 的值。

有没有办法将值从外部范围传递到匿名函数?或者一个更好的问题是,我应该怎么做?

我打算让它工作,然后重构代码,但我被卡住了。感谢您的帮助。

【问题讨论】:

    标签: elixir


    【解决方案1】:

    正如我所猜测的那样,我确实做错了。以下是我最终完成这项工作的方式:

    defp extract_song_url_from_youtube_response(html = _) do
      sorted = html
      |> Floki.find(".yt-lockup-content")
      |> Enum.sort(fn(item1, item2) -> view_count(item1) > view_count(item2) end)
    
      [_, id] = Enum.at(sorted, 0)
      |> Floki.find("h3 > a")
      |> Floki.attribute("href")
      |> Enum.find(fn(x) -> x =~ "/watch" end)
      |> String.split("=")
      "https://www.youtube.com/embed/" <> id
    end
    
    defp view_count(item) do
      meta = item |> Floki.find(".yt-lockup-meta-info > li")
      views = case Enum.at(meta, 1) do
        {"li", _, viewlist} ->
          parts = String.split(Enum.at(viewlist, 0), " ")
          String.to_integer(String.replace(Enum.at(parts, 0), ",", ""))
        nil ->
          # most likely a playlist
          0
      end
    end
    

    因此,我没有尝试从匿名函数范围之外修改变量,而是退回到 HTML 层次结构,并根据视频收到的观看次数对每个 &lt;div&gt; 结果进行排序。

    Elixir 真是太棒了,一旦我能把头缠在它身上,不再试图强迫事情像 Ruby 一样。

    【讨论】:

    • Enum.sort_by 可以简化您对Enum.sort_by(items, &amp;view_count/1) 的排序。
    【解决方案2】:

    Elixir 是不可变的。该函数是一个闭包,因此外部变量在那里可见,但您不能改变它们。您只能重新绑定它们,但重新绑定保留在内部的匿名函数范围内。

    但是你想要做的工具都在Enum模块中。

    您实际上是在寻找具有最大浏览量的索引。让我们看一下Enum functionsEnum.max_by/2 看起来很有希望。它需要一个可枚举和一个返回我们想要最大化的值的函数。我会将它与Enum.with_index/1 配对,它接受一个列表,并使用该元素的索引将每个元素包装在一个元组中。

    metas
    |> Enum.with_index
    |> Enum.max_by(fn {li, index} ->
      text = Floki.text(li)
      case String.split(text) do # (splits on whitespace by default)
        [count, "views"] ->
          views = count |> String.replace(",", "") |> String.to_integer
          IO.puts ">>> #{index} - #{views} views"
          views
        _ -> -1
      end
    end)
    

    与您的实现的主要区别在于内部函数根据其参数返回一个值,而不是尝试改变外部状态。

    我将“什么都不做”的情况折叠为一个简单的包罗万象的_,并在 youtube 视频没有负观看次数的假设下返回 -1。您的示例的直接翻译将在此处返回零(您的 high_views 的初始值)。这也可能是安全的。

    【讨论】:

    • 谢谢@martin-svalin。我相信这也会让我到达那里。我想我只需要用 Elixir 解决更多问题就可以了。
    • 当然,如果你只想要元素,而不想要索引,你可以去掉with_index函数,改变匿名函数参数的模式,删除IO.puts调试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-10
    • 1970-01-01
    • 2018-08-16
    • 2014-03-25
    • 2018-11-14
    • 2016-10-04
    • 2018-03-07
    相关资源
    最近更新 更多