I am trying to iterate through a Core Data model object and make calculations as follow. I can't figure out the for - in loops tho. Here every value is multiplied by every amount so the append and total are wrong. I only need each value to be multiplied to it's corresponding amount (for example bitcoin with 1.00000000).
func updateWalletLabel() {
var values : [Double] = []
guard let items : [CryptosMO] = CoreDataHandler.fetchObject() else { return }
let codes = items.map { $0.code! }
let amounts = items.map { $0.amount! }
print(codes) // ["bitcoin", "litecoin"]
print(amounts) // ["1.00000000", "2.00000000"]
// please note these values are in correct order (x1 bitcoin, x2 litecoin)
for code in codes {
for amount in amounts {
let convertedAmount = Double(amount)!
let crypto = code
guard let price = CryptoInfo.cryptoPriceDic[crypto] else { return }
let calculation = price * convertedAmount
values.append(calculation)
}
}
let total = values.reduce(0.0, { $0 + Double($1) } )
print("VALUES", values) // [7460.22, 14920.44, 142.68, 285.36] (need [7460.22, 285.36])
print("TOTAL:", total) // 22808.7 (need 7745.58)
}
How can I modify my for-in loops here so the calculations are only happening once for each array item?
Thanks!
When you have two arrays of the same length and in the same order, you can use Swift's zip function to combine the two into an array of tuples. In that case, your loop would change to
for (code, amount) in zip(codes, amounts) {
// Your calculation
}
See also the documentation
Related
var brachNames = ["AP","AP","AP","AS","AS","AS","BR","BR","BR"]
var overAllTaget = ["84","84","84","84","84","84","84","84","84"]
var overAllSold = ["135","135","135","135","135","135","135","135","135"]
extension Array where Element : Hashable {
func removeDups() -> [Element] {
var uniquedElements = Set<Element>()
return filter { uniquedElements.insert($0).inserted }
}
}
I want this type of output - [AP,84,135,AS,84,135,BR,84,135]
Since you've 3 different Arrays, you need to first combine these to get an Array of Arrays using zip(_:_:) and map(_:), i.e.
var arr = zip(brachNames, zip(overAllTaget, overAllSold)).map { [$0.0, $0.1.0, $0.1.1] }
Now, use Set to filter out the duplicates. Then use flatMap(_:) to get a single result Array, i.e.
let result = Array(Set(arr)).flatMap{ $0 } //["AP", "84", "135", "AS", "84", "135", "BR", "84", "135"]
Note: Set is unordered. So, the sequence of the result might change.
Another approach would be to create a struct with the required fields (brachName, overallTarget, overallSold), comply to Hashable and apply something like this:
https://www.hackingwithswift.com/example-code/language/how-to-remove-duplicate-items-from-an-array
This way you could keep the order, if that's important.
It would be much better to work with an array of a custom type instead of 3 different arrays of data to make the code clearer and to avoid simple mistakes when accessing the data. Below is an example of such solution using a struct to hold the data
struct BranchData: Hashable {
let branchName: String
let overallTarget: Int
let overallSold: Int
}
var set = Set<BranchData>()
for (index, branch) in brachNames.enumerated() {
guard index < overAllSold.count, index < overAllTaget.count else {
break
}
set.insert(BranchData(branchName: branch, overallTarget: Int(overAllTaget[index]) ?? 0, overallSold: Int(overAllSold[index]) ?? 0))
}
To support the specific output with all values in an array we can add a computed property
extension BranchData {
var propertyArray: [String] {
[branchName, String(overallTarget), String(overallSold)]
}
}
let output = set.flatMap { $0.propertyArray }
or a more direct approach
let output = set.flatMap { [$0.branchName, $0.overallTarget, $0.overallSold] }
So I recently learned that you can use objects as a data type in an array.
So what I need to do is loop through this array, and add each age, then divide age sum by the length of the array to get an average age of all people. Thing is I don't understand how you would call a given key.
I understand that to call the first object, you would write "people[0]" but I have no idea how to get the key from that object and pass that to a function.
let people=[ {name:'Don', Age:23},
{name:'Ron', Age:21},
{name:'Juan', Age:20}
]
// return /people.length
If you want to add the age and then divide by length of array to get avg value, in this case you can simply used Array.map()
let people=[
{name:'Don', Age:23},
{name:'Ron', Age:21},
{name:'Juan', Age:20}]
//Sum
let sum = 0;
//Map
people.map((value)=> {
sum += value.Age;
})
//Average Result
let avg_Age = sum / people.length
console.log('Average Age', avg_Age)
hope this may help.
let people=[ {name:'Don', Age:23},
{name:'Ron', Age:21},
{name:'Juan', Age:20}
];
var totalPeople = people.length;
var totalAge = 0
var averageAge = 0;
for (var key in people) {
// skip loop if the property is from prototype
if (!people.hasOwnProperty(key)) continue;
var obj = people[key];
for (var prop in obj) {
// skip loop if the property is from prototype
if (!obj.hasOwnProperty(prop)) continue;
if(prop == 'Age') {
totalAge += obj[prop];
}
}
}
averageAge = totalAge / totalPeople;
alert(averageAge);
You can use below code to traverse through the object and access keys:
let sum = 0;
for(let i=1;i<=people.length;i++) {
sum = sum + people[i-1].Age;
}
console.log(sum/people.length);
//output
21.333333333333332
You can also use foreach method to traverse the array.
https://www.w3schools.com/jsref/jsref_obj_array.asp
Go through this link here you can find all the methods for array/object manipulation.
To get the keys call Object.keys(people[0])
Please take a look Object.keys method
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
I want to use arc4random to generate array of 30 different numbers, so that there is no repeating in it. How can I do it?
Update:
Thanks to #Sulthan for his elegant thought (commented on Gereon's answer):
anyway, depending on the difference between limit and the number of
generated elements this can have a terrible performance. The problem
is the case when limit is close to the number of generated elements.
Then it would be much better to take 1..<limit and shuffle it.
Which means that there is no even need to generate random Int. The simplest way I could think of is to do it as (Swift 4.2):
let randoms = Array(0..<30).shuffled()
therefore randoms is an array of Ints, contains 30 unique values from 0 to 29.
Less Than Swift 4.2:
However, if you are not using Swift 4.2, I would recommend to check this great answer for getting shuffled collection.
Ignored Solution:
You could achieve it like this:
var uniques = Set<UInt32>()
func generateUniqueUInt32() -> UInt32 {
let random = arc4random_uniform(31)
if !uniques.contains(random) {
uniques.insert(random)
return random
} else {
return generateUniqueUInt32()
}
}
let randomArray = Array(0..<30).map { _ in Int(generateUniqueUInt32()) }
Therefore randomArray is an array of Ints, contains 30 unique values from 0 to 29.
Swift 4.2:
You should replace:
let random = arc4random_uniform(31)
with:
let random = Int.random(in: 1..<30)
which means that generateUniqueUInt32 should return -directly- Int (and renamed to generateUniqueInt):
func generateUniqueInt() -> Int {
let random = Int.random(in: 1..<30)
if !uniques.contains(random) {
uniques.insert(random)
return random
} else {
return generateUniqueInt()
}
}
let randomArray = Array(0..<30).map { _ in generateUniqueInt() }
It may be a pretty heavy action but you can do like that:
var randomNumbers: [Int] = []
while randomNumbers.count != 30{
let number = Int(arc4random_uniform(1000) + 1)
if randomNumbers.contains(number) {
print("Already Exits")
}else{
randomNumbers.append(number)
}
}
replace "1000" for a range of number that you need. That function generated 30 different number between 0...1000
Use a Set to store the generated numbers so far
func thirtyUniqueRandomNumbers(_ limit: Int) -> [Int] {
var randoms = Set<Int>()
repeat {
let rnd = Int(arc4random_uniform(UInt32(limit)))
if !randoms.contains(rnd) {
randoms.insert(rnd)
}
} while randoms.count < 30
return randoms.map { $0 }
}
Here is a simple solution. Start a while loop. Within this loop generate a random number (between 0 and 1000). And then append the number into your array if the array doesn't contains the number.
func generateArrayOfRandomNumbers() -> [Int] {
var array: [Int] = []
while array.count < 30 {
let randomInt = Int.random(in: 0...1000)
guard !array.contains(randomInt) else { continue }
array.append(randomInt)
}
return array
}
Func to see if two arrays have some same values:
func hasAllSame(largeCombinedArry:[Int], wantToKeep: [Int])->Bool{
var howManySame = [Int]()
for intElement in largeCombinedArry{
if wantToKeep.contains(intElement){
howManySame.append(intElement)
}
}
howManySame.sort()
if wantToKeep == howManySame{
print("These are the same!")
return true
}
else{
print("These are NOT same!")
return false
}
}
Array of tuples declared like this:
var TuplesArry:[(score: Double, value: [Int])] = []
Array filled thusly:
for (arry1, score) in zip(arryOfArrays, AllScores) {
let calculatedDiff = otherValue - score
TuplesArry.append((score: calculatedDiff, value: arry1))
}
var arrayForComparison = [8,9,7,6]
arrayForComparison.sort()
Error occurs here after several iteration at function call hasAllSame()
for i in 0..<TuplesArry.count{
if hasAllSame(largeCombinedArry: TuplesArry[i].value, wantToKeep:
arrayForComparison){
//do nothing
}
else{
/*
want to remove tuple that does not have all elements that are
in arrayForComparison
*/
TuplesArry.remove(at: i)
}
}
This code seems to be working but it seems like tuplesArry.count continues to decrease and iterator i continues to increase until error occurs "fatal index out of range"
My goal is to remove a tuple from the array of tuples if it's value does not meet criteria.
I've also tried something like:
for tuple in TuplesArry{
if hasAllSame(largeCombinedArry: tuple.value, wantToKeep:
arrayForComparison){
//do nothing
}
else{
//this does NOT work
let index = TuplesArry.index(of:tuple)
TuplesArry.remove(at: index)
}
}
The immediate issue is that you need to iterate in reverse to avoid the "index out of range" issue.
The much simpler solution is to use filter on your array. Then you entire for loop can be replaced with:
TouplesArry = TouplesArry.filter { hasAllSame(largeCombinedArry: $0.value, wantToKeep: arrayForComparison) }
I have the following swift array:
var winSuitArray = [cardSuit1, cardSuit2, cardSuit3, cardSuit4, cardSuit5, cardSuit6, cardSuit7]
cardSuit1, cardSuit2 and so on, are variables that will equal strings like "clubs" or "hearts". What I want to do is loop through this array, and if the loop finds 4 identical objects whose indexes are consecutive, set the winSuitStatus bool to true.
For example, if the array looks like this:
["hearts", "clubs", "clubs", "clubs", "clubs", "diamonds", "spades"]
I want to loop through it like so:
for card in winSuitArray {
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
Is this possible to do?
To tell the truth, I'd probably do something similar to #KnightOfDragon's answer. There's nothing wrong with that approach. But this problem opens up some opportunities to build some much more reusable code at little cost, so it seems worth a little trouble to do that.
The basic problem is that you want to create a sliding window of a given size over the list, and then you want to know if any of the windows contain only a single value. So the first issue to to create these windows. We can do that very generally for all collections, and we can do it lazily so we don't have to compute all the windows (we might find our answer at the start of the list).
extension Collection {
func slidingWindow(length: Int) -> AnyRandomAccessCollection<SubSequence> {
guard length <= count else { return AnyRandomAccessCollection([]) }
let windows = sequence(first: (startIndex, index(startIndex, offsetBy: length)),
next: { (start, end) in
let nextStart = self.index(after: start)
let nextEnd = self.index(after: end)
guard nextEnd <= self.endIndex else { return nil }
return (nextStart, nextEnd)
})
return AnyRandomAccessCollection(
windows.lazy.map{ (start, end) in self[start..<end] }
)
}
}
The use of AnyRandomAccessCollection here is to just hide the lazy implementation detail. Otherwise we'd have to return a LazyMapSequence<UnfoldSequence<(Index, Index), ((Index, Index)?, Bool)>, SubSequence>, which would be kind of crazy.
Now are next question is whether all the elements in a window are equal. We can do that for any kind of Equatable sequence:
extension Sequence where Iterator.Element: Equatable {
func allEqual() -> Bool {
var g = makeIterator()
guard let f = g.next() else { return true }
return !contains { $0 != f }
}
}
And with those two pieces, we can just ask our question. In the windows of length 4, are there any runs that area all equal?
let didWin = suits.slidingWindow(length: 4).contains{ $0.allEqual() }
Or we could go a little different way, and create a SlidingWindowSequence that we could iterate over. The logic here is basically the same. This just wraps up the windowing into a specific type rather than a AnyRandomAccessCollection. This may be overkill for this problem, but it demonstrates another powerful pattern.
public struct SlidingWindowSequence<Base: Collection>: Sequence, IteratorProtocol {
let base: Base
let windowSize: Base.IndexDistance
private var windowStart: Base.Index
public init(_ base: Base, windowSize: Base.IndexDistance) {
self.base = base
self.windowSize = windowSize
self.windowStart = base.startIndex
}
public mutating func next() -> Base.SubSequence? {
if base.distance(from: windowStart, to: base.endIndex) < windowSize {
return nil
}
let window = base[windowStart..<base.index(windowStart, offsetBy: windowSize)]
windowStart = base.index(after: windowStart)
return window
}
}
let didWin = SlidingWindowSequence(suits, windowSize: 4).contains{ $0.allEqual() }
var suit = ""
var count = 1
for card in winSuitArray {
if(suit == card)
{
count++
}
else
{
count = 1
suit = card
}
if(count == 4)
{
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
}
You can use a counter variable to do this, initialized to 1.
for each value in array:
if value equals previous value
increment counter
else
counter = 1
if counter >= 4
set winCounter to true