【问题标题】:Need an algorithm for to shuffle elements of 5 arrays, each with the same 5 elements, such that no two arrays have the same element at the same index需要一种算法来打乱 5 个数组的元素,每个数组都有相同的 5 个元素,这样没有两个数组在相同的索引处具有相同的元素
【发布时间】:2016-03-31 14:38:17
【问题描述】:

我有以下五个数组

var E1 = ["A", "B", "C", "D", "E"]
var E2 = ["A", "B", "C", "D", "E"]
var E3 = ["A", "B", "C", "D", "E"]
var E4 = ["A", "B", "C", "D", "E"]
var E5 = ["A", "B", "C", "D", "E"]

每个数组都有相同的五个元素,即“A”、“B”、“C”、“D”和“E”。我想编写一个算法来对所有数组中的元素进行排序,这样没有两个数组在同一索引处具有相同的元素(比如说“A”)。

一种对我有用的示例输出如下:

var E1 = ["A", "B", "C", "D", "E"]
var E2 = ["B", "C", "D", "E", "A"]
var E3 = ["C", "D", "E", "A", "B"]
var E4 = ["D", "E", "A", "B", "C"]
var E5 = ["E", "A", "B", "C", "D"]

我已尝试解决此问题,但无法完成。我刚刚编写了一个洗牌函数,用于对两个数组(E1 和 E2)的元素进行排序。

var E1 = ["A", "B", "C", "D", "E"]
var E2 = ["A", "B", "C", "D", "E"]
var E3 = ["A", "B", "C", "D", "E"]
var E4 = ["A", "B", "C", "D", "E"]
var E5 = ["A", "B", "C", "D", "E"]

func shuffledArrays(var array1: [String],var array2: [String]) {


if array1[0] == array2[0] || array1[1] == array2[1] || array1[2] == array2[2] || array1[3] == array2[3] || array1[4] == array2[4] {

    shuffled1 = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(array1)
    shuffled2 = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(array2)

    var array3 = shuffled1 as! [String]
    var array4 = shuffled2 as! [String]

} else {
    var array3 = array1
    var array4 = array2
}

array1 = array3
    array2 = array4
}

// Now calling the function on arrays E1 and E2
    shuffledArrays(E1, array2: E2)
    print(E1)
    print(E2)

使用此代码,我在 Xcode Playground 上收到以下错误。虽然有时会删除错误并且第 102 行和第 103 行的输出是正确的,但我仍然无法提取该输出并将其分别永久保存到 E1 和 E2 中。请帮助我安排五个数组元素的整个算法。

谢谢

【问题讨论】:

  • 什么编程语言,儿子?
  • 需要随机吗?还是所有唯一元素都需要在不同数组中具有不同的索引?两者都做只会适得其反。如果两者都需要,请添加一些上下文。
  • @RMenke 看来他需要数组的五种随机不同排列...
  • @user3441734 他明确指出,任何两个数组都不应该在同一索引处具有相同的元素。这根本不是随机的。
  • @RMenke 我同意,请参阅 Leo 写的答案。

标签: swift algorithm sorting multidimensional-array


【解决方案1】:

我喜欢 Leo 的回答。我的回答几乎是“纯粹的”swift(导入 Foundation 是由于 arc4random_uniform 函数)

import Foundation // arc4random_uniform

let arr = Array(1...5)
var result: [[Int]] = []
repeat {
    let r = arr.sort { (a, b) -> Bool in
        arc4random_uniform(2) == 0
    }
    if !result.contains({ (s) -> Bool in
        s == r
    }) {
        result.append(r)
    }
} while result.count < 6

print(result)
// [[2, 3, 1, 4, 5], [4, 2, 1, 3, 5], [2, 1, 3, 4, 5], [2, 3, 1, 5, 4], [2, 4, 5, 1, 3], [2, 1, 4, 3, 5]]

【讨论】:

  • 请注意,对于 Leos 的回答:平均而言,他的方法会有很大的偏差。尝试分析 while 循环中的预期运行次数:随机化 5 个大小为 5 的数组,使其彼此明显不同。 满足这一点的五元组排列的数量是巨大的。但是,您上面的方法看起来不那么浪费。
  • @dfri,检查一下,你会发现它和 Leo 的回答一样有效。如果你做一些性能分析,“纯”的 swift 不会像 Leo 的解决方案那样最差。让我们试试吧!
  • @LeoDabus :-) 所以,也许其他人可以使用我们的解决方案。是的,我现在看到了!
  • @LeoDabus,订单不能与我在示例输出中提供的订单有任何不同。 user3441734 的代码问题是一样的。我用这段代码得到的输出在两个数组的 index0 处仍然有元素“2”。我想在不同数组的不同索引处有一个元素。
  • @user3441734:请注意,我写的是你上面的方法可能很好(“你上面的方法,但是,看起来不那么浪费”,可能是一个不好的词选择)。我只关心 Leos 的回答,但我现在无法测试它,因为它已被删除。但也许,当我浏览它时,我误解了 while 循环中发生的事情。我将while循环解释为“蛮力尝试生成随机数组直到它们唯一”,但也许我误解了。
【解决方案2】:

首先是一些快捷方式:

  • 构造一个类型,可以轻松地在行/列中显示嵌套数组。
  • 有办法检测列中的重复元素(不同行中具有相同索引的相同元素)

这可以找到here

我没有将它包含在答案中,因为它不是算法的一部分。


仅当Array 是多维的并且它的嵌套ElementEquatable 时,才扩展Array 以实现函数。

extension Array where Element : _ArrayType, Element.Element : Equatable {

Self 将是 Array&lt;Element&gt; 类型,并且不会识别它可以转换为 Array&lt;Array&lt;Element.Element&gt;&gt;。请参阅转换的要点。

    func entropy() -> [[Element.Element]]? { // returns nil when it is impossible

从数组中创建一个矩阵。

        var matrix = self.matrix() // see gist for Matrix type

这个嵌套函数执行实际的Array 突变。它在row 中交换两个elements

        func swap(row r:Int,column c:Int) {

            let nextColumn : Int = {
                if (c + 1) < matrix[r].count {
                    return c + 1
                } else {
                    return 0
                }
            }()

            let element = matrix[r][c]
            let neighbour = matrix[r][nextColumn]
            matrix[r][c] = neighbour
            matrix[r][nextColumn] = element

        }

这是循环逻辑:

只要矩阵的列包含重复项 -> 遍历每一列 -> 找到重复项 -> 将其与邻居交换。

效率不高。但是您可以尝试不同的函数来改变行,看看什么效果最好。

        while matrix.columnsContainDuplicates() {
            for c in 0..<matrix.columns.count {
                let column = matrix.columns[c]
                if let dupeIndex = column.indexOfDuplicate() {
                    swap(row: dupeIndex, column: c)
                }
            }
        }

        return matrix.rows
    }
}

完整的代码和额外的嵌套函数来检查它是否可以解决。可能还有其他原因导致它可能无法解决。

extension Array where Element : _ArrayType, Element.Element : Equatable {

    func entropy() -> [[Element.Element]]? {

        var matrix = self.matrix() // this comes from an extension to Array: if nested, can be converted to a matrix

        // is there a possible configuration where no column has duplicates?
        func solvable() -> Bool {
            // this just checks if element x does not appear more than there are possible indexes.
            var uniqueElements : [Element.Element] = []
            var occurences : [Int] = []

            for row in matrix.rows {
                var rowCopy = row
                while let pop = rowCopy.popLast() {
                    if let index = uniqueElements.indexOf(pop) {
                        occurences[index] += 1
                        occurences[index]
                    } else {
                        uniqueElements.append(pop)
                        occurences.append(1)
                    }
                }
            }

            var highest = 0
            for times in occurences {
                if highest < times { highest = times }
            }

            if highest > matrix.columns.count {
                highest
                return false
            }

            return true
        }

        func swap(row r:Int,column c:Int) {

            let nextColumn : Int = {
                if (c + 1) < matrix[r].count {
                    return c + 1
                } else {
                    return 0
                }
            }()

            let element = matrix[r][c]
            let neighbour = matrix[r][nextColumn]
            matrix[r][c] = neighbour
            matrix[r][nextColumn] = element

        }

        guard solvable() else {
            return nil
        }

        while matrix.columnsContainDuplicates() {
            for c in 0..<matrix.columns.count {
                let column = matrix.columns[c]
                if let dupeIndex = column.indexOfDuplicate() {
                    swap(row: dupeIndex, column: c)
                }
            }
        }

        return matrix.rows
    }
}

测试:

let test = [[4,3,2,1],[5,2,3,4],[1,6,3,4],[1,2,3,4]] // 8 loops
test.entropy() // [[3, 4, 1, 2], [4, 3, 2, 5], [6, 1, 4, 3], [1, 2, 3, 4]]

let test2 = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]] // 8 loops
test2.entropy() // [[3, 4, 1, 2], [4, 3, 2, 1], [2, 1, 4, 3], [1, 2, 3, 4]]

【讨论】:

    【解决方案3】:

    我是从 iPhone 上回答的,所以我没有编译器来测试一些代码。我可以给你一个基本的想法如何解决这个问题。

    创建一个字典,其中键是一个元素(如“A”),值是一个已添加索引的数组。

    var dict = [String : [Int]]()
    

    然后遍历所有 5 个数组并附加每个元素。在添加它之前,请检查字典中是否已在同一索引的其他位置添加了此元素。如果不是你可以添加它,如果是,附加另一个元素并将添加的索引添加到字典。

    希望你能明白。

    【讨论】:

      【解决方案4】:

      据我所知,您可以这样做:

      1. 采取有效的解决方案(就像您给出的示例一样)
      2. 洗牌(这只能导致有效的解决方案)
      3. 洗牌(这只能导致有效的解决方案)

      这样你可以获得所有可能的解决方案。

      例如,从一个有效的解决方案开始。

      ["A", "B", "C", "D", "E"]
      ["B", "C", "D", "E", "A"]
      ["C", "D", "E", "A", "B"]
      ["D", "E", "A", "B", "C"]
      ["E", "A", "B", "C", "D"]
      

      随机排列。

      ["C", "D", "E", "A", "B"]
      ["B", "C", "D", "E", "A"]
      ["A", "B", "C", "D", "E"]
      ["D", "E", "A", "B", "C"]
      ["E", "A", "B", "C", "D"]
      

      然后洗牌。

      ["C", "E", "A", "D", "B"]
      ["B", "D", "E", "C", "A"]
      ["A", "C", "D", "B", "E"]
      ["D", "A", "B", "E", "C"]
      ["E", "B", "C", "A", "D"]
      

      这保证没有两个数组在同一索引中具有相同的元素并且它们是随机的。

      这是 Ruby 中的一个示例。该算法适用于任何语言。

      # Initialize the first row of the matrix
      matrix = []
      matrix[0] = ('A'..'E').to_a
      
      size = matrix[0].size
      
      # Initialize the rotated array
      for i in 1..size-1
        matrix[i] = matrix[i-1].rotate
      end
      
      puts "Original matrix"
      for x in matrix
        puts x.inspect
      end
      
      # Shuffle the indexes of the rows and columns
      rows = (0..size-1).to_a.shuffle
      cols = (0..size-1).to_a.shuffle
      
      # Shuffle the rows
      for i in 0..size-1
        row1 = i
        row2 = rows[i]
        tmp = matrix[row1]
        matrix[row1] = matrix[row2]
        matrix[row2] = tmp
      end
      
      # Shuffle the columns.
      for i in 0..size-1
        col1 = i
        col2 = cols[i]
      
        for j in 0..size-1
          tmp = matrix[j][col1]
          matrix[j][col1] = matrix[j][col2]
          matrix[j][col2] = tmp
        end
      end
      
      puts "Shuffled matrix"
      for x in matrix
        puts x.inspect
      end
      

      @Schwern: 用于添加示例和代码的坦克。

      【讨论】:

      • 你能证明这个方法可以产生所有的解决方案吗?
      • 这无效。如果你随机排列一行,没有什么可以阻止它匹配另一行。
      • @Schwern:最初的解决方案是有效的,无论我们交换什么行,它都是有效的解决方案。列也一样。如果您吐出两列,则解决方案仍然有效。
      • @MrSmith 我想我现在明白你的意思了,你是对的。你的意思是打乱行和列的列表,而不是它们的内容。
      • @MrSmith42 我希望我的编辑不会太多,但我觉得你应该因为正确的算法而受到赞扬。
      【解决方案5】:

      由于您知道数组 E1, ..., E5 保存相同的条目(以相同的顺序),因此您无需显式保存数组 E2E5(因为您知道这些是值- 等于E1)。

      因此,您可以简单地定义一个移位函数并通过重复移位先前的数组来创建E2E5

      import GameplayKit
      
      func shiftByOne (arr: [String]) -> [String] {
          var shiftedArr = arr
          shiftedArr.insert(shiftedArr.popLast()!, atIndex: 0)
          return shiftedArr
      }
      
      var E1 = ["A", "B", "C", "D", "E"]
      E1 = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(E1) as! [String]
      var E2 = shiftByOne(E1)
      var E3 = shiftByOne(E2)
      var E4 = shiftByOne(E3)
      var E5 = shiftByOne(E4)
      
      /** Result without initial shuffle:
      E1 = ["A", "B", "C", "D", "E"]
      E2 = ["B", "C", "D", "E", "A"]
      E3 = ["C", "D", "E", "A", "B"]
      E4 = ["D", "E", "A", "B", "C"]
      E5 = ["E", "A", "B", "C", "D"] */
      

      此方法以E1 开头(可能仅对这个数组进行混洗),并保证E2E5 的构造构造都不同,w.r.t。顺序,彼此之间。

      如下面的 R Menke 所述,如果您对数组 E1 进行洗牌,则将保持相同的行为(但是使用洗牌的初始数组)。在这里,我使用了与您的示例相同的洗牌器,但对于更多 Swifty 方法,请参阅例如:

      【讨论】:

      • 如果您在开始轮班之前随机化数组,它将完成 OP 要求的所有操作。如果将数组 E1 - E5 的顺序随机化,那就更好了。
      • 伙计们,即使您在移动之前对数组进行随机化,也不足以拥有所有可能排列的随机子集。看狮子座的回答,最接近给男生好的结果
      • @user3441734 但 OP 不想要完全随机的数组。他希望每个元素相对于其他数组具有不同的索引。
      • @dfri,感谢 dfri 的回答。它确实符合我的示例输出的目的。但有时在未来,我想要一些我不想移动的数组的一些指定元素,甚至在数组元素是不同字符串的情况下。您能否建议如何进行相应的修改?
      • @dfri,为了清楚地解释这一点,我需要上传一张图片。我是新程序员,也是 stackoverflow 的新手(用于发布问题),我不知道如何专门为您私下上传图片。你能指导我怎么做吗?
      猜你喜欢
      • 2021-06-11
      • 1970-01-01
      • 1970-01-01
      • 2018-05-08
      • 1970-01-01
      • 2020-08-14
      • 2017-11-05
      • 2018-01-17
      • 1970-01-01
      相关资源
      最近更新 更多