用Swift对数组进行分组和排序

5

假设我有这段代码:

class Stat {
   var statEvents : [StatEvents] = []
}

struct StatEvents {
   var name: String
   var date: String
   var hours: Int
}

var currentStat = Stat()

currentStat.statEvents = [
   StatEvents(name: "lunch", date: "01-01-2015", hours: 1),
   StatEvents(name: "dinner", date: "02-01-2015", hours: 2),
   StatEvents(name: "dinner", date: "03-01-2015", hours: 3),
   StatEvents(name: "lunch", date: "04-01-2015", hours: 4),
   StatEvents(name: "dinner", date: "05-01-2015", hours: 5),
   StatEvents(name: "breakfast", date: "06-01-2015", hours: 6),
   StatEvents(name: "lunch", date: "07-01-2015", hours: 7),
   StatEvents(name: "breakfast", date: "08-01-2015", hours: 8)
]

我想知道是否有一种方法可以获得以下输出的数组:
- [0]
  - name : "lunch"
  - date
    - [0] : "01-01-2015"
    - [1] : "04-01-2015"
    - [2] : "07-01-2015"
  - hours
    - [0] : 1
    - [1] : 4
    - [2] : 7     
- [1]
  - name : "dinner"
  - date
    - [0] : "02-01-2015"
    - [1] : "03-01-2015"
    - [2] : "05-01-2015"
  - hours
    - [0] : 2
    - [1] : 3   
    - [2] : 5          
- [2]
  - name : "breakfast"
  - date
    - [0] : "06-01-2015"
    - [1] : "08-01-2015"
  - hours
    - [0] : 6
    - [1] : 8 

正如您所看到的,最终的数组应该按“名称”降序分组。 @oisdk,请您检查一下?

4个回答

2

这可能看起来有点过度,但这是我想到的解决方案。

extension Array {
    /**
    Indicates whether there are any elements in self that satisfy the predicate.
    If no predicate is supplied, indicates whether there are any elements in self.
    */
    func any(predicate: T -> Bool = { t in true }) -> Bool {
        for element in self {
            if predicate(element) {
                return true
            }
        }
        return false
    }

    /**
    Takes an equality comparer and returns a new array containing all the distinct elements.
    */
    func distinct(comparer: (T, T) -> Bool) -> [T] {
        var result = [T]()
        for t in self {
            // if there are no elements in the result set equal to this element, add it
            if !result.any(predicate: { comparer($0, t) }) {
                result.append(t)
            }
        }
        return result
    }
}

let result = currentStat.statEvents
    .map({ $0.name })
    .distinct(==)
    .sorted(>)
    .map({ name in currentStat.statEvents.filter({ $0.name == name }) })

现在你有一个列表的列表,其中第一个列表包含所有晚餐类型的statEvents,下一个列表包含午餐类型的事件等。
显而易见的缺点是这种方法可能比其他解决方案性能较差。好处在于,您不必依赖并行数组来获取与特定日期相关联的小时数。

2
我的看法:
extension StatEvents : Comparable {}

func < (lhs:StatEvents, rhs:StatEvents) -> Bool {
    if lhs.name != rhs.name {
        return lhs.name > rhs.name
    } else if lhs.date != rhs.date {
        return lhs.date < rhs.date
    } else {
        return lhs.hours < rhs.hours
    }
}

func == (lhs:StatEvents, rhs:StatEvents) -> Bool {
    return lhs.name == rhs.name
        && lhs.date == rhs.date
        && lhs.hours == rhs.hours
}

struct ResultRow {
    var name: String
    var dates: [String]
    var hours: [Int]
}

var result : [ResultRow] = []

let sorted = currentStat.statEvents.sort()
for event in sorted {
    if result.last?.name != event.name {
        result.append(ResultRow(name: event.name, dates: [], hours: []))
    }
    result[result.endIndex - 1].dates.append(event.date)
    result[result.endIndex - 1].hours.append(event.hours)
}

测试:

for r in result { print(r) }

打印:

p.ResultRow(name: "lunch", dates: ["01-01-2015", "04-01-2015", "07-01-2015"], hours: [1, 4, 7])
p.ResultRow(name: "dinner", dates: ["02-01-2015", "03-01-2015", "05-01-2015"], hours: [2, 3, 5])
p.ResultRow(name: "breakfast", dates: ["06-01-2015", "08-01-2015"], hours: [6, 8])

2

已经有一些答案了,但是这很有趣。我的答案没有使用太多Swift中的高阶函数,但仍然可以完成任务:

// Get the list of unique event names
var eventNames = [String]()
for event in currentStat.statEvents {
    if !eventNames.contains(event.name) {
        eventNames.append(event.name)
    }
}

// The type of the result
struct ResultType {
    var name : String
    var date : [String]
    var hours : [Int]
}

var result = [ResultType]()
for name in eventNames {
    let matchingEvents = currentStat.statEvents.filter { $0.name == name }
    let dates = matchingEvents.map { $0.date }
    let hours = matchingEvents.map { $0.hours }

    result.append(ResultType(name: name, date: dates, hours: hours))
}

1
最终结果是类型为[[String : AnyObject]],或者您创建一个新的结构体类型来保存这些值,结果就是类型为[String : NewStructType]:
struct NewStructType
{ 
    var dates: [String]?
    var hours: [Int]?
}

所以你必须做出决定,然后编写自己的函数来对StatEvents对象进行排序和分组。也许你可以优化它的性能,但这里是实现第二个版本(使用NewStructType)的第一个想法:

var result = [String : NewStructType]()

for statEvent in currentStat.statEvents
{
    if (result[statEvent.name] != nil)
    {
        var newStructType = result[statEvent.name]!

        newStructType.dates.append(statEvent.date)
        newStructType.hours.append(statEvent.hours)
    }
    else
    {
        result[statEvent.name] = NewStructType(dates: [statEvent.date], hours: [statEvent.hours])
    }
}

1
在结构体定义中,不需要将日期和小时作为可选项。它也可以有一个初始化器,接受日期[String]和小时[Int]。此外,如果这是一次性使用,类型可以简单地为'[String:(dates:[String],hours:[Int])]'。但是,如果您将此值传递给本地上下文或类之外的调用者,则拥有该结构将是很好的选择。 - Rikki Gibson

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