在特定索引处交换数组值?

9

我正在基于一个包含多个字典的数组创建异步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值,导致它回到原来的位置。
这个假设可能是完全错误的,我意识到我很难描述这种情况,但我会尽可能提供更多的澄清。
我做了一个测试用例,你可以在这里下载源代码。它的代码如下:
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

值得注意的是,使用元组代码会导致略微有些不正常的结果,但没有匹配实际排序的内容。您可以通过取消注释该行来查看我的意思。

1
@RobNapier 哎呀,我把数组简化了,但忘记在键周围加引号了。我将编写一个测试用例,希望能展示问题,并在完成后发布它。 - Charlie
为什么不直接通过指定一个闭包来进行排序呢? - neo
@RobNapier 更新了帖子,并附上了一个示例测试用例。我可能错过了一些愚蠢的东西,但这已经成为我盯着看太久的问题之一,没有任何帮助! - Charlie
@neo 这比使用 swap 或 Rob 的元组代码更好吗? - Charlie
@neo 但是大多数项目还没有它们的calledIndex键,因为它们还没有被加载。 - Charlie
显示剩余8条评论
6个回答

14
你可以使用元组进行赋值:
var xs = [1,2,3]   
(xs[1], xs[2]) = (xs[2], xs[1])

但你实际上对于swap有什么问题呢?以下代码应该可以正常工作:

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

我已经更新了问题,并提供了一个测试用例。下载和下面的代码片段之间唯一的区别是数组不再硬编码calledIndex键。 - Charlie

3
如果您可以将changeIndex的值类型更改为String,则下面的代码应该有效。
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"]
})

2
问题在于交换值并不能给你正确的顺序,因为你可能会交换已经交换过的值。例如,如果你按照顺序收到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。

如果你想让交换起作用,你需要跟踪已经交换的内容,以便知道要交换到哪个索引。当你获取数据返回时,将其添加到一个新数组中可能会更容易,或者添加一个新字段来存储已加载的索引,并在最后进行排序。


2
您可以直接使用sort函数对calledIndex值进行排序:
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)}

如评论中所述,只有数组的某些部分将指定calledIndex变量。 - Charlie

2

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

allPosts[loadedIndex] = loadedPost

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


1
在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)
}

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接