※ 引述《lambking (BB)》之铭言:
: 想要写一个function 去计算一个字串中某元素出现的次数
: 例如:
: list={a,a,b,b,c,c,c,} ;
: frequncycount[list]
: output:
: {{a,2},{b,2},{c,3}}
: 请问有什么方法能够写这个function ?
: 谢谢
诚如chungyuandye所言
Mathematica内建的Tally指令是这个问题最好最快的解法
Tally甚至还能自订"相同"元素的比对函数
但如果题目稍微变化一下呢?
像是想以连续相同元素个数对表示一串行
例如将{a,a,a,b,b,a,b,b,b,a,a}变成{{a,3},{b,2},{a,1},{b,3},{a,2}}
该怎么计算呢?
当然可以用循环轻易地达成
不过这就无法发挥Mathematica的长处了
其实这类问题能够非常直觉地以pattern跟rule解决
这也是Mathematica另一个强大之处
直接写出原po问题的pattern/rule解法:
{#, 1} & /@ list //. {head___, {x_, n_}, mid___, {x_, m_}, tail___} ->
{head, mid, tail, {x, n + m}}
这么直白的程式码 相信许多人已经了解了其计算原理
一开始的 {#,1} & /@ list 利用纯函数转换原串行
例如{a,b,a,b,c,c,c}将变为{{a,1},{b,1},{a,1},{b,1},{c,1},{c,1},{c,1}}
这边的 1 就是代表出现次数
接着后面的 //. 表示将持续替换后面的rule直到结果不再变化为止
如果写的是 /. 那就表示仅替换一次
这里先点到功能,稍后会说明为什么要使用 //.
重点来了
后面的rule是这个程式码的计算核心:
{head___, {x_, n_}, mid___, {x_, m_}, tail___} -> {head, mid, tail, {x, n+m}}
简要介绍一下:
head___、mid___、tail___中的三连续底线
表示这些pattern可以匹配任意序列(sequence),并且可为空序列
如果只有2个连续底线也可匹配任意序列,但不包含空序列
至于单一底线,则是匹配任意表示式(expression)
例如其后的x_、n_、m_等
初学者在这边只要简单地想像为单一元素即可
仔细看的话会发现箭头的左边出现了两个 x_
这表示两个pattern必须匹配相同的元素
而右边只有出现一次
表示这个rule每作用一次就会减少一项
换句话说,它会合并相同的 x_ 项
并且将其出现次数相加
至于使用head___、mid___、tail___这三个pattern的原因
是为了表示这两个x_之前、之后、之间可以存在任意序列,包含空序列
只要不断地重复这个步骤 最后就能得到我们要的结果
这就是要使用 //. 的原因
介绍完这个解法之后,相信各位会觉得这真是太直接了
甚至可以说这个rule同时描述了问题以及解法
这是Mathematica pattern/rule好用之处
可惜的是,方便跟效率经常位于天平的两端
这个方法的效率并无法与Tally相比
不过在串行不大的情况下,还是挺好用的
行文至此,忍不住要为各位出一道作业
如果将{x,n+m}换个位置:
{head___, {x_, n_}, mid___, {x_, m_}, tail___} -> {head,{x,n+m}, mid, tail}
输出结果是相同的(但顺序可能不同)
但效率变得奇差无比
各位可以思考一下原因
回到先前抛出来的问题:
如何以连续相同元素个数对表示一串行
解法非常直觉:
{#, 1} & /@ list //.{head___,{x_,n_},{x_,m_},tail___}-> {head, {x, n+m}, tail}
后话
pattern/rule不常被注意 但其实很好用
至于中文参考书籍可参考最近出版的Mathematica Cookbook