在排列上应用处理程序,而不使用级别缓存?

huangapple go评论79阅读模式
英文:

Apply handler on permutation, without cache of levels?

问题

我想编写一个函数,将给定的处理程序应用于输入的所有排列,而不返回整个排列。


代码

(使用 go 语言)

  • 寻找排列:

    // 对每个组合应用给定的处理程序,并仅返回计数,
    func FindAllPermutationApplyHandler[T any](ts []T, handler func([]T)) int {
        n := 0
        combList := [][]T{{}} // 当输入为空时,有 1 个空组合,而不是 0 个组合
        for i := len(ts) - 1; i >= 0; i-- {
            isLastLevel := false
            if i == 0 {
                isLastLevel = true
            }
    
            // prefix := ts[0:i]
            mover := ts[i]
            // fmt.Printf("prefix = %v, mover = %v:\n", prefix, mover)
            var combList2 [][]T // 添加一个额外项后的组合
            for _, comb := range combList {
                for j := 0; j <= len(comb); j++ { // 在 comb 的索引 j 处插入 mover
                    comb2 := append(append(append([]T{}, comb[0:j]...), mover), comb[j:]...) // 新空 + 左 + mover + 右
                    if isLastLevel {
                        n++
                        handler(comb2)
                    } else {
                        combList2 = append(combList2, comb2)
                    }
                }
            }
    
            combList = combList2
        }
    
        return n
    }
    
  • 测试用例(简单):

    func TestFindAllPermutationApplyHandler(t *testing.T) {
        assert.Equal(t, FindAllPermutationApplyHandler([]int{1, 2, 3}, func(comb []int) {
            fmt.Printf("\t%v\n", comb)
        }), 6)
    }
    

解释

  • 上述函数 FindAllPermutationApplyHandler() 可以找到排列,并对每个组合应用给定的处理程序。
  • 但是它需要缓存前面的 n-1 层(同时缓存最近的两层)。
  • 我已经避免了对最后一层的缓存,因为没有更多的层依赖于它。

问题

    1. 是否有可能避免缓存最近的两层?
      (即使空间复杂度为 O(1)O(n),甚至 O(n^2) 也更好)
    1. 但是对我来说似乎是不可能的,因为第 i 层是基于第 i-1 层的,对吗?
    1. 如果是这样,是否有更好的算法可以减少空间复杂度?并且更倾向于迭代(而不是递归)。
英文:

I want to write a function that apply given handler to all permutation of input, without return the whole permutation.


Code

(in go)

  • Find permutation:

    // apply given handler on each combination, and return count only,
    func FindAllPermutationApplyHandler[T any](ts []T, handler func([]T)) int {
        n := 0
        combList := [][]T{{}} // when empty input, has 1 empty combination, not 0 combination,
        for i := len(ts) - 1; i &gt;= 0; i-- {
            isLastLevel := false
            if i == 0 {
                isLastLevel = true
            }
    
            // prefix := ts[0:i]
            mover := ts[i]
            // fmt.Printf(&quot;\nprefix = %v, mover = %v:\n&quot;, prefix, mover)
            var combList2 [][]T // combinations with an extra item added,
            for _, comb := range combList {
                for j := 0; j &lt;= len(comb); j++ { // insert mover at index j of comb,
                    comb2 := append(append(append([]T{}, comb[0:j]...), mover), comb[j:]...) // new_empty + left + mover + right
                    if isLastLevel {
                        n++
                        handler(comb2)
                    } else {
                        combList2 = append(combList2, comb2)
                    }
                }
            }
    
            combList = combList2
        }
    
        return n
    }
    
  • Test case (simple):

    func TestFindAllPermutationApplyHandler(t *testing.T) {
        assert.Equal(t, FindAllPermutationApplyHandler([]int{1, 2, 3}, func(comb []int) {
            fmt.Printf(&quot;\t%v\n&quot;, comb)
        }), 6)
    }
    

Explanation

  • The above function FindAllPermutationApplyHandler() can find permutation, and apply given handler to each combination.
  • But it need to cache the previous n-1 levels (most recent 2 levels at the same time).
  • I've already avoided cache of the final level, since no more level depends on it.

Questions

    1. Is it possible to avoid cache of the recent 2 levels ?
      (aka. make the space complexity O(1) or O(n), or even O(n^2) is much better I guess).
    1. But it seems impossible to me, since level i is based on level i-1, right?
    1. If so, is there a better algorithm that reduce the space complexity? And iteration is preferred (than recursion).

答案1

得分: 1

听起来你正在寻找Pandita算法

这是一种按字典顺序迭代生成数组所有排列的简单方法。

但是,这需要你能够对数组元素进行排序。如果你不能排序(因为它们是通用类型),那么你可以创建一个包含数组索引的辅助数组,并对其进行排列生成。

英文:

It sounds like you're looking for Pandita's algorithm

This is a simple way to iteratively generate all permutations of an array in lexicographic order.

It requires that you can order elements of your array, however. If you can't (because they're of generic type), then you can make a secondary array of all the array indexes, and generate permutations of that.

答案2

得分: 0

根据Matt Timmermans的回答,我实现了Pandita's算法,它是无缓存顺序的。


代码

(使用go语言)

  • 按顺序找到排列:

    package permutation_util
    
    import (
        "fmt"
        "golang.org/x/exp/constraints"
        "golang.org/x/exp/slices"
    )
    
    // 默认排序
    func FindAllPermutationInOrderApplyHandlerDefaultSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int)) int {
        return FindAllPermutationInOrderApplyHandlerCustomizedSort(ts, handler, nil)
    }
    
    // 逆序排序
    func FindAllPermutationInOrderApplyHandlerReversedSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int)) int {
        return FindAllPermutationInOrderApplyHandlerCustomizedSort(ts, handler, func(a, b T) bool {
            return a > b // 逆序
        })
    }
    
    // 根据给定的比较函数排序
    func FindAllPermutationInOrderApplyHandlerCustomizedSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int), less func(a, b T) bool) int {
        if less != nil {
            slices.SortFunc(ts, less)
        } else {
            slices.Sort(ts)
        }
        return FindAllPermutationInOrderApplyHandlerNoSort(ts, handler)
    }
    
    // 使用Pandita's算法按顺序找到[]T的所有排列
    // ts应该已经按照所需的顺序排序
    // 对每个组合应用给定的处理函数,并返回计数
    // 重复元素:它还处理了重复元素的情况,即不会生成重复的组合
    // 参考:https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order
    func FindAllPermutationInOrderApplyHandlerNoSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int)) int {
        is := genInitialIs(ts)
        fmt.Printf("initial order: %v\n", is)
    
        n := 1 // 包括初始组合
        handler(ts, is)
    
        size := len(ts)
    
        for {
            var k int
            for k = size - 2; ; k-- { // 找到k
                if k < 0 { // 完成
                    return n
                }
                if is[k] < is[k+1] {
                    n++
                    break
                }
            }
            valueK := is[k]
    
            var l int
            for l = size - 1; ; l-- { // 找到l
                if is[l] > valueK {
                    break
                }
            }
    
            is[k], is[l] = is[l], is[k] // 交换k和l
    
            sc := (size - 1 - k) >> 1
            maxLeft := k + sc
            for left, right := k+1, size-1; left <= maxLeft; left, right = left+1, right-1 { // 反转is[k+1:]
                is[left], is[right] = is[right], is[left]
            }
    
            handler(ts, is)
        }
    }
    
    // 生成初始索引,并处理重复元素,以避免重复组合
    // 例如:[7 11 11 13] -> [0 1 1 3]
    func genInitialIs[T constraints.Ordered](ts []T) []int {
        size := len(ts)
        is := make([]int, size)
    
        for i, ir := 0, 0; i < size; i++ { // 生成初始顺序
            if i == 0 || ts[i] != ts[i-1] {
                ir = i
            }
    
            is[i] = ir
        }
    
        return is
    }
    
  • 测试用例:

    package permutation_util
    
    import (
        "fmt"
        "github.com/stretchr/testify/assert"
        "golang.org/x/exp/constraints"
        "testing"
    )
    
    // 默认顺序
    func Test_FindAllPermutationInOrderApplyHandlerDefaultSort(t *testing.T) {
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2}, 6)
    
        // 输入未排序
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{1, 2, 0}, 6)
    
        /* 边界情况 */
        // 空数组
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{}, 1)
        // 只有一个元素
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0}, 1)
    }
    
    func Test_FindAllPermutationInOrderApplyHandlerDefaultSort_RepeatedEle(t *testing.T) {
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2, 2}, 12)           // A(4) / a(2)
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2, 2, 2}, 20)        // A(5) / a(3)
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2, 2, 2, 3, 3}, 420) // A(7) / (A(3) * A(2))
    
        runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{11, 13, 7, 11}, 12) // A(4) / a(2)
    }
    
    func runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print[T constraints.Ordered](t *testing.T, ts []T, expectedN int) {
        fmt.Printf("\n%v:\n", ts)
        assert.Equal(t, FindAllPermutationInOrderApplyHandlerDefaultSort(ts, func(ts []T, isComb []int) {
            tsComb := make([]T, len(ts))
            for i := 0; i < len(ts); i++ {
                tsComb[i] = ts[isComb[i]]
            }
            fmt.Printf("\t%v\n", tsComb)
        }), expectedN)
    }
    
    // 逆序
    func TestFindAllPermutationInOrderApplyHandler_reversed(t *testing.T) {
        runOnce_FindAllPermutationInOrderApplyHandlerCustomizedSort_reversedPrint(t, []int{0, 1, 2}, 6)
    }
    
    func runOnce_FindAllPermutationInOrderApplyHandlerCustomizedSort_reversedPrint[T constraints.Ordered](t *testing.T, ts []T, expectedN int) {
        fmt.Printf("\n%v:\n", ts)
        assert.Equal(t, FindAllPermutationInOrderApplyHandlerReversedSort(ts, func(ts []T, isComb []int) {
            tsComb := make([]T, len(ts))
            for i := 0; i < len(ts); i++ {
                tsComb[i] = ts[isComb[i]]
            }
            fmt.Printf("\t%v\n", tsComb)
        }), expectedN)
    }
    

复杂度

  • 时间复杂度O(n!)O(n! * n) // TODO: 不确定
    维基百科上说:每个排列需要3次比较和1.5次交换。
  • 空间复杂度O(1)
    因为它是原地修改的。

重复元素

它还处理了重复元素的情况,即不会生成重复的组合。

要计算总数,对于每个重复的组,应该除以A(组的大小),公式如下:

A(n) / (A(r1) * A(r2) * .. * A(rx))

英文:

According to Matt Timmermans's answer, I implemented the Pandita&#39;s algorithm, which is cache-free & in-order.


Code

(in go)

  • Find permutation, in order:

    package permutation_util
    import (
    &quot;fmt&quot;
    &quot;golang.org/x/exp/constraints&quot;
    &quot;golang.org/x/exp/slices&quot;
    )
    // will sort first, in default order,
    func FindAllPermutationInOrderApplyHandlerDefaultSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int)) int {
    return FindAllPermutationInOrderApplyHandlerCustomizedSort(ts, handler, nil)
    }
    // sort reversely,
    func FindAllPermutationInOrderApplyHandlerReversedSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int)) int {
    return FindAllPermutationInOrderApplyHandlerCustomizedSort(ts, handler, func(a, b T) bool {
    return a &gt; b // reversed,
    })
    }
    // sort by given compare function,
    func FindAllPermutationInOrderApplyHandlerCustomizedSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int), less func(a, b T) bool) int {
    if less != nil {
    slices.SortFunc(ts, less)
    } else {
    slices.Sort(ts)
    }
    return FindAllPermutationInOrderApplyHandlerNoSort(ts, handler)
    }
    // find all permutation of []T, in order, using Pandita&#39;s algorithm,
    // ts should be already sorted, in the order you wish,
    // apply given handler on each combination, and return count only,
    // repeated elements: it also handle the case with repeated elements, aka. it won&#39;t generate repeated combination,
    // refer:	https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order
    func FindAllPermutationInOrderApplyHandlerNoSort[T constraints.Ordered](ts []T, handler func(ts []T, isComb []int)) int {
    is := genInitialIs(ts)
    fmt.Printf(&quot;initial order: %v\n&quot;, is)
    n := 1 // include initial combination,
    handler(ts, is)
    size := len(ts)
    for {
    var k int
    for k = size - 2; ; k-- { // find k,
    if k &lt; 0 { // done
    return n
    }
    if is[k] &lt; is[k+1] {
    n++
    break
    }
    }
    valueK := is[k]
    var l int
    for l = size - 1; ; l-- { // find l,
    if is[l] &gt; valueK {
    break
    }
    }
    is[k], is[l] = is[l], is[k] // swap k &amp; l,
    sc := (size - 1 - k) &gt;&gt; 1
    maxLeft := k + sc
    for left, right := k+1, size-1; left &lt;= maxLeft; left, right = left+1, right-1 { // reverse is[k+1:],
    is[left], is[right] = is[right], is[left]
    }
    handler(ts, is)
    }
    }
    // generate initial index, and handle repeated elements, to avoid repeated combination,
    // e.g [7 11 11 13] -&gt; [0 1 1 3]
    func genInitialIs[T constraints.Ordered](ts []T) []int {
    size := len(ts)
    is := make([]int, size)
    for i, ir := 0, 0; i &lt; size; i++ { // generation initial order,
    if i == 0 || ts[i] != ts[i-1] {
    ir = i
    }
    is[i] = ir
    }
    return is
    }
    
  • Test case:

    package permutation_util
    import (
    &quot;fmt&quot;
    &quot;github.com/stretchr/testify/assert&quot;
    &quot;golang.org/x/exp/constraints&quot;
    &quot;testing&quot;
    )
    // default order
    func Test_FindAllPermutationInOrderApplyHandlerDefaultSort(t *testing.T) {
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2}, 6)
    // input not ordered,
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{1, 2, 0}, 6)
    /* corner */
    // empty
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{}, 1)
    // 1
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0}, 1)
    }
    func Test_FindAllPermutationInOrderApplyHandlerDefaultSort_RepeatedEle(t *testing.T) {
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2, 2}, 12)           // A(4) / a(2)
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2, 2, 2}, 20)        // A(5) / a(3)
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{0, 1, 2, 2, 2, 3, 3}, 420) // A(7) / (A(3) * A(2))
    runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print(t, []int{11, 13, 7, 11}, 12) // A(4) / a(2)
    }
    func runOnce_FindAllPermutationInOrderApplyHandlerDefaultSort_print[T constraints.Ordered](t *testing.T, ts []T, expectedN int) {
    fmt.Printf(&quot;\n%v:\n&quot;, ts)
    assert.Equal(t, FindAllPermutationInOrderApplyHandlerDefaultSort(ts, func(ts []T, isComb []int) {
    tsComb := make([]T, len(ts))
    for i := 0; i &lt; len(ts); i++ {
    tsComb[i] = ts[isComb[i]]
    }
    fmt.Printf(&quot;\t%v\n&quot;, tsComb)
    }), expectedN)
    }
    // reversed order,
    func TestFindAllPermutationInOrderApplyHandler_reversed(t *testing.T) {
    runOnce_FindAllPermutationInOrderApplyHandlerCustomizedSort_reversedPrint(t, []int{0, 1, 2}, 6)
    }
    func runOnce_FindAllPermutationInOrderApplyHandlerCustomizedSort_reversedPrint[T constraints.Ordered](t *testing.T, ts []T, expectedN int) {
    fmt.Printf(&quot;\n%v:\n&quot;, ts)
    assert.Equal(t, FindAllPermutationInOrderApplyHandlerReversedSort(ts, func(ts []T, isComb []int) {
    tsComb := make([]T, len(ts))
    for i := 0; i &lt; len(ts); i++ {
    tsComb[i] = ts[isComb[i]]
    }
    fmt.Printf(&quot;\t%v\n&quot;, tsComb)
    }), expectedN)
    }
    

Complexity

  • Time: O(n!) or O(n! * n) // TODO: not sure,
    wikipedia says: 3 comparisons and 1.5 swaps per permutation
  • Space: O(1)
    Since it change in place.

Repeated elements

It also handle the case with repeated elements, aka. it won't generate repeated combination.

To figure out total count, for each repeated group, should divide A(group&#39;s size), formula:
> A(n) / (A(r1) * A(r2) * .. * A(rx))

huangapple
  • 本文由 发表于 2023年3月30日 19:32:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75887690.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定