Swift - 洗牌过滤后的结构数组不会改变原始数组

Swift - Shuffling a filtered array of structs doesn't change originating array

我正在编写一个纸牌游戏,将许多纸牌收集在一个数组中。

卡片的数据结构相同,只是数据不同

注意:我的随机播放确实有效。

我想过滤这个数组,只洗我过滤过的牌。

但是,虽然我可以洗牌,但我注意到我的原始牌组根本没有改变。

我认为这个问题是因为我的卡模型是结构而不是 class。

更多背景信息。

虽然每张卡的数据不同,但结构完全一样;两种类型都有一个名称和一个数值。

这样设计的;

enum FSCardType: Int {
    case property
    case anotherType
    case yetAnotherType
}

// Card Model
struct FSCard {
    let type: FSCardType
    let name: String
    let value: Int

    var description: String {
        return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
    }
}

我在这样的静态函数中创建卡片:

class FSCardAPI: NSObject {

    public static func createCards() -> [FSCard] {
        var cards:[FSCard] = [FSCard]()
        let properties:[FSCard] = FSCardAPI.createProperties()

        cards.append(contentsOf: properties)

        return cards
    }

    public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
        let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
        return shuffled
    }

    fileprivate static func createProperties() -> [FSCard] {
        var properties:[FSCard] = [FSCard]()
        for idx in 1...30 {
            let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx, owner: nil)
            properties.append(property)
        }
        return properties
    }
}

好的,现在我想在这个卡片数组中洗牌我的属性张卡片。

所以在我的 XCTest 文件中,首先过滤所有类型的卡片:property

func testShuffleProperties() {
        var propertyCards  = gameModel.cards.filter { (fs:FSCard) -> Bool in
            return (fs.type == .property)
        }

        propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)

        print(propertyCards)
        print(" ---- ")
        print(gameModel.cards)       
}

这调用:

// Inside my FSCardAPI 
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
    let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
    return shuffled
}

问题:

当我 运行 我的 XCTest 时,我注意到虽然 propertyCards 被打乱,但我的 gameModel.cards 没有打乱;

Test Case 'testShuffleProperties' started.

// contents of `propertyCards`
[FSCard(type: FSCardType.property, name: "Property #4", value: 4, owner: nil), 
FSCard(type: FSCardType.property, name: "Property #16", value: 16, owner: nil), 
// .. etc

// contents of `gameModel.cards`
[FSCard(type: FSCardType.property, name: "Property #1", value: 1, owner: nil), 
FSCard(type: FSCardType.property, name: "Property #2", value: 2, owner: nil), 
// .. etc

总结

在我的测试中,数组被打乱;但原始数组保持不变。

我能想到的一种方法是我进行所有的洗牌,然后按卡片类型对数组进行排序,然后更新 gameModel.cards,但这似乎有点过头了。

我可以考虑解决这个问题的另一种明显方法是将我的 struct 更改为 class 或;也许我需要在结构之间添加一层?

所以我的查询是:

如何过滤结构数组,仅打乱这些结果并更改原始数组的状态?

非常感谢


编辑:

属性数组是唯一要随机播放的项目。

如果我向列表中添加另一个数组,它应该不会打乱整个内容。

浏览器;如果我这样做:

public static func createCards() -> [FSCard] {
        var cards:[FSCard] = [FSCard]()
        let properties:[FSCard] = FSCardAPI.createProperties()
        let cheqs:[FSCard] = FSCardAPI.createCheques()

        cards.append(contentsOf: properties)
        cards.append(contentsOf: cheqs)

        return cards
    }

我应该只在不影响支票的情况下洗 属性 张牌。

我想我可以让它更简单,只有 2 个数组,但同时我认为没有理由这样做,因为数据结构是相同的。

您没有分配给 gameModel.cards,也没有实际更改数组。所以我不希望 gameModel.cards 被洗牌。

您应该像这样将打乱后的数组分配回 gameModel.cards:

func testShuffleProperties() {
    var propertyCards  = gameModel.cards.filter { (fs:FSCard) -> Bool in
        return (fs.type == .property)
    }

    propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
    gameModel.cards = propertyCards

    print(propertyCards)
    print(" ---- ")
    print(gameModel.cards)       
}

或者,如果您想直接改变数组,您应该考虑通过引用传递卡片,或者在 Swift.. 中使用 inout 参数。 inout 参数通过引用将值传递给函数,或者传递值的内存地址,以便可以直接修改它。查看下面的洗牌功能(如何定义和使用)

(为了便于在操场上使用,我用 swift 扩展替换了随机播放功能)

extension MutableCollection where Indices.Iterator.Element == Index {
    /// Shuffles the contents of this collection.
    mutating func shuffle() {
        let c = count
        guard c > 1 else { return }

        for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
            let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
            guard d != 0 else { continue }
            let i = index(firstUnshuffled, offsetBy: d)
            swap(&self[firstUnshuffled], &self[i])
        }
    }
}

extension Sequence {
    /// Returns an array with the contents of this sequence, shuffled.
    func shuffled() -> [Iterator.Element] {
        var result = Array(self)
        result.shuffle()
        return result
    }
}

enum FSCardType: Int {
    case property
    case anotherType
    case yetAnotherType
}

// Card Model
struct FSCard {
    let type: FSCardType
    let name: String
    let value: Int

    var description: String {
        return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
    }
}

class FSCardAPI: NSObject {

    public static func createCards() -> [FSCard] {
        var cards:[FSCard] = [FSCard]()
        let properties:[FSCard] = FSCardAPI.createProperties()

        cards.append(contentsOf: properties)

        return cards
    }

    public static func shuffleCards(cards:inout [FSCard]) {
        cards = cards.shuffled()
    }

    fileprivate static func createProperties() -> [FSCard] {
        var properties:[FSCard] = [FSCard]()
        for idx in 1...30 {
            let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx)
            properties.append(property)
        }
        return properties
    }
}

var cards = FSCardAPI.createCards()

FSCardAPI.shuffleCards(cards: &cards)

输出:

阅读 In-Out 参数部分中的 inout 参数 here

从文档中提取:

函数参数默认为常量。尝试从函数体内更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。

您可以通过将 inout 关键字放在参数类型之前来编写输入输出参数。 in-out 参数有一个值,该值被传入函数,被函数修改,并传回函数外以替换原始值。有关 in-out 参数行为和相关编译器优化的详细讨论,请参阅 In-Out 参数。

您只能将变量作为参数传递给输入输出参数。您不能将常量或文字值作为参数传递,因为无法修改常量和文字。当您将变量作为实参传递给输入输出参数时,您可以在变量名称前直接放置一个符号 (&),以指示它可以被函数修改。

编辑: 试试这个更新的洗牌函数,传入整个数组,看看是否满足您的需要。

public static func shuffleCards(cards:inout [FSCard]) {
    let propertyCards = cards.filter({ [=12=].type == .property })
    let indexes = propertyCards.map { Int(cards.index(of: [=12=])!) }
    let shuffledCards = propertyCards.shuffled()

    for (idx, val) in indexes.enumerated() {
        cards[val] = shuffledCards[idx]
    }
}

问题是在你洗好你的 属性 牌后,你没有对它们做任何事情。您应该将原始牌列表中的 属性 牌替换为洗牌后的 属性 牌列表中的牌。

var propertyCardsShuffledOriginalArray = originalArray.map {

    var card = [=10=]

    if [=10=].type == .property {
        card = shuffledPropertyCards.first as! FSCard
        shuffledPropertyCards.removeFirst()
    }

    return card
}

propertyCardsShuffledOriginalArray就是你所需要的