I have an Avatar struct. This struct has properties called elements that hold several parts of the face like Eyes, Mouth and so on. In the init(withGender: AvatarGender) method. I want all these elements to have their default to zero.
But instead of having zeros I get huge random Ints. I can't figure out why!
struct Avatar {
var gender: AvatarGender
var skin: AvatarSkinColor
var elements: [AvatarElement: Int]
var rawString: String {
// a computed property
}
init(withGender: AvatarGender) {
gender = withGender
skin = .White
elements = [AvatarElement: Int]()
var allElementsType = [AvatarElement]()
if gender == .Man {
allElementsType = AvatarElement.allMaleValues
} else if gender == .Woman {
allElementsType = AvatarElement.allFemaleValues
}
for element in allElementsType {
elements[element] = 0 // <= This doesn't work !!!
}
}
init(fromRawString: String) {
// Another init method that works properly
}
}
Above AvatarGender, AvatarSkinColor and AvatarElement are enums.
Related
In my code I display numbers in a list. User can choose the grouping method and the numbers will be put into sections accordingly (either groups of 5 elements or odd/even). Now, I would like to add a green row after 4*n elements where n=1,2,3,.. as seen from the UI perspective (not the perspective of the data source !). So after the fourth row, a green row should follow. After eighth row, a green row should follow etc.
In my current code this works for the groups of 5 elements but does not work for the odd/even variant. The problem seems to be in the indexes because they don't depend on the actual placement.
I know this seems to sound a bit complicated but maybe someone has an idea how to approach the problem, ideally in some scalable way so that if I add a third grouping method in the future it will all also work.
import SwiftUI
import Combine
struct ContentView: View {
#StateObject var myViewModel = MyViewModel()
var body: some View {
VStack {
Button {
myViewModel.groupStrategy.send(myViewModel.groupStrategy.value == .multiplesOfFive ? .oddEven : .multiplesOfFive)
} label: {
Text("Toggle grouping strategy")
}
List() {
ForEach(myViewModel.numOfSections, id:\.self) { sectNum in
Section("Sc \(sectNum)") {
ForEach(Array(myViewModel.nums.enumerated()), id: \.offset) { idx, element in
let _ = print("Sc \(sectNum) \(idx) \(element)")
if myViewModel.shouldSkipNumberInThisSection(number: element, sectionNumber: sectNum) {
EmptyView()
} else {
Text(element.description + " idx: " + idx.description)
if idx > 0 && (idx+1) % 4 == 0 {
Color.green
}
}
}
}
}
}
}
.padding()
}
}
class MyViewModel: ObservableObject {
enum GroupStrategy {
case multiplesOfFive
case oddEven
}
#Published var nums: [Int]
#Published var numOfSections: [Int] = []
var groupStrategy = CurrentValueSubject<GroupStrategy, Never>(.multiplesOfFive)
private var cancellables: Set<AnyCancellable> = []
func shouldSkipNumberInThisSection(number: Int, sectionNumber: Int) -> Bool {
switch groupStrategy.value {
case .multiplesOfFive:
return number >= sectionNumber * 5 || number < (sectionNumber-1) * 5
case .oddEven:
return sectionNumber == 0 ? (number % 2) == 0 : (number % 2) != 0
}
}
func shouldPutGreenRow() -> Bool {
return false
}
init() {
self.nums = []
let numbers: [Int] = Array(3...27)
self.nums = numbers
self.numOfSections = Array(1..<Int(nums.count / 5)+1)
groupStrategy.sink { strategy in
switch self.groupStrategy.value {
case .multiplesOfFive:
self.numOfSections = Array(1..<Int(self.nums.count / 5)+1)
case .oddEven:
self.numOfSections = Array(0..<2)
}
}.store(in: &cancellables)
}
}
For the multiplesOfFive group - OK:
For the odd/even group - NOT OK:
In the odd/even group, the green row should appear after numbers 9, 17, 25, 8, 16, 24. Instead, it appears only in the group of even numbers
The code is not working for the odd/even section because, for each section, you are iterating over all numbers and re-creating the indexes (or idx in your code).
So, the condition if idx > 0 && (idx+1) % 4 == 0 is good for the multiples of five, instead for odd/even it should be if idx > 0 && (idx + sectNum + 2) % 8 == 0. However, if you just add this condition to the view, it gets less scalable, as you will need to create new conditions for each different grouping.
I have thought of an alternative to your View Model using a dictionary within a dictionary, to store the sections, the indexes and the values, with no repeated entries.
With this approach, the view model gets a little bit more complicated, but the underlying data is the same, and the view is scalable: if you create a new GroupingStrategy, you don't need to change the view.
Here's the code with comments, I hope it might help you:
A bold view model:
class MyViewModel: ObservableObject {
// Create raw values and conform to CaseIterable,
// so you don't need to change the view when creating new strategies
enum GroupStrategy: String, CaseIterable {
case multiplesOfFive = "Groups of 5 items"
case oddEven = "Odd vs. even"
}
// This is the underlying data: no changes
#Published var nums = Array(3...27)
// This is where the sections are stored: a dictionary that
// includes the numbers within and their position in the final sequence.
#Published var sequence = [Int:[Int:Int]]()
// It can work with any type of underlying data, not just Int.
// For example, if the data is of type MyModel, this variable can be of type
// [Int:[Int:MyModel]]
#Published var groupStrategy = GroupStrategy.multiplesOfFive {
didSet {
// Will rebuild the dictionary every time the grouping changes
rebuildGroupStrategy()
}
}
// Extract all the sections for the current strategy
var sections: [Int] {
Array(Set(sequence.keys)).sorted()
}
// Extract all the numbers for a specific section: the key of
// the dictionary is the index in the sequence
func numsInSection(_ section: Int) -> [Int:Int] {
return sequence[section] ?? [:]
}
// Define the strategies in the Enum, then here...
var arrayOfSections: [Int] {
switch groupStrategy {
case .multiplesOfFive:
return Array(1..<Int(nums.count / 5) + 1)
case .oddEven:
return [0, 1]
}
}
// ... and here
func isIncludedInSection(number: Int, section: Int) -> Bool {
switch groupStrategy {
case .multiplesOfFive:
return number < section * 5 && number >= (section - 1) * 5
case .oddEven:
return section == 0 ? (number % 2) != 0 : (number % 2) == 0
}
}
// When you need to set a new strategy
func rebuildGroupStrategy() {
sequence = [:]
var prog = 0
// Create the sequence, which will not contain repeated elements
arrayOfSections.forEach { section in
sequence[section] = [:]
nums.forEach { number in
if isIncludedInSection(number: number, section: section) {
sequence[section]?[prog] = number
prog += 1
}
}
}
}
init() {
rebuildGroupStrategy()
}
}
A simpler view:
struct MyView: View {
#StateObject var myViewModel = MyViewModel()
var body: some View {
VStack {
// Use a Picker for scalability
Picker("Group by:", selection: $myViewModel.groupStrategy) {
ForEach(MyViewModel.GroupStrategy.allCases, id: \.self) { item in
Text(item.rawValue)
}
}
List() {
// Iterate over the sections
ForEach(myViewModel.sections, id:\.self) { sectNum in
Section("Sc \(sectNum)") {
let numbers = myViewModel.numsInSection(sectNum)
// Iterate only over the numbers of each section
ForEach(numbers.keys.sorted(), id: \.self) { index in
Text("\(numbers[index] ?? 0), Index = \(index)")
// Same condition ever: paint it green after every 4th row
if (index + 1) % 4 == 0 {
Color.green
}
}
}
}
}
}
.padding()
}
}
I'm running into a weird error with a for in loop and an array. it says
For-in loop requires '[DeepSpeechTokenMetadata]' to conform to 'Sequence'
Which doesn't make any sense... it knows it's an Array...
The for loop in question:
var transcriptCandidate = decoded.transcripts[0].tokens
var words = [String]()
var timestamps = [Int]()
var workingString = ""
var lastTimestamp = -1
for (x, token) in transcriptCandidate {
let text = token.text
let timestamp = token.startTime
if(lastTimestamp == -1){
lastTimestamp = timestamp.toInt()
}
Here's the definition of the class that contains the array I'm trying to iterate through:
public struct DeepSpeechCandidateTranscript {
/// Array of DeepSpeechTokenMetadata objects
public private(set) var tokens: [DeepSpeechTokenMetadata] = []
/** Approximated confidence value for this transcript. This corresponds to
both acoustic model and language model scores that contributed to the
creation of this transcript.
*/
let confidence: Double
internal init(fromInternal: CandidateTranscript) {
let tokensBuffer = UnsafeBufferPointer<TokenMetadata>(start: fromInternal.tokens, count: Int(fromInternal.num_tokens))
for tok in tokensBuffer {
tokens.append(DeepSpeechTokenMetadata(fromInternal: tok))
}
confidence = fromInternal.confidence
}
}
Thanks!
You can either do this, where x is the index and token is the element:
for (x, token) in transcriptCandidate.enumerated() {
}
Or this if you don't need the index:
for token in transcriptCandidate {
}
In Swift, I want to have an array of items and then to be able to access those items by name or by index. The array is a fixed size array with 3 elements.
I have the following implementation using a struct
struct Boxes {
var array: [Int]!
var a: Int {
get { return array[0] }
set { array[0] = newValue }
}
var b: Int {
get { return array[1] }
set { array[1] = newValue }
}
var c: Int {
get { return array[2] }
set { array[2] = newValue }
}
init() {
self.array = Array<Int>(repeating: 0, count: 3)
}
}
And then, use it as follows:
var box = Boxes()
box.a = 1
box.b = box.array[0]
box.array[0] = 2
box.b = box.a
Is there a simpler, more elegant/concise form to do this?
struct Boxes {
var array: [Int] {
get { return [a, b, c] }
set { (a, b, c) = (newValue[0], newValue[1], newValue[2]) }
}
var (a, b, c) = (0, 0 ,0)
}
Note that it is based on this sentence: fixed size array with 3 elements
Exact same usage:
var box = Boxes()
box.a = 1
box.b = box.array[0]
box.array[0] = 2
box.b = box.a
Extra:
You can add custom initializer if you want to init with an array:
init(array: [Int] = [0, 0, 0]) {
self.array = array
}
How about using an enum to store name-to-index associations, and add a subscript to Boxes to access the array:
enum BoxName : Int {
case a, b, c
}
struct Boxes {
var array: [Int]!
subscript(_ name: BoxName) -> Int {
get { return array[name.rawValue] }
set { array[name.rawValue] = newValue }
}
init() {
self.array = Array<Int>(repeating: 0, count: 3)
}
}
// usage:
var box = Boxes()
box[.a] = 1
box[.b] = box.array[0]
box.array[0] = 2
box[.b] = box[.a]
Now when you want to add a new element and a new name in the array, you just need to add a new enum case.
I am trying to initialize a deck of cards. I have card attributes in my card struct. My approach is to try and create an array of "enum states", then iterate through those to initialize each card. I am having trouble doing so.
Game Class
import Foundation
struct Set{
var cards = [Card]()
init(){
let properties : [Any] =
[cardShape.self, cardColor.self, cardNumber.self, cardShading.self]
for prop in properties{
// Not really sure how to iterate through this array...
// Ideally it would be something like this.
// Iterate through array, for property in array,
// card.add(property)
}
}
}
Card Class
import UIKit
import Foundation
struct Card{
var attributes : properties = properties()
mutating func addProperty(value : Property){
if value is cardShape{
attributes.shape = value as! cardShape
} else if value is cardColor{
attributes.color = value as! cardColor
} else if value is cardNumber{
attributes.number = value as! cardNumber
}else if value is cardShading{
attributes.shading = value as! cardShading
}else{
print("error")
}
}
}
protocol Property{
static var allValues : [Property] {get}
}
struct properties{
var shape : cardShape = cardShape.none
var color : cardColor = cardColor.none
var number : cardNumber = cardNumber.none
var shading : cardShading = cardShading.none
}
enum cardShape : String,Property{
case Square = "■"
case Triangle = "▲"
case Circle = "●"
case none
static var allValues : [Property]{ return [cardShape.Square,cardShape.Triangle,cardShape.Circle]}
}
enum cardColor:Property {
case Red
case Purple
case Green
case none
static var allValues : [Property] {return [cardColor.Red,cardColor.Purple,cardColor.Green]}
}
enum cardNumber : Int,Property{
case One = 1
case Two = 2
case Three = 3
case none
static var allValues : [Property] {return [cardNumber.One,cardNumber.Two,cardNumber.Three]}
}
enum cardShading: Property {
case Solid
case Striped
case Outlined
case none
static var allValues : [Property] {return [cardShading.Solid,cardShading.Striped,cardShading.Outlined]}
}
So to summarize, my main issue is trying to create an array of enums, then cycling through the enum states to initialize a card with specific attribute states.
You will want to make sure you cover all combinations of attributes and make sure each card has one of each of the four types of attributes. I would suggest using nested loops:
for shape in cardShape.allValues {
for color in cardColor.allValues {
for number in cardNumber.allValues {
for shading in cardShading.allValues {
var card = Card()
card.addProperty(shape)
card.addProperty(color)
card.addProperty(number)
card.addProperty(shading)
cards.append(card)
}
}
}
}
I believe your Card struct is a bit too complex. If you change your representation, it will be easier to create the cards.
Have your card represent the different attributes as their own property:
struct Card {
let shape: CardShape
let color: CardColor
let number: CardNumber
let shading: CardShading
}
Then use nested loops to create your cards:
for shape in CardShape.allValues {
for color in CardColor.allValues {
for number in CardNumber.allValues {
for shading in CardShading.allValues {
cards.append(Card(shape: shape, color: color, number: number, shading: shading))
}
}
}
}
Notes:
Your enums should start with uppercase characters, and your enum values should start with lowercase characters.
Using separate properties for each attribute will make it much easier to check for matching attributes between cards.
You get an initializer by default that initializes all properties. By initializing them with nested loops, you will be able to create all possible cards.
Change your allValues properties to return arrays of the specific attribute type (for example [CardShape]).
Alternate Answer:
Instead of using nested arrays, you could use MartinR's combinations function to create the list of combinations of the properties. Adding an init to Card that takes [Property], you can create the cards in two lines of code:
struct Card {
var shape = CardShape.none
var color = CardColor.none
var number = CardNumber.none
var shading = CardShading.none
init(properties: [Property]) {
for property in properties {
switch property {
case let shape as CardShape:
self.shape = shape
case let color as CardColor:
self.color = color
case let number as CardNumber:
self.number = number
case let shading as CardShading:
self.shading = shading
default:
break
}
}
}
}
// https://stackoverflow.com/a/45136672/1630618
func combinations<T>(options: [[T]]) -> AnySequence<[T]> {
guard let lastOption = options.last else {
return AnySequence(CollectionOfOne([]))
}
let headCombinations = combinations(options: Array(options.dropLast()))
return AnySequence(headCombinations.lazy.flatMap { head in
lastOption.lazy.map { head + [$0] }
})
}
struct SetGame {
let cards: [Card]
init(){
let properties: [Property.Type] = [CardShape.self, CardColor.self, CardNumber.self, CardShading.self]
cards = combinations(options: properties.map { $0.allValues }).map(Card.init)
}
}
How this works:
properties.map { $0.allValues } calls allValues on each item of the properties array creating an [[Property]] with [[.square, .triangle, .circle], [.red, .purple, .green], [.one, .two, .three], [.solid, .striped, .outlined]]
This is passed to combinations which creates a sequence with all 81 combinations of these properties: [[.square, .red, .one, .solid], ..., [.circle, .green, .three, .outlined]].
map is run on this sequence to call Card.init with each combination which results in an [Card] with 81 cards.
I have a class defined like this:
class someClass {
var isCompleted = false
}
how to sort the list of the someClass? if want to move the completed items to the top of the list.
You can sort according to the boolean property by converting
the values to Int:
let arrayOfClasses = ... // array of classes
let sortedArrayOfClasses = sorted(arrayOfClasses) {
Int($0.isCompleted) > Int($1.isCompleted)
}
Or with "in-situ" sort:
var arrayOfClasses = ... // array of classes
sort(&arrayOfClasses) {
Int($0.isCompleted) > Int($1.isCompleted)
}
parameter closure for sort or sorted returns true iff the first parameter must be ordered before the second, otherwise false.
So you can:
let array = [ ... ]
let sortedArray = array.sorted { a, b in
a.isCompleted && !b.isCompleted
}
var yourArrayOfBooleans:[SomeClass] = ...
var sortedArrayOfBooleans:[SomeClass] = []
var tmpArray:[SomeClass] = []
for ar in yourArrayOfBooleans
{
if ar.isCompleted
{
sortedArrayOfBooleans.append(ar)
}
else
{
tmpArray.append(ar)
}
}
sortedArrayOfBooleans += tmpArray