Count elements of nested array swift - ios

Does anyone know here how to count amount of elements in all nested array of custom objects in Swift?
I.e. I have
[Comments] array which includes [Attachments] array. There may be 100 comments and 5 attachments in each of them. What is the most Swifty way to count all attachments in all comments? I tried few solutions like flatMap, map, compactMap, filter, reduce, but couldn't figure out how to achieve the desire result. The only one that worked for me was typical for in loop.
for comment in comments {
attachmentsCount += comment.attachments.count
}
Is there any better approach to achieve the same? Thanks

You can use reduce(_:_:) function of the Array to do that:
let attachementsCount = comments.reduce(0) { $0 + $1.attachments.count }

Here are two ways to do it based on using map
First with reduce
let count = comments.map(\.attachments.count).reduce(0, +)
And one variant using joined
let count = comments.map(\.attachments).joined().count

Related

How to sort array based on key?

I have an array which has two types of objects: Awarded and non-awarded. I simply want to sort my array so that awarded objects are placed first, and the rest are placed afterwards.
Here is the code that defines the key "awarded":
if let awarded = achievements[indexPath.row].userRelation["awarded"] as? String where awarded != "<null>" { }
Within those brackets I'd like to use my unwrapped value "awarded" to sort through the achievements and add those to the beginning, and the rest at the end.
How would I go about doing that?
You should experiment with sort function, it is very useful.
let sortedAchievements = achievements.sorted{ $0.userRelation["awarded"] < $1.userRelation["awarded"] }
To sort in place use this:
achievements.sort{ $0.userRelation["awarded"] < $1.userRelation["awarded"] }
I would recommend to refactor your model. It's better to use objects or structs instead of dictionaries. Also awarded should be a Bool property, not a String, right?

programatically making a 2d array in swift

I need to use a for loop to create a 2d array. So far "+=" and .append have not yielded any results. Here is my code. Please excuse the rushed variable naming.
let firstThing = contentsOfFile!.componentsSeparatedByString("\n")
var secondThing: [AnyObject] = []
for i in firstThing {
let temp = i.componentsSeparatedByString("\"")
secondThing.append(temp)
}
The idea is that it takes the contents of a csv file, then separates the individual lines. It then tries to separate each of the lines by quotation marks. This is where the problem arises. I am successfully making the quotation separated array (stored in temp), however, I cannot make a collection of these in one array (i.e. a 2d array) using the for loop. The above code generates an error. Does anybody have the answer of how to construct this 2d array?
You can do this using higher order functions...
let twoDimensionalArray = contentsOfFile!.componentsSeparatedByString("\n").map{
$0.componentsSeparatedByString("\"")
}
The map function takes an array of items and maps each one to another type. In this I'm mapping the strings from the first array into an array pf strings and so creating a 2d array.
This will infer the type of array that is created so no need to put [[String]].
Here you go...

Summing 5 objects after a where statement

I have an active record of 5 objects as follows
#top_sold = Photo.where(photographer_id: #photog).order('qty_sold DESC').first(5)
I want to know the sum of qty_sold for all 5 photos
This isnt working
#top_sold.sum(:qty_sold)
Much thanks in adcance
.first returns Array, use limit instead (which returns ActiveRecord::Relation).
#top_sold = Photo.where(photographer_id: #photog).order('qty_sold DESC').limit(5)
for Array you can do
#top_sold.map(:qty_sold).inject(:+)
Have you tried appending sum to your query?
#top_sold = Photo.where(photographer_id: #photog)
.order('qty_sold DESC').first(5).sum('qty_old')
If you need the original result set, then #Nithin answer is what you want to do.

How to sum all properties of a nested collection?

Given I got User.attachments and Attachment.visits as an integer with the number count.
How can I easily count all the visits of all images of that user?
Use ActiveRecord::Base#sum:
user.attachments.sum(:visits)
This should generate an efficient SQL query like this:
SELECT SUM(attachments.visits) FROM attachments WHERE attachments.user_id = ID
user.attachments.map{|a| a.visits}.sum
There's also inject:
user.attachments.inject(0) { |sum, a| sum + a.visits }
People generally (and quite rightly) hate inject, but since the two other main ways of achieving this have been mentioned, I thought I may as well throw it out there. :)
The following works with Plain Old Ruby Objects, and I suspect the following is marginally faster than using count += a.visits, plus it has an emoticon in it:
user.attachments.map(&:visits).inject(:+)

How do I collect and combine multiple arrays for calculation?

I am collecting the values for a specific column from a named_scope as follows:
a = survey_job.survey_responses.collect(&:base_pay)
This gives me a numeric array for example (1,2,3,4,5). I can then pass this array into various functions I have created to retrieve the mean, median, standard deviation of the number set. This all works fine however I now need to start combining multiple columns of data to carry out the same types of calculation.
I need to collect the details of perhaps three fields as follows:
survey_job.survey_responses.collect(&:base_pay)
survey_job.survey_responses.collect(&:bonus_pay)
survey_job.survey_responses.collect(&:overtime_pay)
This will give me 3 arrays. I then need to combine these into a single array by adding each of the matching values together - i.e. add the first result from each array, the second result from each array and so on so I have an array of the totals.
How do I create a method which will collect all of this data together and how do I call it from the view template?
Really appreciate any help on this one...
Thanks
Simon
s = survey_job.survey_responses
pay = s.collect(&:base_pay).zip(s.collect(&:bonus_pay), s.collect(&:overtime_pay))
pay.map{|i| i.compact.inject(&:+) }
Do that, but with meaningful variable names and I think it will work.
Define a normal method in app/helpers/_helper.rb and it will work in the view
Edit: now it works if they contain nil or are of different sizes (as long as the longest array is the one on which zip is called.
Here's a method that will combine an arbitrary number of arrays by taking the sum at each index. It'll allow each array to be of different length, too.
def combine(*arrays)
# Get the length of the largest array, that'll be the number of iterations needed
maxlen = arrays.map(&:length).max
out = []
maxlen.times do |i|
# Push the sum of all array elements at a given index to the result array
out.push( arrays.map{|a| a[i]}.inject(0) { |memo, value| memo += value.to_i } )
end
out
end
Then, in the controller, you could do
base_pay = survey_job.survey_responses.collect(&:base_pay)
bonus_pay = survey_job.survey_responses.collect(&:bonus_pay)
overtime_pay = survey_job.survey_responses.collect(&:overtime_pay)
#total_pay = combine(base_pay, bonus_pay, overtime_pay)
And then refer to #total_pay as needed in your view.

Resources