You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell permutationsNub去重排列生成代码逻辑咨询

你的改写核心逻辑是正确的:将顶层foldr替换为scanr仅用于输出每一步的中间累积结果,insert、combine的执行逻辑和原代码完全等价,运行结果一致也验证了改写有效性。

下面逐个解答你的疑问:

1. <*> 运算符的作用

(zip <*> tail . tails)是针对函数Applicative实例的紧凑无点写法,不是实现上的必须选择,只是作者的编码风格偏好。
函数Applicative实例的运算规则为f <*> g = \x -> f x (g x),展开后该表达式等价于可读性更高的普通写法:

\list -> zip list (tail (tails list))

它的作用是将输入列表拆分为所有「拆分点首元素 + 拆分点后剩余元素」的元组,正好对应往列表中插入新元素的所有可能切分位置。例如传入列表[7,8]时:

  • tails [7,8]生成[[7,8],[8],[]]
  • 取tail后得到[[8],[]]
  • 和原列表zip后得到[(7, [8]), (8, [])],分别对应「在7前插入」「在8前插入」两个切分点,最末尾的插入位置由foldr的初始值覆盖。
2、3. 类型理解偏差的原因:变量命名误导

你对参数类型的推导错误完全来自变量命名的偏差,逐一把参数类型拆清就不存在类型不匹配的问题:
首先注意外层是concatMap . insert,concatMap会将insert逐次应用到每一个已生成的单排列上,而非一次性传入整个排列列表,因此insert的两个参数类型为:

  • 第一个参数digit/y:单个待插入元素(如Int类型)
  • 第二个参数:某一个已生成的排列([Int]类型,不是排列列表)

在此基础上看内部combine函数的参数:

  • 第一个参数(x, xs):是前述zip生成的拆分元组,x是拆分点的首元素(单个Int值,不是列表),xs是拆分点后的剩余元素([Int]类型)
  • 第二个参数xss:foldr的累积值,存储在当前拆分点右侧所有位置插入digit得到的排列集合([[Int]]类型)

你之前将x误命名为permX认为是列表类型,将累积值误命名为digitAsDoubleList,才会出现类型推导矛盾。我们拿你提到的「第二轮digit=7、处理排列[8]」的场景验证:

  • zip生成的拆分元组为[(8, [])]
  • foldr初始值为[[7]],对应在列表最末尾插入7得到的排列[8,7]
  • 处理唯一元组(8, []):首先生成本拆分点插入7的结果7:8:[] = [7,8];判断7≠8,因此将8拼接在累积值所有排列的头部,得到map (8:) [[7]] = [[8,7]];最终结果为[7,8] : [[8,7]] = [[7,8],[8,7]],类型完全自洽,和你观测到的中间结果一致。
4. 去重逻辑的核心原理

作者提到的「不位于和待插入元素相等的头部元素之后的位置」,本质是一套约定式的去重规则:往已有排列中插入新元素y时,从左向右遍历插入位置,只要遇到第一个和y相等的元素,插入完该元素前的位置就立刻停止,不再遍历该元素之后的插入位置,以此避免生成重复排列。
我们用你举的「待插入元素为7,已有尾部排列集合为[[7,8],[8,7]]」的场景验证:

  1. 处理第一个排列[7,8]:
    • 从右向左foldr遍历拆分点,先处理最右侧的(8, []),得到插入结果[[7,8,7], [8,7]]
    • 再处理左侧拆分点(7, [8]):生成本位置插入结果[7,7,8],判断发现y=7和x=7相等,此时直接跳过后续累积值(也就是不在7之后的位置插入),仅返回[[7,7,8]]
    • 跳过的原因很简单:在7之后的位置插入7,要么和当前位置插入的结果重复(如插在7和8之间得到的[7,7,8]和头部插入结果完全一致),要么会在处理其他排列时生成(如插在末尾得到的[7,8,7]会在处理排列[8,7]时生成),不需要重复计算。
  2. 处理第二个排列[8,7]:
    • 从右向左遍历拆分点,先处理最右侧的(7, []),生成本位置插入结果[7,7],判断发现y=7和x=7相等,跳过后续累积值,返回[[7,7]]
    • 再处理左侧拆分点(8, [7]):生成本位置插入结果[7,8,7],判断7≠8,将8拼接在累积值所有排列头部得到[[8,7,7]],最终返回[[7,8,7], [8,7,7]]
  3. 最后将两个排列的插入结果通过concat拼接,得到[[7,7,8], [7,8,7], [8,7,7]],正好是无重复的全排列结果。

这套逻辑不需要生成重复排列后再调用nub去重,时间和内存效率都比先生成全排列再去重的方案高很多。


内容的提问来源于stack exchange,提问作者Κωστής Καρβουνιάρης

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 22:03:32