2

I have an array of Item objects:

struct Item {
   let date: Date
   let amount: Double
}

How can I group this items into a dictionary with Date as key and sum of amount for that date in Swift 3 ?

Here is an example:

let data = [Item(date:"2017.02.15", amount: 25), 
            Item(date:"2017.02.14", amount: 50), 
            Item(date:"2017.02.11", amount: 35), 
            Item(date:"2017.02.15", amount: 15)]

Result should be:

["2017.02.15": 40, "2017.02.14": 50, "2017.02.11": 35]
4
  • 1
    Looking at your example results, it seems you want the String representation of your Date to be the key (not the Date itself). Also, if Date is not a typealias for String, the Item default initializer calls when instantiating the data array wont compile. Commented Feb 15, 2017 at 18:03
  • I actually want date, but it was easier to write it as a string Commented Feb 15, 2017 at 18:05
  • The initializer from String to Date on your example will not work. Do you mean declaring Item.date as String instead of Date? Commented Feb 15, 2017 at 18:05
  • @Kobe just gave an example of date for better understanding, right? Commented Feb 15, 2017 at 18:06

3 Answers 3

3

Given we fix your example up somewhat (using String as Date example), you could simply pass over the elements in data and add to (and optionally increase) the corresponding key-value pair in the dict.

struct Item {
   let date: String
   let amount: Double
}

let data = [Item(date: "2017.02.15", amount: 25), 
            Item(date: "2017.02.14", amount: 50), 
            Item(date: "2017.02.11", amount: 35), 
            Item(date: "2017.02.15", amount: 15)]

var dict: [String: Double] = [:]
for item in data {
    dict[item.date] = (dict[item.date] ?? 0) + item.amount
}

print(dict) // ["2017.02.14": 50.0, "2017.02.15": 40.0, "2017.02.11": 35.0]
Sign up to request clarification or add additional context in comments.

1 Comment

@Xcoder123 dictionaries are non-ordered collections, so the "order" shown holds no value here.
3

With Swift 4, according to your needs, you may choose one of the following solutions in order to solve your problem.


#1 Using Dictionary subscript(_:default:)

Dictionay has a subscript called subscript(_:default:). subscript(_:default:) has the following declaration:

subscript(key: Dictionary.Key, default defaultValue: @autoclosure () -> Dictionary.Value) -> Dictionary.Value { get set }

Accesses the element with the given key, or the specified default value, if the dictionary doesn’t contain the given key.

The following Playground example shows how to use subscript(_:default:) in order to solve your problem:

struct Item {
    let date: String
    let amount: Double
}

let data = [
    Item(date:"2017.02.15", amount: 25),
    Item(date:"2017.02.14", amount: 50),
    Item(date:"2017.02.11", amount: 35),
    Item(date:"2017.02.15", amount: 15)
]

var dictionary = [String: Double]()

for item in data {
    dictionary[item.date, default: 0.0] += item.amount
}

print(dictionary) // ["2017.02.11": 35.0, "2017.02.15": 40.0, "2017.02.14": 50.0]

#2 Using Dictionary init(_:uniquingKeysWith:) initializer

Dictionay has a init(_:uniquingKeysWith:) initializer. init(_:uniquingKeysWith:) has the following declaration:

init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)

Creates a new dictionary from the key-value pairs in the given sequence, using a combining closure to determine the value for any duplicate keys.

The following Playground example shows how to use init(_:uniquingKeysWith:) in order to solve your problem:

struct Item {
    let date: String
    let amount: Double
}

let data = [
    Item(date:"2017.02.15", amount: 25),
    Item(date:"2017.02.14", amount: 50),
    Item(date:"2017.02.11", amount: 35),
    Item(date:"2017.02.15", amount: 15)
]

let tupleArray = data.map({ ($0.date, $0.amount) })
let dictonary = Dictionary(tupleArray, uniquingKeysWith: { (current, new) in
    current + new
})
//let dictonary = Dictionary(tupleArray, uniquingKeysWith: +) // also works

print(dictonary) // prints ["2017.02.11": 35.0, "2017.02.15": 40.0, "2017.02.14": 50.0]

Comments

2

I thought this solution may be better since your property date actually is a Date

Solution:

struct Item {
  let date: Date
  let amount: Double
}

var data = [Item(date:createDate(stringDate: "2017.02.15"), amount: 25),
            Item(date:createDate(stringDate: "2017.02.14"), amount: 50),
            Item(date:createDate(stringDate: "2017.02.11"), amount: 35),
            Item(date:createDate(stringDate: "2017.02.15"), amount: 15)]

data = sumAmounts(data)

print(data)
//[
//  Item(date: 2017-02-15 04:00:00 +0000, amount: 40.0),
//  Item(date: 2017-02-14 04:00:00 +0000, amount: 50.0),
//  Item(date: 2017-02-11 04:00:00 +0000, amount: 35.0)
//]

Helper methods:

func createDate(stringDate: String) -> Date {
  let formatter = DateFormatter()
  formatter.dateFormat = "yyyy.MM.dd"
  return formatter.date(from: stringDate)!
}

func sumAmounts(_ data: [Item]) -> [Item] {
  var dict = [Double: Double]()
  data.forEach { item in
    let key = item.date.timeIntervalSince1970
    
    var amount = dict[key] ?? 0
    amount += item.amount
    dict[key] = amount
  }
  
  var newData = [Item]()
  for (key, val) in dict {
    newData.append(Item(date: Date.init(timeIntervalSince1970: key), amount: val))
  }
  
  return newData
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.