有没有办法对数组进行混洗,以便没有两个连续的值相同?

时间:2016-08-26 16:03:11

标签: swift random distribution

我有一系列颜色可以填充饼图作为游戏微调器。我不希望相同的颜色彼此相邻,在圆圈中形成一个巨大的块。

我的数组看起来像这样:

var colors = ["blue", "red", "green", "red", "blue", "blue", "blue", "green"]

问题当然是三个蓝调在一起。 Swift中是否有任何内容可以让我在总分布中平等(或尽可能接近)扩散值并避免它们相邻?

我可以使用以下代码测试匹配,但重新排列它们会更加困难。

var lastColor = "white"

for color in colors {
    if color == lastColor {
        print("match")
    }
    lastColor = color    
}

更新:

要制作我的colors数组,我先从每种颜色的空格数开始。它看起来像这样:

let numberOfReds = 2
let numberOfGreens = 2
let numberOfBlues = 4

let spaces = numberOfReds + numberOfGreens + numberOfBlues

for _ in 0..< spaces {
    if numberOfReds > 0 {
        numberOfReds -= 1
        colors.append("red")
    }
    if numberOfGreens > 0 {
        numberOfGreens -= 1
        colors.append("green")
    }
    if numberOfBlues > 0 {
        numberOfBlues -= 1
        colors.append("blue")
    }
}

最终会吐出来:

colors = ["red", "green", "blue", "red", "green", "blue", "blue", "blue" ]

4 个答案:

答案 0 :(得分:5)

  

免责声明:为了生成&#34;随机&#34; 解决方案,我将使用回溯。从空间的角度来看,这种方法 NOT 快,并且便宜。

     

事实上,时间和空间的复杂性都是O(n!)......这是巨大的!

     

然而,它为您提供了一个有效的解决方案尽可能随机

回溯

所以如果没有2个连续的等于元素,你想要一个值列表的随机组合和解决方案有效的条件。

为了获得真正的随机解决方案,我建议采用以下方法。

我生成了所有可能的有效组合。为此,我使用了回溯方法

enter image description here

func combinations<Element:Equatable>(unusedElms: [Element], sequence:[Element] = []) -> [[Element]] {
    // continue if the current sequence doesn't contain adjacent equal elms
    guard !Array(zip(sequence.dropFirst(), sequence)).contains(==) else { return [] }

    // continue if there are more elms to add
    guard !unusedElms.isEmpty else { return [sequence] }

    // try every possible way of completing this sequence
    var results = [[Element]]()
    for i in 0..<unusedElms.count {
        var unusedElms = unusedElms
        let newElm = unusedElms.removeAtIndex(i)
        let newSequence = sequence + [newElm]
        results += combinations(unusedElms, sequence: newSequence)
    }
    return results
}

现在给出一个颜色列表

let colors = ["blue", "red", "green", "red", "blue", "blue", "blue", "green"]

我可以生成每个有效的可能组合

let combs = combinations(colors)

[["blue", "red", "green", "blue", "red", "blue", "green", "blue"], ["blue", "red", "green", "blue", "red", "blue", "green", "blue"], ["blue", "red", "green", "blue", "green", "blue", "red", "blue"], ["blue", "red", "green", "blue", "green", "blue", "red", "blue"], ["blue", "red", "green", "blue", "red", "blue", "green", "blue"], ["blue", "red", "green", "blue", "red", "blue", "green", "blue"], ["blue", "red", "green", "blue", "green", "blue", "red", "blue"], ["blue", "red", "green", "blue", "green", "blue", "red", "blue"], ["blue", "red", "green", "blue", "red", "blue", "green", "blue"], ["blue", "red", "green", "blue", "red", "blue", "green", "blue"], ["blue", "red", "green", "blue", "green", "blue", "red", "blue"], ["blue", "red", "green", "blue", "green", "blue", "red", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "blue", "green", "blue", "green"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "red", "blue", "green", "blue"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "green", "blue", "red", "blue", "green"], ["blue", "red", "blue", "green", "blue", "red", "green", "blue"], ["blue", "red", "blue", "green", "blue", "green", "red", "blue"], ["blue", "red", "blue", "green", "blue", "green", "blue", "red"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], ["blue", "red", "blue", "red", "green", "blue", "green", "blue"], …, ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "green", "blue", "red", "blue", "red", "blue"], ["green", "blue", "red", "blue", "red", "blue", "green", "blue"], ["green", "blue", "red", "blue", "red", "blue", "green", "blue"], ["green", "blue", "red", "blue", "green", "blue", "red", "blue"], ["green", "blue", "red", "blue", "green", "blue", "red", "blue"], ["green", "blue", "red", "blue", "red", "blue", "green", "blue"], ["green", "blue", "red", "blue", "red", "blue", "green", "blue"], ["green", "blue", "red", "blue", "green", "blue", "red", "blue"], ["green", "blue", "red", "blue", "green", "blue", "red", "blue"], ["green", "blue", "red", "blue", "red", "blue", "green", "blue"], ["green", "blue", "red", "blue", "red", "blue", "green", "blue"], ["green", "blue", "red", "blue", "green", "blue", "red", "blue"], ["green", "blue", "red", "blue", "green", "blue", "red", "blue"]]

最后,我只需要选择其中一种组合

let comb = combs[Int(arc4random_uniform(UInt32(combs.count)))]
// ["red", "blue", "green", "blue", "green", "blue", "red", "blue"]

改进

如果您不需要真正的随机解决方案,但只是一个没有2个连续相等元素的排列,我们可以更改之前的函数以返回第一个有效的解决方案。

func combination<Element:Equatable>(unusedElms: [Element], sequence:[Element] = []) -> [Element]? {
    guard !Array(zip(sequence.dropFirst(), sequence)).contains(==) else { return nil }
    guard !unusedElms.isEmpty else { return sequence }

    for i in 0..<unusedElms.count {
        var unusedElms = unusedElms
        let newElm = unusedElms.removeAtIndex(i)
        let newSequence = sequence + [newElm]
        if let solution = combination(unusedElms, sequence: newSequence) {
            return solution
        }
    }
    return nil
}

现在你可以简单地写

combination(["blue", "red", "green", "red", "blue", "blue", "blue", "green"])

获得有效的解决方案(如果确实存在)

["blue", "red", "green", "blue", "red", "blue", "green", "blue"]
  

这种方法可以更快(当解决方案确实存在时),但最坏的情况仍然是空间和时间复杂度的O(n!)。

答案 1 :(得分:3)

尽管有外表,但这并不重要。正如评论员@ antonio081014指出的那样,它实际上是一个算法问题,并且(正如@MartinR指出的那样)被解决here。这是一个非常简单的启发式(与@appzYourLife的解决方案不同)算法,但在大多数情况下都可以使用,而且速度更快(O(n ^ 2) )而不是O(n!))。对于随机性,只需先将输入数组洗牌:

func unSort(_ a: [String]) -> [String] {
    // construct a measure of "blockiness"
    func blockiness(_ a: [String]) -> Int {
        var bl = 0
        for i in 0 ..< a.count {
            // Wrap around, as OP wants this on a circle
            if a[i] == a[(i + 1) % a.count] { bl += 1 } 
        }
        return bl
    }
    var aCopy = a // Make it a mutable var
    var giveUpAfter = aCopy.count // Frankly, arbitrary... 
    while (blockiness(aCopy) > 0) && (giveUpAfter > 0) {
        // i.e. we give up if either blockiness has been removed ( == 0)
        // OR if we have made too many changes without solving

        // Look for adjacent pairs    
        for i in 0 ..< aCopy.count {
            // Wrap around, as OP wants this on a circle
            let prev = (i - 1 >= 0) ? i - 1 : i - 1 + aCopy.count
            if aCopy[i] == aCopy[prev] { // two adjacent elements match
                let next = (i + 1) % aCopy.count // again, circular 
                // move the known match away, swapping it with the "unknown" next element
                (aCopy[i], aCopy[next]) = (aCopy[next], aCopy[i])
            }
        }
        giveUpAfter -= 1
    }
    return aCopy
}

var colors = ["blue", "red", "green", "red", "blue", "blue", "blue", "green"]
unSort(colors) // ["blue", "green", "blue", "red", "blue", "green", "blue", "red"]

// Add an extra blue to make it impossible...
colors = ["blue", "blue", "green", "red", "blue", "blue", "blue", "green"]
unSort(colors) //["blue", "green", "blue", "red", "blue", "blue", "green", "blue"]

答案 2 :(得分:1)

O(N)时空解决方案

Pie Chart Histogram

我从图像开始,因为它总是更有趣:)

简介

首先,我想要注意你不能有一个均匀分布的序列,因为在你的情况下,大量的颜色是不一样的。

要回答如何生成随机序列让我们从最简单的案例开始。

拥有所有颜色,您可以从1 - N生成随机值,取出颜色,从1 - (N-1)生成,等等。

现在,有一些颜色比其他颜色,你做的事情与前一种方法相同,但现在每种颜色的概率都不同 - 如果你有更多的黑色,它的概率是更高。

现在,在你的情况下,你有确切的情况,但有一个额外的要求 - 当前随机颜色不等于前一个。因此,只需在生成每种颜色时应用此要求 - 就随机性而言,这将是最好的。

实施例

例如,您总共有4种颜色:

  • 黑色:2;
  • red:1;
  • 绿色:1。

首先想到的步骤如下:

  1. 将它们放在一行B B R G;
  2. 选择一个随机的,例如:B,取走所有相同的颜色,以保证下一个颜色不同。现在你有R G;
  3. 选择下一个随机的一个,例如R,取出所有相同的颜色,带上以前颜色的所有相同颜色,因为它现在可供选择。在此步骤中,您最终得到B G
  4. 等等...
  5. 但这是错误的。注意,在步骤3中,黑色和绿色的颜色出现的概率相似(B G - 它是黑色或绿色),而在开始时黑色的概率更大。

    要避免这种情况,请使用颜色框。分档具有宽度(概率)和保留在其中的颜色数量。宽度永远不会改变,并在启动时设置。

    所以正确的步骤是

    1. 创建3个bean并将它们放在一行中:
      • 黑色:0.5,数量:2;
      • 红色:0.25,数量:1;
      • 绿色:0.25,数量:1。
    2. 0.0 <-> 1.0 范围内生成一个随机数。例如,它是0.4,这意味着黑色(例如,0.9表示绿色)。之后,如果您在此步骤中无法选择黑色,您的选择是:
      • 红色:0.25,数量:1;
      • 绿色:0.25,数量:1。
    3. 由于您已采用宽度为0.5的黑色纸槽,因此请生成0.0 <-> (1.0 - 0.5) = 0.0 <-> 0.5 范围内的随机数。设为0.4,即红色。
    4. 取消红色(-0.25),但将黑色带回(+0.5)。在这一步你有:

      • 黑色:0.5,数量:1;
      • 绿色:0.25,数量:1。

      下一个随机值的范围是0.0 <-> (0.5 - 0.25 + 0.5) = 0.0 <-> 0.75 。请注意,与之前的方法相比,颜色保留了其起始概率(黑色具有更大的概率)。

    5. 该算法的时间复杂度为O(N) ,因为您执行相同数量的工作O(1)(选择随机区域,将其排除,包含前一个区域)你拥有O(N)种颜色的次数是很多倍。

      我应该注意的最后一件事 - 因为它是一种概率方法,最大的bin的几种颜色可能留在算法的末尾。在这种情况下,只需迭代最终的颜色列表并将它们放在合适的位置(颜色与它们不同)。

      也有可能没有这样的颜色排列,因此没有相同的颜色相邻(例如:黑色 - 2,红色 - 1)。对于这种情况,我在下面的代码中抛出异常。

      算法结果的例子出现在图片的开头。

      代码

      Java(Groovy)。

      注意,为了便于阅读,从列表中删除元素是标准的(bins.remove(bin)),这是Groovy中的O(N)操作。因此,该算法总共不起作用O(N)。删除应该被重写为使用要删除的元素更改列表的最后一个元素并递减列表的size属性 - O(1)

      Bin {
          Color color;
          int quantity;
          double probability;
      }
      
      List<Color> finalColors = []
      List<Bin> bins // Should be initialized before start of the algorithm.
      double maxRandomValue = 1
      
      private void startAlgorithm() {
          def binToExclude = null
      
          while (bins.size() > 0) {
              def randomBin = getRandomBin(binToExclude)
              finalColors.add(randomBin.color)
      
              // If quantity = 0, the bin's already been excluded.
              binToExclude = randomBin.quantity != 0 ? randomBin : null
      
              // Break at this special case, it will be handled below.
              if (bins.size() == 1) {
                  break
              }
          }
      
          def lastBin = bins.get(0)
          if (lastBin != null) {
              // At this point lastBin.quantity >= 1 is guaranteed.
              handleLastBin(lastBin)
          }
      }
      
      private Bin getRandomBin(Bin binToExclude) {
          excludeBin(binToExclude)
      
          def randomBin = getRandomBin()
      
          randomBin.quantity--
          if (randomBin.quantity == 0) {
              excludeBin(randomBin)
          }
      
          includeBin(binToExclude)
      
          return randomBin
      }
      
      private Bin getRandomBin() {
          double randomValue = randomValue()
      
          int binIndex = 0;
          double sum = bins.get(binIndex).probability
          while (sum < randomValue && binIndex < bins.size() - 1) {
              sum += bins.get(binIndex).probability;
              binIndex++;
          }
      
          return bins.get(binIndex)
      }
      
      private void excludeBin(Bin bin) {
          if (bin == null) return
      
          bins.remove(bin)
          maxRandomValue -= bin.probability
      }
      
      private void includeBin(Bin bin) {
          if (bin == null) return
      
          bins.add(bin)
          def addedBinProbability = bin.probability
      
          maxRandomValue += addedBinProbability
      }
      
      private double randomValue() {
          return Math.random() * maxRandomValue;
      }
      
      private void handleLastBin(Bin lastBin) {
          // The first and the last color're adjacent (since colors form a circle),
          // If they're the same (RED,...,RED), need to break it.
          if (finalColors.get(0) == finalColors.get(finalColors.size() - 1)) {
              // Can we break it? I.e. is the last bin's color different from them?
              if (lastBin.color != finalColors.get(0)) {
                  finalColors.add(lastBin.color)
                  lastBin.quantity--
              } else {
                  throw new RuntimeException("No possible combination of non adjacent colors.")
              }
          }
      
          // Add the first color to the other side of the list
          // so that "circle case" is handled as a linear one.
          finalColors.add(finalColors.get(0))
      
          int q = 0
          int j = 1
          while (q < lastBin.quantity && j < finalColors.size()) {
              // Doesn't it coincide with the colors on the left and right?
              if (finalColors.get(j - 1) != lastBin.color && finalColors.get(j) != lastBin.color) {
                  finalColors.add(j, lastBin.color)
                  q++
                  j += 2
              }  else {
                  j++
              }
          }
          // Remove the fake color.
          finalColors.remove(finalColors.size() - 1)
      
          // If still has colors to insert.
          if (q < lastBin.quantity) {
              throw new RuntimeException("No possible combination of non adjacent colors.")
          }
      }
      

答案 3 :(得分:1)

GameplayKit中的GKShuffledDistribution类有两个功能可以很容易地满足这个要求:

  1. 它从初始化范围中绘制“随机”数字,使得它必须使用该范围内的所有数字,然后重复其中任何一个。

    单独,此行为会在随机序列中创建“块”(缺少更好的词)。例如,如果您有4个可能的值,前四个nextInt()调用将耗尽所有四个GKShuffledDistribution调用。但是在第五次调用时你会遇到一个新的“块”,你可以再次随机获得4个值中的任何一个,包括最后一个“块”的最终值。

  2. 因此,nextInt()还确保“块”边界没有重复。

  3. 通过在操场上尝试以下内容并显示import GameplayKit let colors = ["red", "green", "blue" // the effect is easier to see with more than three items, so uncomment for more: // , "mauve", "puce", "burnt sienna", "mahogany", // "periwinkle", "fuschia", "wisteria", "chartreuse" ] let randomizer = GKShuffledDistribution(lowestValue: 0, highestValue: colors.count - 1) for _ in 1...100 { randomizer.nextInt() } 行的值图表,您可以非常轻松地看到这一点:

    11, 10, 11

    100 random shuffles from a set of three

    或者有更多颜色: 100 random shuffles from a set of 11

    您会注意到某些值会在中间跳过重复(请注意第二个图中早期extension GKShuffledDistribution { func shuffledInts(count: Int) -> [Int] { // map on a range to get an array of `count` random draws from the shuffle return (0..<count).map { _ in self.nextInt() } } } let colors = [#colorLiteral(red: 1, green: 0, blue: 0, alpha: 1), #colorLiteral(red: 0, green: 1, blue: 0, alpha: 1), #colorLiteral(red: 0, green: 0, blue: 1, alpha: 1)] let random = GKShuffledDistribution(forDieWithSideCount: colors.count) let dieRolls = random.shuffledInts(count: 10) let shuffledColors: [SKColor] = dieRolls.map { num in // forDieWithSideCount gives us values between 1 and count // we want values betwen 0 and (count-1) return colors[num - 1] } 的序列),但永远不会连续重复一个值。

    要使用它来混合颜色数组,只需使用混洗索引:

    dieWithSideCount

    (在此示例中还显示了其他一些内容:使用颜色文字而不是颜色名称,尽管您也可以这样做,并使用GKShuffledDistribution初始值设定项3。在Xcode中,颜色文字看起来比在网络上看起来要好得多。)