【问题标题】:Finding all combinations in an array (Swift 5) with enumeration使用枚举查找数组中的所有组合(Swift 5)
【发布时间】:2019-07-27 12:53:16
【问题描述】:

我在 C# 中有一些代码正是我喜欢做的:枚举数组中给定长度的所有组合(重复)。像这样:

public static IEnumerable<IEnumerable<int>> CombinationsWithRepition(IEnumerable<int> input, int length)
{
    if (length <= 0)
        yield return new List<int>();
    else
    {
        foreach (int i in input)
            foreach (IEnumerable<int> c in CombinationsWithRepition(input, length - 1))
            {
                List<int> list = new List<int>();
                list.Add(i);
                list.AddRange(c);
                yield return list;
            }
    }
}

用法:

foreach (var c in CombinationsWithRepition(new int[] { 0, 1, 2 }, 2))
{
    foreach (var x in c)
        Console.Write(x + " : ");

    Console.WriteLine("");
}

输出:

0 : 0
0 : 1
0 : 2
1 : 0
1 : 1
1 : 2
2 : 0
2 : 1
2 : 2

现在我想将它移植到 swift 5,但我失败了。我在网上搜索,但我只能找到没有重复的解决方案或创建庞大数组的解决方案。枚举整个事物的能力很重要(不作为数组或包含所有结果的列表输出)。我不知道如何将“yield”移植到 swift 代码。

这是我找到的最接近的: How do I return a sequence in Swift?

提前谢谢你

【问题讨论】:

标签: arrays swift find combinations enumeration


【解决方案1】:

以下似乎达到了您想要的效果:

let source = Array((0...5))
let length = 3

func combinationsWithRepetition(input source: [Int], length: Int) -> [[Int]] {
  if length == 0 { return [[]] }
  let baseArray = combinationsWithRepetition(input: source, length: length - 1)
  var newArray = [[Int]]()
  for value in source {
    baseArray.forEach {
      newArray.append($0 + [value])
    }
  }
  return newArray
}

print(combinationsWithRepetition(input: [0, 1, 2], length: 2))
// [[0, 0], [1, 0], [2, 0], [0, 1], [1, 1], [2, 1], [0, 2], [1, 2], [2, 2]]

或者更实用:

func combinationsWithRepetition(input source: [Int], length: Int) -> [[Int]] {
  if length == 0 { return [[]] }
  let baseArray = combinationsWithRepetition(input: source, length: length - 1)
  return baseArray.flatMap { array in
    return source.map { array + [$0] }
  }
}

基本上,combinationsWithRepetition 返回一个由length 元素组合的所有可能数组组成的数组。 例如,combinationsWithRepetition(input: [0, 1, 2, 3], length: 1) 返回[[0], [1], [2], [3]]length 超过 0 是使用递归处理的,您最初似乎使用了递归(可能是因为我不知道 C# 语法)。 Swift 支持数组加法,例如,[1] + [2][1, 2],上面的代码对从source 到结果数组的元素(可能是长度length - 1)的所有值都这样做。

第二个代码使用flatMap 将嵌套数组“展平”为数组,例如[[[1, 2], [1, 3]], [[1, 4], [1, 5]]] 变成 [[1, 2], [1, 3], [1, 4], [1, 5]]。 否则逻辑与使用迭代的逻辑完全相同。

要获得您帖子中的输出,您可以:

combinationsWithRepetition(input: [0, 1, 2], length: 2).forEach {
  print($0.map(String.init).joined(separator: " : "))
}

导致

0 : 0
0 : 1
0 : 2
1 : 0
1 : 1
1 : 2
2 : 0
2 : 1
2 : 2

【讨论】:

    【解决方案2】:

    在具有yield 的语言中,它是对返回结果的调用函数的回调。当前函数的状态保持不变,并在yield 返回时继续。

    我们可以用 closure 回调在 Swift 中实现类似的功能。

    这几乎是您日常工作的直接翻译:

    func combinationsWithRepetition(_ input: [Int], length: Int, yield: ([Int]) -> ()) {
        if length <= 0 {
            yield([])
        }
        else {
            for i in input {
                combinationsWithRepetition(input, length: length - 1) { c in
                    var list = [Int]()
                    list.append(i)
                    list += c
                    yield(list)
                }
            }
        }
    }
    
    combinationsWithRepetition([0, 1, 2], length: 2) { c in
        print(c.map(String.init).joined(separator: " : "))
    }
    

    输出:

    0 : 0
    0 : 1
    0 : 2
    1 : 0
    1 : 1
    1 : 2
    2 : 0
    2 : 1
    2 : 2
    

    有时调用者想要控制 yielding 的函数。如果您希望能够停止该函数,您可以让 yield 回调返回一个 Bool 告诉屈服函数是否应该继续。

    考虑这个例子:

    // Generate squares until told to stop
    func squares(_ yield: (_ n: Int) -> Bool) {
        var i = 1
        var run = true
        while run {
            run = yield(i * i)
            i += 1
        }
    }
    
    // print squares until they exceed 200
    squares() { n -> Bool in
        print(n)
        return n < 200
    }
    
    1
    4
    9
    16
    25
    36
    49
    64
    81
    100
    121
    144
    169
    196
    225
    

    【讨论】:

      【解决方案3】:

      Swift 没有yield 语句。为了“惰性”枚举所有组合(而不将所有组合存储在数组中),我们必须实现一个 Sequence,其 Iterator 在其 next() 方法中按需计算组合。

      这是一个可能的实现。这个想法是维护一个“位置”数组作为状态变量。一开始是

       [ base.startIndex, ..., base.startIndex ]
      

      其中base 是构建组合的集合(例如Array)。在每次迭代中,最后一个位置都会递增。如果达到base.endIndex,则将其重置为base.startIndex,并增加下一个位置。 (这正是在数字系统中递增数字的方式,例如十进制数。)如果第一个位置已递增到 base.endIndex,则枚举完成。

      在每次迭代中,这个positions 数组被映射到基本集合中的相应元素数组,并从next() 方法返回。

      只需要这个数组和两个布尔变量作为中间存储。该实现使用了来自 Code Review 中 this answer 的想法,例如在 next() 方法中只有一个退出点。

      struct CombinationsWithRepetition<C: Collection> : Sequence {
      
          let base: C
          let length: Int
      
          init(of base: C, length: Int) {
              self.base = base
              self.length = length
          }
      
          struct Iterator : IteratorProtocol {
              let base: C
      
              var firstIteration = true
              var finished: Bool
              var positions: [C.Index]
      
              init(of base: C, length: Int) {
                  self.base = base
                  finished = base.isEmpty
                  positions = Array(repeating: base.startIndex, count: length)
              }
      
              mutating func next() -> [C.Element]? {
                  if firstIteration {
                      firstIteration = false
                  } else {
                      // Update indices for next combination.
                      finished = true
                      for i in positions.indices.reversed() {
                          base.formIndex(after: &positions[i])
                          if positions[i] != base.endIndex {
                              finished = false
                              break
                          } else {
                              positions[i] = base.startIndex
                          }
                      }
      
                  }
                  return finished ? nil : positions.map { base[$0] }
              }
          }
      
          func makeIterator() -> Iterator {
              return Iterator(of: base, length: length)
          }
      }
      

      示例 1:

      let comb = CombinationsWithRepetition(of: [0, 1, 3], length: 2)
      for c in comb { print(c) }
      

      输出:

      [0, 0] [0, 1] [0, 3] [1, 0] [1, 1] [1, 3] [3, 0] [3, 1] [3, 3]

      示例 2:

      let comb = CombinationsWithRepetition(of: "abcd", length: 3)
      for c in comb { print(c) }
      

      输出:

      [“一”,“一”,“一”] [“a”,“a”,“b”] [“a”,“a”,“c”] [“a”,“a”,“d”] [“a”,“b”,“a”] ... [“d”,“d”,“c”] [“d”,“d”,“d”]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-12-10
        • 1970-01-01
        • 1970-01-01
        • 2020-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多