【问题标题】:Swap array values at specific indices?在特定索引处交换数组值?
【发布时间】:2015-07-23 19:07:40
【问题描述】:

我正在为基于字典数组的图像创建异步 NSURLConnections,每个字典都有自己的图像 URL:

var posts = [
    ["url": "url0", "calledIndex": 0],
    ["url": "url1", "calledIndex": 1],
    ["url": "url2", "calledIndex": 2],
    ["url": "url3", "calledIndex": 3]
]

鉴于连接的异步特性(这是我想要的,首先加载最快的图像),图像可能以不同的顺序加载,例如:

url0
url2
url3
url1

但是,如果图像被乱序加载,则需要根据图像加载的时间重新组织原始的posts 数组。因此,鉴于上述示例,posts 现在应该如下所示:

var posts = [
    ["url": "url0", "calledIndex": 0],
    ["url": "url2", "calledIndex": 2],
    ["url": "url3", "calledIndex": 3],
    ["url": "url1", "calledIndex": 1]
]

在 Swift 中有什么方法可以将特定索引处的数组值与不同索引处的同一数组的值交换?我首先尝试使用swap 函数:

// Index the images load
var loadedIndex = 0

func connectionDidFinishLoading(connection: NSURLConnection) {

    // Index of the called image in posts
    let calledIndex = posts["calledIndex"] as! Int

    // Index that the image actually loaded
    let loadedIndex = loadedIndex

    // If the indicies are the same, the image is already in the correct position
    if loadedIndex != calledIndex {

        // If they're not the same, swap them
        swap(&posts[calledIndex], &posts[loadedIndex])
    }
}

然后我尝试了没有swap 函数的类似操作:

// The post that was actually loaded
let loadedPost = posts[calledIndex]

// The post at the correct index
let postAtCorrectIndex = posts[loadedIndex]

posts[calledIndex] = postAtCorrectIndex
posts[loadedIndex] = loadedPost 

但是,在这两种情况下,数组值都没有正确交换。我意识到这是一个逻辑错误,但我没有看到错误的实际位置。

据我所知,它第一次交换正确,但是新字典的 calledIndex 值不正确,导致它交换回原来的位置。

这个假设可能是完全错误的,我意识到我很难描述这种情况,但我会尝试提供尽可能多的说明。


我做了一个测试用例,你可以download the source code here。它的代码是:

var allPosts:Array<Dictionary<String, AnyObject>> = [
    ["imageURL": "http://i.imgur.com/aLsnGqn.jpg", "postTitle":"0"],
    ["imageURL": "http://i.imgur.com/vgTXEYY.png", "postTitle":"1"],
    ["imageURL": "http://i.imgur.com/OXzDEA6.jpg", "postTitle":"2"],
    ["imageURL": "http://i.imgur.com/ilOKOx5.jpg", "postTitle":"3"],
]

var lastIndex = 0
var threshold = 4
var activeConnections = Dictionary<NSURLConnection, Dictionary<String, AnyObject?>>()

func loadBatchInForwardDirection(){
    func createConnection(i: Int){
        allPosts[i]["calledIndex"] = i
        var post = allPosts[i]
        let imageURL = NSURL(string: post["imageURL"] as! String)
        if imageURL != nil {
            let request = NSMutableURLRequest(URL: imageURL!, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: 60)
            let connection = NSURLConnection(request: request, delegate: self, startImmediately: true)
            if connection != nil {
                activeConnections[connection!] = post
            }
        }
    }
    let startingIndex = lastIndex;
    for (var i = startingIndex; i < startingIndex + threshold; i++){
        createConnection(i)
        lastIndex++
    }
}

func connection(connection: NSURLConnection, didReceiveData data: NSData) {
    if activeConnections[connection] != nil {
        let dataDict = activeConnections[connection]!["data"]
        if dataDict == nil {
            activeConnections[connection]!["data"] = NSMutableData(data: data)
        } else {
            (activeConnections[connection]!["data"] as! NSMutableData).appendData(data)
        }
    }
}

var loadedIndex = 0
func connectionDidFinishLoading(connection: NSURLConnection) {
    let loadedPost = activeConnections[connection]!
    activeConnections.removeValueForKey(connection)
    let data = loadedPost["data"] as? NSData
    let calledIndex = loadedPost["calledIndex"] as! Int
    println(calledIndex)

    swap(&allPosts[calledIndex], &allPosts[loadedIndex])
    //(allPosts[calledIndex], allPosts[loadedIndex]) = (allPosts[loadedIndex], allPosts[calledIndex])

    loadedIndex++
    done(loadedIndex)
}

func done(index: Int){
    if index == 4 {
        println()
        println("Actual: ")
        println(allPosts[0]["postTitle"] as! String)
        println(allPosts[1]["postTitle"] as! String)
        println(allPosts[2]["postTitle"] as! String)
        println(allPosts[3]["postTitle"] as! String)
    }
}

func applicationDidFinishLaunching(aNotification: NSNotification) {
    loadBatchInForwardDirection()
    println("Loaded: ")
}

func applicationWillTerminate(aNotification: NSNotification) {
    // Insert code here to tear down your application
}

输出是:

已加载: 1 0 2 3

实际: 0 1 2 3

但是,预期的“实际”输出应该是:

1 0 2 3

值得注意的是,使用元组代码会产生略微不稳定的结果,但与实际顺序不符。您可以通过取消注释该行来了解我的意思。

【问题讨论】:

  • 你的 posts 定义是不合法的 Swift。这是您的实际代码吗?你看到什么问题? posts 真的是 Swift Array,还是 NSArray 或 NSDictionary 以其他方式定义的?
  • @RobNapier 哎呀,我简化了数组,但忘记了键周围的引号。我将编写一个测试用例,希望能说明问题,完成后我会发布它。
  • 为什么不直接通过指定一个闭包来进行比较?
  • @RobNapier 用示例测试用例更新了帖子。我可能错过了一些愚蠢的东西,但它已经成为我一直盯着的问题之一,没有任何帮助!
  • @neo 这比使用swap 或 Rob 的元组代码更好吗?

标签: arrays swift cocoa


【解决方案1】:

您可以通过元组进行分配:

var xs = [1,2,3]   
(xs[1], xs[2]) = (xs[2], xs[1])

但是您实际上对swap 有什么问题?以下应该可以正常工作:

swap(&xs[1], &xs[2])

【讨论】:

  • 我用一个测试用例更新了这个问题。下载和下面sn-p的唯一区别是数组不再有calledIndex键硬编码。
【解决方案2】:

如果您可以将changeIndex的值类型更改为字符串,那么下面的代码应该可以工作

var posts = [
    ["url": "url0", "calledIndex": "0"],
    ["url": "url2", "calledIndex": "2"],
    ["url": "url3", "calledIndex": "3"],
    ["url": "url1", "calledIndex": "1"]
]

posts = sorted(posts, { (s1: [String:String], s2: [String:String]) -> Bool in
    return s1["calledIndex"] < s2["calledIndex"]
})

【讨论】:

    【解决方案3】:

    你可以对被调用的索引值使用排序:

    var posts = [
      ["url": "url0", "calledIndex": 0],
      ["url": "url2", "calledIndex": 2],
      ["url": "url1", "calledIndex": 1],
      ["url": "url3", "calledIndex": 3]
    ]
    
    
    var sortedPosts = sorted(posts) { ($0["calledIndex"] as! Int) < ($1["calledIndex"] as! Int)}
    

    【讨论】:

    • 正如 cmets 中提到的,只有数组的部分会指定 calledIndex 变量。
    【解决方案4】:

    问题是交换值不会给你正确的顺序,因为你可能会交换一个已经交换的值。例如,如果您按 3,0,1,2 的顺序收到,那么您将交换:

    array_before   called_index   loaded_index   array_after
    0, 1, 2, 3     0              3              3, 1, 2, 0
    3, 1, 2, 0     1              0              1, 3, 2, 0
    1, 3, 2, 0     2              1              1, 2, 3, 0
    1, 2, 3, 0     3              2              1, 2, 0, 3
    

    因此,即使您收到(并正确交换)3,0,1,2,这也会给您 1,2,0,3。

    如果您希望交换正常工作,您必须跟踪已交换的内容,以便知道要交换到哪个索引。在获取数据时添加到新数组可能会更容易,或者添加一个新字段来存储加载的索引并在最后对其进行排序。

    【讨论】:

      【解决方案5】:

      这实际上比预期的要容易得多。由于当前加载的帖子保存在activeConnections 中,我可以简单地将加载索引处的allPosts 值替换为当前加载的帖子:

      allPosts[loadedIndex] = loadedPost
      

      我想不出这种方法有什么缺点,但如果有人想到任何缺点,请告诉我。

      【讨论】:

        【解决方案6】:

        在 Swift 4 中

          array.swapAt(i, j)  // will swap elements at i and j
        

        协议

        protocol MutableCollection {
          /// Exchange the values at indices `i` and `j`.
          ///
          /// Has no effect when `i` and `j` are equal.
          public mutating func swapAt(_ i: Index, _ j: Index)
        }
        

        有关 swapAt 的更多信息,请参阅SE-0173 Add MutableCollection.swapAt(::)。

        【讨论】:

          猜你喜欢
          • 2020-06-19
          • 1970-01-01
          • 2017-08-06
          • 2021-03-29
          • 2011-11-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多