参考JS实现,如何在Elixir中用Map结合不可变性统计字符次数?
问题:Elixir中基于不可变性用Map统计字符出现次数的最佳实现?
在JavaScript中,我可以通过以下代码统计字符出现次数:
function countIt(str){ let obj = {}; for(let i = 0; i < str.length; i++){ if(!obj[str[i]]){ obj[str[i]] = 1; } else { obj[str[i]]++; } } return obj; } console.log(countIt("hello")); //返回 { e: 1, h: 1, l: 2, o: 1 }请问考虑到Elixir的不可变性特性,使用Map统计每个字符出现次数的最佳实现方式是什么?
回答
在Elixir里,所有数据都是不可变的,我们没法像JS那样直接修改对象属性,得用函数式的累加思路来处理统计需求,最常用也最优雅的方式就是借助Enum.reduce/3,它能帮我们遍历字符序列,每次返回一个新的Map来记录统计结果,完美契合不可变性的设计。
基础优雅实现
这是最贴近需求逻辑的实现,可读性和简洁性都拉满:
def count_it(str) do String.graphemes(str) |> Enum.reduce(%{}, fn char, acc -> Map.update(acc, char, 1, &(&1 + 1)) end) end # 测试调用 IO.inspect(count_it("hello")) # 输出 %{"e" => 1, "h" => 1, "l" => 2, "o" => 1}
我拆解下关键步骤:
String.graphemes(str):把字符串拆成单个字符的列表,比如"hello"会变成["h", "e", "l", "l", "o"],这一步是为了让字符串能被Enum模块处理。Enum.reduce/3:第一个参数是待遍历的字符列表,第二个是初始累加器(空Map%{}),第三个是处理每个字符的匿名函数。Map.update/4:这个函数简直是为统计场景量身定做的——如果Map里没有当前字符,就把它的计数设为1;如果已经存在,就把对应的值加1。这里的&(&1 + 1)是Elixir的简写匿名函数,等价于fn current_value -> current_value + 1 end。
模式匹配风格实现
如果你想更贴近函数式的模式匹配思路,也可以这么写,逻辑和上面完全一致,只是写法不同:
def count_it(str) do String.graphemes(str) |> Enum.reduce(%{}, fn char, acc when is_map_key(acc, char) -> Map.put(acc, char, acc[char] + 1) char, acc -> Map.put(acc, char, 1) end) end
这里通过对累加器的模式匹配,判断字符是否已存在,分别处理两种情况,适合喜欢用模式匹配表达逻辑的开发者。
小提醒
- Elixir里
String.graphemes返回的是字符串类型的单个字符(比如"h"),所以统计的键都是字符串。如果你想用字符码(比如?h对应ASCII码104)作为键,可以用String.to_charlist(str)来拆分字符串。 - 不可变性的好处:每次
Map.update或Map.put都会返回新的Map,原Map不会被修改,这避免了并发场景下的竞态问题,也让代码更安全、可预测。
内容的提问来源于stack exchange,提问作者Iggy
相关产品推荐
相关产品推荐

