I am trying to make an Array
extension in Swift 3.1.1 that supports the addition of an object to a certain index in a 2D Array even if the array hasn't been populated yet. The extension should also provide the ability to get an object at certain indexPath
. I have the code for this in Swift 2 but I don't seem to be able to migrate it to Swift 3. This is the Swift 2 code:
我试图在Swift 3.1.1中创建一个Array扩展,它支持将对象添加到2D Array中的某个索引,即使该数组尚未填充。扩展还应该提供在某个indexPath上获取对象的能力。我在Swift 2中有这个代码,但我似乎无法将其迁移到Swift 3.这是Swift 2代码:
extension Array where Element: _ArrayProtocol, Element.Iterator.Element: Any {
mutating func addObject(_ anObject : Element.Iterator.Element, toSubarrayAtIndex idx : Int) {
while self.count <= idx {
let newSubArray = Element()
self.append(newSubArray)
}
var subArray = self[idx]
subArray.append(anObject)
}
func objectAtIndexPath(_ indexPath: IndexPath) -> Any {
let subArray = self[indexPath.section]
return subArray[indexPath.row] as Element.Iterator.Element
}
}
The code is taken from this answer.
代码来自这个答案。
2 个解决方案
#1
5
As Martin says in his answer here, _ArrayProtocol
is no longer public
in Swift 3.1, therefore meaning that you cannot use it as a constraint in your extension.
正如Martin在他的回答中所说,_ArrayProtocol在Swift 3.1中不再公开,因此意味着你不能将它用作扩展中的约束。
A simple alternative in your case is to instead constrain the Array
's Element
to being a RangeReplaceableCollection
– which both defines an init()
requirement meaning "empty collection", and an append(_:)
method in order to add elements to the collection.
在您的情况下,一个简单的替代方法是将Array的Element约束为RangeReplaceableCollection - 它定义了一个init()需求,意味着“空集合”,以及一个append(_ :)方法,以便向集合中添加元素。
extension Array where Element : RangeReplaceableCollection {
typealias InnerCollection = Element
typealias InnerElement = InnerCollection.Iterator.Element
mutating func fillingAppend(
_ newElement: InnerElement,
toSubCollectionAtIndex index: Index) {
if index >= count {
append(contentsOf: repeatElement(InnerCollection(), count: index + 1 - count))
}
self[index].append(newElement)
}
}
Note also that we're doing the append as a single call (using append(contentsOf:
), ensuring that we only have to resize the outer array at most once.
另请注意,我们将附加作为单个调用进行(使用append(contentsOf :),确保我们只需要调整外部数组一次。
For your method to get an element from a given IndexPath
, you can just constrain the inner element type to being a Collection
with an Int
Index
:
对于从给定IndexPath获取元素的方法,您可以将内部元素类型约束为具有Int索引的Collection:
// could also make this an extension on Collection where the outer Index is also an Int.
extension Array where Element : Collection, Element.Index == Int {
subscript(indexPath indexPath: IndexPath) -> Element.Iterator.Element {
return self[indexPath.section][indexPath.row]
}
}
Note that I've made it a subscript
rather than a method, as I feel it fits better with Array
's API.
请注意,我已经将它作为下标而不是方法,因为我觉得它更符合Array的API。
You can now simply use these extensions like so:
您现在可以像这样简单地使用这些扩展:
var arr = [[Int]]()
arr.fillingAppend(6, toSubCollectionAtIndex: 3)
print(arr) // [[], [], [], [6]]
let indexPath = IndexPath(row: 0, section: 3)
print(arr[indexPath: indexPath]) // 6
Although of course if you know the size of the outer array in advance, the fillingAppend(_:toSubCollectionAtIndex:)
method is redundant, as you can just create your nested array by saying:
虽然当然如果您事先知道外部数组的大小,fillAppend(_:toSubCollectionAtIndex :)方法是多余的,因为您可以通过以下方式创建嵌套数组:
var arr = [[Int]](repeating: [], count: 5)
which will create an [[Int]]
array containing 5 empty [Int]
elements.
这将创建一个包含5个空[Int]元素的[[Int]]数组。
#2
0
There's no need to limit all these ideas to the concrete Array
type.
没有必要将所有这些想法限制为具体的Array类型。
Here's my solution. This discussion was great in that I just learned about RangeReplaceableCollection
. Merging (what I think is) the best of both worlds, I pushed all the operations down (up?) the Type hierarchy as far as possible.
这是我的解决方案。这个讨论非常棒,因为我刚刚了解了RangeReplaceableCollection。合并(我认为是)两个世界中最好的,我尽可能地推动所有操作(向上?)类型层次结构。
Subscript works on much more than Array
as @Hamish says. But also, there's no need to constrain the index type, so we have to get rid of IndexPath
. We can always sugar this with typealias Index2d = ...
正如@Hamish所说,下标比Array更有效。但是,也没有必要约束索引类型,所以我们必须摆脱IndexPath。我们总是可以用类型Index2d = ...加糖...
extension Collection where Self.Element: Collection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
}
}
Why not have a mutable version at the most generic possible level (between Collection
and RangeReplaceableCollection
) (unfortunately I don't think the getter can be inherited when we redefine subscript
):
为什么不在最通用的可能级别(在Collection和RangeReplaceableCollection之间)有一个可变版本(遗憾的是我不认为在重新定义下标时可以继承getter):
extension MutableCollection where Self.Element: MutableCollection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
set {
self[indexTuple.row][indexTuple.column] = newValue
}
}
}
Then, if you want to initialize lazily, avoid using init:repeatedValue
and revise set
to have auto-initialization semantics. You can trap bounds overflow and add missing empty elements in both dimensions by integrating the accepted answer's fillingAppend
idea.
然后,如果要延迟初始化,请避免使用init:repeatedValue和revise set来使用自动初始化语义。您可以通过集成接受的答案的fillingAppend想法来捕获边界溢出并在两个维度中添加缺少的空元素。
And when creating a 2D initializer, why not extend the idea of repeating
in the natural way:
在创建2D初始化程序时,为什么不以自然方式扩展重复的想法:
extension RangeReplaceableCollection where Element: RangeReplaceableCollection {
init(repeating repeatedVal: Element.Element, extents: (row: Int, column: Int)) {
let repeatingColumn = Element(repeating: repeatedVal, count: extents.column)
self.init(repeating: repeatingColumn, count: extents.row)
}
}
Example Usage:
enum Player {
case first
case second
}
class Model {
let playerGrid: Array<Array<Player>> = {
var p = [[Player]](repeating: .first, extents: (row: 10, column: 10))
p[(3, 4)] = .second
print("Player at 3, 4 is: \(p[(row: 3, column: 4)])")
return p
}()
}
#1
5
As Martin says in his answer here, _ArrayProtocol
is no longer public
in Swift 3.1, therefore meaning that you cannot use it as a constraint in your extension.
正如Martin在他的回答中所说,_ArrayProtocol在Swift 3.1中不再公开,因此意味着你不能将它用作扩展中的约束。
A simple alternative in your case is to instead constrain the Array
's Element
to being a RangeReplaceableCollection
– which both defines an init()
requirement meaning "empty collection", and an append(_:)
method in order to add elements to the collection.
在您的情况下,一个简单的替代方法是将Array的Element约束为RangeReplaceableCollection - 它定义了一个init()需求,意味着“空集合”,以及一个append(_ :)方法,以便向集合中添加元素。
extension Array where Element : RangeReplaceableCollection {
typealias InnerCollection = Element
typealias InnerElement = InnerCollection.Iterator.Element
mutating func fillingAppend(
_ newElement: InnerElement,
toSubCollectionAtIndex index: Index) {
if index >= count {
append(contentsOf: repeatElement(InnerCollection(), count: index + 1 - count))
}
self[index].append(newElement)
}
}
Note also that we're doing the append as a single call (using append(contentsOf:
), ensuring that we only have to resize the outer array at most once.
另请注意,我们将附加作为单个调用进行(使用append(contentsOf :),确保我们只需要调整外部数组一次。
For your method to get an element from a given IndexPath
, you can just constrain the inner element type to being a Collection
with an Int
Index
:
对于从给定IndexPath获取元素的方法,您可以将内部元素类型约束为具有Int索引的Collection:
// could also make this an extension on Collection where the outer Index is also an Int.
extension Array where Element : Collection, Element.Index == Int {
subscript(indexPath indexPath: IndexPath) -> Element.Iterator.Element {
return self[indexPath.section][indexPath.row]
}
}
Note that I've made it a subscript
rather than a method, as I feel it fits better with Array
's API.
请注意,我已经将它作为下标而不是方法,因为我觉得它更符合Array的API。
You can now simply use these extensions like so:
您现在可以像这样简单地使用这些扩展:
var arr = [[Int]]()
arr.fillingAppend(6, toSubCollectionAtIndex: 3)
print(arr) // [[], [], [], [6]]
let indexPath = IndexPath(row: 0, section: 3)
print(arr[indexPath: indexPath]) // 6
Although of course if you know the size of the outer array in advance, the fillingAppend(_:toSubCollectionAtIndex:)
method is redundant, as you can just create your nested array by saying:
虽然当然如果您事先知道外部数组的大小,fillAppend(_:toSubCollectionAtIndex :)方法是多余的,因为您可以通过以下方式创建嵌套数组:
var arr = [[Int]](repeating: [], count: 5)
which will create an [[Int]]
array containing 5 empty [Int]
elements.
这将创建一个包含5个空[Int]元素的[[Int]]数组。
#2
0
There's no need to limit all these ideas to the concrete Array
type.
没有必要将所有这些想法限制为具体的Array类型。
Here's my solution. This discussion was great in that I just learned about RangeReplaceableCollection
. Merging (what I think is) the best of both worlds, I pushed all the operations down (up?) the Type hierarchy as far as possible.
这是我的解决方案。这个讨论非常棒,因为我刚刚了解了RangeReplaceableCollection。合并(我认为是)两个世界中最好的,我尽可能地推动所有操作(向上?)类型层次结构。
Subscript works on much more than Array
as @Hamish says. But also, there's no need to constrain the index type, so we have to get rid of IndexPath
. We can always sugar this with typealias Index2d = ...
正如@Hamish所说,下标比Array更有效。但是,也没有必要约束索引类型,所以我们必须摆脱IndexPath。我们总是可以用类型Index2d = ...加糖...
extension Collection where Self.Element: Collection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
}
}
Why not have a mutable version at the most generic possible level (between Collection
and RangeReplaceableCollection
) (unfortunately I don't think the getter can be inherited when we redefine subscript
):
为什么不在最通用的可能级别(在Collection和RangeReplaceableCollection之间)有一个可变版本(遗憾的是我不认为在重新定义下标时可以继承getter):
extension MutableCollection where Self.Element: MutableCollection {
subscript(_ indexTuple: (row: Self.Index, column: Self.Element.Index)) -> Self.Element.Element {
get {
return self[indexTuple.row][indexTuple.column]
}
set {
self[indexTuple.row][indexTuple.column] = newValue
}
}
}
Then, if you want to initialize lazily, avoid using init:repeatedValue
and revise set
to have auto-initialization semantics. You can trap bounds overflow and add missing empty elements in both dimensions by integrating the accepted answer's fillingAppend
idea.
然后,如果要延迟初始化,请避免使用init:repeatedValue和revise set来使用自动初始化语义。您可以通过集成接受的答案的fillingAppend想法来捕获边界溢出并在两个维度中添加缺少的空元素。
And when creating a 2D initializer, why not extend the idea of repeating
in the natural way:
在创建2D初始化程序时,为什么不以自然方式扩展重复的想法:
extension RangeReplaceableCollection where Element: RangeReplaceableCollection {
init(repeating repeatedVal: Element.Element, extents: (row: Int, column: Int)) {
let repeatingColumn = Element(repeating: repeatedVal, count: extents.column)
self.init(repeating: repeatingColumn, count: extents.row)
}
}
Example Usage:
enum Player {
case first
case second
}
class Model {
let playerGrid: Array<Array<Player>> = {
var p = [[Player]](repeating: .first, extents: (row: 10, column: 10))
p[(3, 4)] = .second
print("Player at 3, 4 is: \(p[(row: 3, column: 4)])")
return p
}()
}