计算列表中列表项的出现次数?

Kin*_*Dan 2 html enums elixir

(参见本文底部的编辑)

我正在Elixir中创建一个程序,它从我已经获得的标签列表中计算HTML标签的类型.这意味着密钥应该是标记,值应该是计数.

例如,在以下示例文件中

<html><head><body><sometag><sometag><sometag2><sometag>
Run Code Online (Sandbox Code Playgroud)

我的输出应该类似于以下内容:

html: 1
head: 1
body: 1
sometag: 3
sometag2: 1
Run Code Online (Sandbox Code Playgroud)

这是我的代码:

def tags(page) do
    taglist = Regex.scan(~r/<[a-zA-Z0-9]+/, page)

    dict = Map.new()

    Enum.map(taglist, fn(x) -> 
                        tag = String.to_atom(hd(x))
                        Map.put_new(dict, tag, 1)
                      end)

end
Run Code Online (Sandbox Code Playgroud)

我知道我应该使用它,Enum.each但是当我这样做时,我的字典最终只是空洞而不是错误.

Enum.map,这是我收到的输出:

iex(15)> A3.test
[%{"<html" => 1}, %{"<body" => 1}, %{"<p" => 1}, %{"<a" => 1}, %{"<p" => 1},
 %{"<a" => 1}, %{"<p" => 1}, %{"<a" => 1}, %{"<p" => 1}, %{"<a" => 1}]
Run Code Online (Sandbox Code Playgroud)

如您所见,有重复的条目,它变成了一个字典列表.现在我甚至都没有尝试让计数工作,只要字典不重复条目(这就是为什么值总是只有"1").

谢谢你的帮助.

编辑:------------------

好的,所以我发现我需要使用 Enum.reduce

以下代码生成我正在寻找的输出(现在):

def tags(page) do
    rawTagList = Regex.scan(~r/<[a-zA-Z0-9]+/, page)
    tagList = Enum.map(rawTagList, fn(tag) -> String.to_atom(hd(tag)) end)


    Enum.reduce(tagList, %{}, fn(tag, acc) -> 
                                    Map.put_new(acc, tag, 1)
                                end)

end
Run Code Online (Sandbox Code Playgroud)

输出:

%{"<a": 1, "<body": 1, "<html": 1, "<p": 1}
Run Code Online (Sandbox Code Playgroud)

现在我必须完成实际计算标签的挑战...如果有人能提供任何见解,我将不胜感激!

tko*_*wal 8

首先,用正则表达式解析html并不是最好的主意.有关详细信息(尤其是已接受的答案),请参阅此问题.

其次,您正在尝试用函数式语言编写命令式代码(这是关于代码的第一个版本).Elixir中的变量是不可变的.dict永远是一张空地图.Enum.map获取一个列表并始终返回相同长度的新列表,并且所有元素都已转换.您的转换函数采用空映射并将一个键值对放入其中.

因此,您将获得包含一个元素映射的列表.这条线:

Map.put_new(dict, tag, 1)
Run Code Online (Sandbox Code Playgroud)

不会dict在适当的位置更新,而是使用旧的创建新的,这是空的.在您的示例中,它与以下内容完全相同:

%{tag => 1}
Run Code Online (Sandbox Code Playgroud)

您有几种选择可以采用不同的方式.最近的方法是使用Enum.reduce.它需要一个列表,一个初始累加器和一个函数elem, acc -> new_acc.

taglist
|> Enum.reduce(%{}, fn(tag, acc) -> Map.update(acc, tag, 1, &(&1 + 1)) end)
Run Code Online (Sandbox Code Playgroud)

它看起来有点复杂,因为有几个很好的语法糖.taglist |> Enum.reduce(%{}, fun)是一样的Enum.reduce(taglist, %{}, fun).&(&1 + 1)是简写fn(counter) -> counter + 1 end.

Map.update 需要四个参数:要更新的映射,要更新的键,如果键不存在则为初始值,以及如果存在则对键执行某些操作的函数.

所以,这两行代码执行此操作:

  • 迭代列表 Enum.reduce
  • 从空地图开始 %{}
  • 获取当前元素和地图fn(tag, acc),并且:
    • 如果密钥不存在则插入1
    • 如果它存在增加一个 &(&1 + 1)