在Swift中将一个数组拆分成子数组

14

问题

给定一个值数组,如何将其分成由相等元素组成的子数组

示例

给定以下数组:

let numbers = [1, 1, 1, 3, 3, 4]

我希望您能将此输出

[[1,1,1], [3, 3], [4]]

我不需要的是什么

解决这个问题的一种可能方法是创建某种索引以指示每个元素的出现情况,就像这样。

let indexes = [1:3, 3:2, 4:1]

最后使用索引来重建输出数组。

let subsequences = indexes.sort { $0.0.0 < $0.1.0 }.reduce([Int]()) { (res, elm) -> [Int] in
    return res + [Int](count: elm.1, repeatedValue: elm.0)
}

然而,使用这种解决方案会导致丢失原始值。当然,在这种情况下,这不是一个大问题(即使重新创建,Int值仍然是Int),但我想将此解决方案应用于像这样更复杂的数据结构

struct Starship: Equatable {
    let name: String
    let warpSpeed: Int
}

func ==(left:Starship, right:Starship) -> Bool {
    return left.warpSpeed == right.warpSpeed
}

最终考虑事项

我正在寻找的函数应该是flatten()的某种相反形式,实际上。

let subsequences: [[Int]] = [[1,1,1], [3, 3], [4]]
print(Array(subsequences.flatten())) // [1, 1, 1, 3, 3, 4]

我希望我的表达清晰明了,如需更多细节,请告知。

4个回答

34
 // extract unique numbers using a set, then
 // map sub-arrays of the original arrays with a filter on each distinct number

 let numbers = [1, 1, 1, 3, 3, 4]

 let numberGroups = Set(numbers).map{ value in return numbers.filter{$0==value} }

 print(numberGroups)

[编辑] 根据Hamish建议,改用Set Initializer。

[编辑2] Swift 4 添加了一个初始化器,可以更高效地完成此操作:

 let numberGroups = Array(Dictionary(grouping:numbers){$0}.values)

要将对象列表按照它们的某个属性分组:

 let objectGroups = Array(Dictionary(grouping:objects){$0.property}.values)

为什么要使用 reduce 来创建 Set?你可以直接使用 Set 初始化器:Set(numbers).map... - Hamish
你是正确的 Hamish,我不确定集合初始化程序是否能够容忍重复,但它确实有效,并且更清晰。然而,由于原始帖子意图使用结构体的内部属性,因此最终需要使用 .map() 或我提出的原始 reduce。 - Alain T.
@AlainT。我有一个自定义对象的数组,需要根据其中一个属性将其拆分为子数组。我尝试稍微修改了您的代码以实现此目的,但是我得到了重复项。我的更改如下:let itemsGrouped = Set(items!).map{ value in return items!.filter{$0.custProp == value.custProp} } 为了获得所需的结果,应该如何更改呢? - user2363025
假设custProp是可哈希的,那么将items!.map {$ 0.custProp}设置为集合,然后映射值并返回items!.filter {$ 0.custProp == value}。 - Alain T.
在 Swift 4 中:Array(Dictionary(grouping:items){$0.custProp}.values) - Alain T.

5

如果您能使用CocoaPods/Carthage/Swift Package Manager等工具,您可以使用像oisdk/SwiftSequence这样的包,该包提供了group()方法:

numbers.lazy.group()
// should return a sequence that generates [1, 1, 1], [3, 3], [4].

或者使用UsrNameu1/TraverSwift,它提供了groupBy函数:

groupBy(SequenceOf(numbers), ==)

如果你不想添加外部依赖,那么你可以编写一个类似下面这样的算法:

func group<S: SequenceType where S.Generator.Element: Equatable>(seq: S) -> [[S.Generator.Element]] {
    var result: [[S.Generator.Element]] = []
    var current: [S.Generator.Element] = []
    for element in seq {
        if current.isEmpty || element == current[0] {
            current.append(element)
        } else {
            result.append(current)
            current = [element]
        }
    }
    result.append(current)
    return result
}

group(numbers)
// returns [[1, 1, 1], [3, 3], [4]].

我认为仅仅为了使用一个简单的分组函数而添加整个依赖关系有点过于繁琐。对于算法解决方案,我给予+1的支持,这也是我通过将其实现为“扩展”来采用的方法。 - Nicholas Allio

2
假设您有一个未排序的项目数组。您需要对初始数组进行排序,然后就会得到像这样的东西:[1, 1, 1, 3, 3, 4] 之后,您将初始化两个数组:一个用于存储数组,另一个用作当前数组。
循环遍历初始数组,并执行以下操作:
- 如果当前值与上一个值不同,则将其推送到当前数组。 - 否则,将当前数组推送到第一个数组,然后清空当前数组。
希望这可以帮助您!

0
值得一提的是,使用Swift Algorithms现在只需要一行代码:
import Algorithms
let numbers = [1, 1, 1, 3, 3, 4]
let chunks: [[Int]] = numbers.chunked(by: ==).map { .init($0) }
print(chunks) // [[1, 1, 1], [3, 3], [4]]

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