Elixir - 修改匿名函数外部的值

时间:2016-05-05 19:38:15

标签: elixir

首先,我绝对相信我会以错误的方式解决这个问题,因为我还在学习来自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的值。

有没有办法将值从外部作用域传递到匿名函数?或者更好的问题是,我该怎么做呢?

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

2 个答案:

答案 0 :(得分:6)

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的初始值)。那也许是安全的。

答案 1 :(得分:0)

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

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层次结构,并根据视频收到的观看次数对每个<div>结果进行排序。 / p>

Elixir真是令人惊叹,一旦我能够绕过它并停止试图强迫事物像Ruby一样。