Difference between creating a dictionary by reduce on an array vs by assigning each item on iteration

老子叫甜甜 提交于 2020-01-25 03:58:11

问题


I came across a question on StackOverflow: Swift - Convert Array to Dictionary where the user wants to take the elements of an array and wants to put them in a dictionary and assign 0 to each of them. (Key as Players and value as their scores) So:

var playerNames = ["Harry", "Ron", "Hermione"]

becomes

var scoreBoard: [String:Int] = [ "Ron":0, "Harry":0, "Hermione":0 ]

This question got 2 answers: 1) Uses reduce on the array

let scoreboard = playerNames.reduce(into: [String: Int]()) { $0[$1] = 0 }

2)Creates a dictionary and iterates on array to add each value key pair to it

var dictionary = [String: Int]()
for player in playerNames {
    dictionary[player] = 0
}

I took the BenchTimer function from https://github.com/nyisztor/swift-algorithms to test both of these ways of solving. And they both seem to operate in O(n).

I was wondering why we would prefer the first one over the other since the person who wrote the second solution got a bad comment about their coding skills.

Edit: Some functionality gets deprecated by Apple in newer versions so isn't it better to stick with the basics and create our own ways to do things?

Thank you for the answers


回答1:


Today, IMO, you shouldn't use either of these. We now have Dictionary.init(uniqueKeysWithValues:) and .init(_:uniquingKeysWith:) which much more clearly state their intent, and make corner cases, such as duplicate keys, explicit.

If you statically can prove that all the keys are unique, then you would use the first one:

let scoreboard = Dictionary(uniqueKeysWithValues: playerNames.map { (name: $0, score: 0) })

If you cannot prove the keys are unique, you'd use the second one, which would allow you to explicitly decide what to do in cases of conflict.

let scoreboard = Dictionary(playerNames.map { (name: $0, score: 0) },
                            uniquingKeysWith: { first, _ in first })

Note how this approach allows labels to make clear what the key is and what the value is. I haven't benchmarked this code, but I would expect it to be very similar in terms of time to the others.




回答2:


so isn't it better to stick with the basics and create our own ways to do things?

I don't think so. The Swift community certainly isn't of that mindset. Swift prioritizes making meaningful abstractions and simplifications, so-long as they're valuable, progressively disclosable.

The Go community shares your line of thinking, but it's quite painful (IMO). The Go standard library doesn't even have an API for reversing a String. You have to cook it yourself. And it's harder than most people think. If you think it's a simple matter of making a loop to reverse the bytes, nope, that's totally broken for unicode (but would likely go unnoticed with simple ASCII test cases like "hello" or whatever).

Similarly, if you write for loops every time you want to implement map, you might forget to call Array.reserveCapacity(_:). Forgetting that will cause multiple array allocations, and make your O(n)-looking algorithm into actually being O(n^2). There's lets of little performance or correctness "gotchas" like this, so there's a great benefit to using popular, shared implementations of these things.

We stand on the shoulders of giants. We couldn't do that if we were all preoccupied with reinventing wheels.

About the two pieces of code

I wouldn't use either of them

The first approach:

  1. Uses a function (reduce), but not the right one for the job (Dictionary.init(uniqueKeysWithValues:)). See https://github.com/amomchilov/Blog/blob/master/Don't%20abuse%20reduce.md
  2. Forgets to reserve capacity on the dictionary, so it causes multiple resizing operations (which involve memory reallocation, and rehashing of all keys in the dictionary)

The second approach:

  1. Leaves the dictionary unecessarily mutable
  2. Also forgets to reserve capacity.

Instead, I would recommend Rob Napier or Martin R's approach. Both of them are more expressive of your intent. Rob's also uses a sequence with a known size, which allows Dictionary.init(uniqueKeysWithValues:) to internally allocate enough memory up-front for the dictionary as will be necessary to fit all values.




回答3:


Generally speaking you should prefer the functional idiom (ie reduce) over the for loop, for a couple of reasons:

  1. The functional version conveys intent. In this case we are reducing an array into a dictionary -- not the best example, but usually reduce transforms a vector into a single scalar through some combining function.
  2. The functional version is correct by default since its been tested. This may seem trivial in the case of reduce, but its much less so if you look at something like shuffled. How easily can you look at a for loop and tell me if its performing a Fisher Yates shuffle and if that shuffle is implemented correctly? Similarly a pipeline of functions is much easier to read than a bunch of sequential for loops or a single for loop that does 5 different things.

  3. The functional version is often (but not in this case) immutable and immutable values are easier to reason about than mutable state, because they never change.

  4. The functional methods on Swift.Sequence mostly have analogous methods on Combine.Publisher. That means you can use a single set of functional idioms across all sequences, whether or not they are synchronous or asynchronous/reactive.



来源:https://stackoverflow.com/questions/58548078/difference-between-creating-a-dictionary-by-reduce-on-an-array-vs-by-assigning-e

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!