2

I have this code in my viewController

var myArray :Array<Data> = Array<Data>()
for i in 0..<mov.count {    
   myArray.append(Data(...))
}

class Data {
    var value :CGFloat
    var name  :String=""
    init({...})
}

My input of Data is as:

  • 10.5 apple
  • 20.0 lemon
  • 15.2 apple
  • 45

Once I loop through, I would like return a new array as:

  • sum(value) group by name
  • delete last row because no have name
  • ordered by value

Expected result based on input:

  • 25.7 apple
  • 20.0 lemon
  • and nothing else

I wrote many rows of code and it is too confused to post it. I'd find easier way, anyone has a idea about this?

2
  • 2
    Consider renaming your Data class, since Foundation includes a class by that name. Commented Nov 17, 2016 at 14:57
  • Please show us your best solution so far and we can let you know where you are going wrong Commented Nov 17, 2016 at 14:57

3 Answers 3

5

First of all Data is reserved in Swift 3, the example uses a struct named Item.

struct Item {
    let value : Float
    let name  : String
}

Create the data array with your given values

let dataArray = [Item(value:10.5, name:"apple"), 
                 Item(value:20.0, name:"lemon"), 
                 Item(value:15.2, name:"apple"), 
                 Item(value:45, name:"")]

and an array for the result:

var resultArray = [Item]()

Now filter all names which are not empty and make a Set - each name occurs one once in the set:

let allKeys = Set<String>(dataArray.filter({!$0.name.isEmpty}).map{$0.name})

Iterate thru the keys, filter all items in dataArray with the same name, sum up the values and create a new Item with the total value:

for key in allKeys {
    let sum = dataArray.filter({$0.name == key}).map({$0.value}).reduce(0, +)
    resultArray.append(Item(value:sum, name:key))
}

Finally sort the result array by value desscending:

resultArray.sorted(by: {$0.value < $1.value})

---

Edit:

Introduced in Swift 4 there is a more efficient API to group arrays by a predicate, Dictionary(grouping:by:

var grouped = Dictionary(grouping: dataArray, by:{$0.name})
grouped.removeValue(forKey: "") // remove the items with the empty name

resultArray = grouped.keys.map { (key) -> Item in
    let value = grouped[key]!
    return Item(value: value.map{$0.value}.reduce(0.0, +), name: key)
}.sorted{$0.value < $1.value}

print(resultArray)
Sign up to request clarification or add additional context in comments.

11 Comments

I'm playing with your answer to understand. I wrote let allKeys = Set<String> (dataArray.filter({!$0.name.isEmpty})) . I intentionally didn't write the map{$0.name}) to see/understand what I get...but I get an error saying ambigous refernce to member filter. I looked here but still couldn't solve it. What am I doing wrong?
Without map you get Set<Item>
I get that. But right now, why am I getting that error if I only write that far into your answer.
The type used in a Set must conform to Hashable which the simple struct does not.
The error might be misleading. The ambiguity is either the type does not match – Set<String> is annotated but the inferred type is Set<Item> or the creation of the set is not possible because Item is not hashable.
|
2

First of all, you should not name your class Data, since that's the name of a Foundation class. I've used a struct called MyData instead:

struct MyData {
    let value: CGFloat
    let name: String
}

let myArray: [MyData] = [MyData(value: 10.5, name: "apple"),
                         MyData(value: 20.0, name: "lemon"), 
                         MyData(value: 15.2, name: "apple"),
                         MyData(value: 45,   name: "")]

You can use a dictionary to add up the values associated with each name:

var myDictionary = [String: CGFloat]()
for dataItem in myArray {
    if dataItem.name.isEmpty {
        // ignore entries with empty names
        continue
    } else if let currentValue = myDictionary[dataItem.name] {
        // we have seen this name before, add to its value
        myDictionary[dataItem.name] = currentValue + dataItem.value
    } else {
       // we haven't seen this name, add it to the dictionary
        myDictionary[dataItem.name] = dataItem.value
    }
}

Then you can convert the dictionary back into an array of MyData objects, sort them and print them:

// turn the dictionary back into an array
var resultArray = myDictionary.map { MyData(value: $1, name: $0) }

// sort the array by value
resultArray.sort { $0.value < $1.value }

// print the sorted array
for dataItem in resultArray {
    print("\(dataItem.value) \(dataItem.name)")
}

1 Comment

apparently this works, but it's written in a very un-Swifty way. :)
1

First change your data class, make string an optional and it becomes a bit easier to handle. So now if there is no name, it's nil. You can keep it as "" if you need to though with some slight changes below.:

class Thing {
    let name: String?
    let value: Double

    init(name: String?, value: Double){
        self.name = name
        self.value = value
    }

    static func + (lhs: Thing, rhs: Thing) -> Thing? {
        if rhs.name != lhs.name {
            return nil
        } else {
            return Thing(name: lhs.name, value: lhs.value + rhs.value)
        }
    }
}

I gave it an operator so they can be added easily. It returns an optional so be careful when using it.

Then lets make a handy extension for arrays full of Things:

extension Array where Element: Thing {

func grouped() -> [Thing] {

    var things = [String: Thing]()

    for i in self {
        if let name = i.name {
            things[name] = (things[name] ?? Thing(name: name, value: 0)) + i
        }
    }
    return things.map{$0.1}.sorted{$0.value > $1.value}
}
}

Give it a quick test:

let t1 = Thing(name: "a", value: 1)
let t2 = Thing(name: "b", value: 2)
let t3 = Thing(name: "a", value: 1)
let t4 = Thing(name: "c", value: 3)
let t5 = Thing(name: "b", value: 2)
let t6 = Thing(name: nil, value: 10)


let bb = [t1,t2,t3,t4,t5,t6]

let c = bb.grouped()

// ("b",4), ("c",3) , ("a",2)

Edit: added an example with nil for name, which is filtered out by the if let in the grouped() function

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.