Plantuml several arrows between two classes - plantuml

I am trying to draw a class diagram where several relationships between two classes are defined with multiplicities.
By default, the results are horrible:
#startuml
class Movie {
genres: String[]
minutes: Integer
movie_id: String
rating: Float
title: String
type: String
votes: Integer
year: Integer
}
class Person {
birthYear: Integer
deathYear: Integer
name: String
person_id: String
}
Person "0..*" -> "1..*" Movie : acted_in
Person "0..*" -> "1..*" Movie : directed
Person "0..*" -> "1..*" Movie : produced
Person "0..*" -> "1..*" Movie : wrote
#enduml
With skinparam nodesep 200 it is a little better, but the multiplicities are still off:
I also tried with skinparam linetype ortho, which looked promising, but gets messed up as soon as I also add hide circle and hide methods…
Not to mention that eventually I would also like to add an association class, which really ruins it:
Here is the final code:
skinparam nodesep 200
skinparam linetype ortho
hide circle
hide methods
class Movie {
genres: String[]
minutes: Integer
movie_id: String
rating: Float
title: String
type: String
votes: Integer
year: Integer
}
class Person {
birthYear: Integer
deathYear: Integer
name: String
person_id: String
}
Person "0..*" -> "1..*" Movie : acted_in
Person "0..*" -> "1..*" Movie : directed
Person "0..*" -> "1..*" Movie : produced
Person "0..*" -> "1..*" Movie : wrote
(Person,Movie) .. Role
class Role {
name: String
}
#enduml
My question is: is there a way to draw this properly with PlantUML?

I don’t know how to directly reach your goal of a more legible arrow layout but you could avoid the problem by using Person as a generalisation for four sub-classes such as Actor, Director, Producer and ScreenplayWriter. Each of these would have separate arrows and it might make your diagramme also generally clearer (the latter is, of course, subject to your taste and the diagramme’s purpose)

Related

Is there a way of reading from sub arrays?

I am currently building an iOS application that stores user added products using Google Firestore. Each product that is added is concatenated into a single, user specific "products" array (as shown below - despite having separate numbers they are part of the same array but separated in the UI by Google to show each individual sub-array more clearly)
I use the following syntax to return the data from the first sub-array of the "products" field in the database
let group_array = document["product"] as? [String] ?? [""]
if (group_array.count) == 1 {
let productName1 = group_array.first ?? "No data to display :("`
self.tableViewData =
[cellData(opened: false, title: "Item 1", sectionData: [productName1])]
}
It is returned in the following format:
Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17
However I am trying to query each of the individual sections of this sub array, so for example, I can return "Product Name: 1" instead of the whole sub-array. As let productName1 = group_array.first is used to return the first sub-array, I have tried let productName1 = group_array.first[0] to try and return the first value in this sub-array however I receive the following error:
Cannot infer contextual base in reference to member 'first'
So my question is, referring to the image from my database (at the top of my question), if I wanted to just return "Product Name: 1" from the example sub-array, is this possible and if so, how would I extract it?
I would reconsider storing the products as long strings that need to be parsed out because I suspect there are more efficient, and less error-prone, patterns. However, this pattern is how JSON works so if this is how you want to organize product data, let's go with it and solve your problem.
let productRaw = "Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17"
First thing you can do is parse the string into an array of components:
let componentsRaw = productRaw.components(separatedBy: ", ")
The result:
["Product Name: 1", "Listing Price: 3", "A brief description: 4", "Product URL: 2", "Listing active until: 21/04/2021 10:22:17"]
Then you can search this array using substrings but for efficiency, let's translate it into a dictionary for easier access:
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: ": ")
product[keyVal[0]] = keyVal[1]
}
The result:
["Listing active until": "21/04/2021 10:22:17", "A brief description": "4", "Product Name": "1", "Product URL": "2", "Listing Price": "3"]
And then simply find the product by its key:
if let productName = product["Product Name"] {
print(productName)
} else {
print("not found")
}
There are lots of caveats here. The product string must always be uniform in that commas and colons must always adhere to this strict formatting. If product names have colons and commas, this will not work. You can modify this to handle those cases but it could turn into a bowl of spaghetti pretty quickly, which is also why I suggest going with a different data pattern altogether. You can also explore other methods of translating the array into a dictionary such as with reduce or grouping but there are big-O performance warnings. But this would be a good starting point if this is the road you want to go down.
All that said, if you truly want to use this data pattern, consider adding a delimiter to the product string. For example, a custom delimiter would greatly reduce the need for handling edge cases:
let productRaw = "Product Name: 1**Listing Price: 3**A brief description: 4**Product URL: 2**Listing active until: 21/04/2021 10:22:17"
With a delimiter like **, the values can contain commas without worry. But for complete safety (and efficiency), I would add a second delimiter so that values can contain commas or colons:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
With this string, you can much more safely parse the components by ** and the value from the key by $$. And it would look something like this:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
let componentsRaw = productRaw.components(separatedBy: "**")
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: "$$")
product[keyVal[0]] = keyVal[1]
}
if let productName = product["name"] {
print(productName)
} else {
print("not found")
}

Swift : multi-dimensional structure organization and init

There are two simple problems here in initializing this structure.
One is the enunumerated value TS (I get error : Cannot convert value of type 'TournNames' to expected argument type 'TournamentName')
the other is initializing an array of strings (I get the error : Cannot convert value of type '[String]' to expected argument type 'TouramentScores'
Suppose I am trying to set up a structure to model the scores of tennis players and all of their matches in each of the major tournaments (just for fun). Each tournament has a name (e.g. Wimbledon) and a series of scores for that player (for example, in the opening match, their score might be "4-6, 6-4, 7-6, 6-2")... upto 7 matches in each tournament. Each player should have an array of four tournaments (names and array of scores), and eventually there should be an array of players. I am also trying to use enums not too successfully. Ideally, if I want to find how Roger Federer did in his third match of wimbledon this year, I would access something like player.tournament.wim.Roundof32 or something roughly like that. But before I can even get to playing with that, I can't seem to init dummy data for even a single tournament.
Any ideas? I don't think this is that hard of question but I just don't know each. See "*** this line" below for two lines that are problematic
// tournament name enum
enum TournNames : String {
case wim = "Wimbledom"
case fo = "French Open"
case ao = "Australian Open"
case uo = "US Open"
}
//
struct TournamentName {
var Tname : TournNames // = .wim
}
// This is the structure for a tournament score array with some dummy values.
struct TouramentScores {
var Match1 : String = "7-6, 6-4, 3-6, 7-6"
var Match2 : String = "7-6, 6-4, 3-6, 7-6"
}
// This is one entire Tournament record for one player = tournament name + array of scores ... the next goal but not used here until I get over these hurdles
struct TournamentResult {
var TournamentName : TournNames = .wim
var Scores : TouramentScores
}
// ... finally the structure of a player ...
struct DummyTennisPlayer {
var LastName : String // last name
var FirstName : String //first name
var TN : TournamentName
var TS : TouramentScores
// var WimRes : TournamentResult // to start a single tournament
// var SeasonResults : [TournamentResult] // ultimately should be an array of 4 tournaments
}
// trying to initialize some dummy data without success after trying several things
extension DummyTennisPlayer {
static var dummyResults : [DummyTennisPlayer] {
[
DummyTennisPlayer.init(
LastName : "Federer",
FirstName: "Roger",
TN : TournNames.wim // **** this line
,
TS : ["XX", "yy"] /// *** this line
)
]
}
}
As I think you're discovering, a simple series of nested types is unlikely to cut it here. As soon as you get to entities like players, tournaments, matches and lookups like "how Roger Federer did in his third match of wimbledon this year", you've become a candidate for using a database where you can manipulate one-to-many and many-to-many relationships. I can't tell you what database to use, and anyway that's a matter of opinion; from what you've said so far, SQLite would be sufficient (and I am personally not a fan of Core Data just for this kind of thing).
I guess your code is a kind of exercise, so before you go on later to Core Data or SQLite,
extension DummyTennisPlayer {
static var dummyResults: [DummyTennisPlayer] = [
DummyTennisPlayer(LastName: "Federer", FirstName: "Roger", WimbledomResult: TournamentResult(Scores: TouramentScores()))
]
}
should answer your question.
1 - To initialize a Swift struct, use the following syntax:
MyStruct(property1: "my property1 value", property2: "my property2 value")
2 - the tournament name property in TournamentResult is already set to .wim so you just need to initialize the Scores. As your TournamentScores properties are already all set, you just need to pass an instance of TournamentScores() to TournamentResult(Scores:).
By the way, only use lowercases for the first letter of the name of your variables or struct properties: var lastName or TournamentResult(scores:).
I think you are confusing the term "multi-dimensional (array) structures" (which are just arrays nested inside other arrays, like that: [ [1, 2, 3], [2, 3, 4], [3, 4, 5]]) with the struct objects. You are probably not supposed to use structs so extensively here.
Don't hesitate to review the way you decide to use enums, structs, or arrays. Your code may work but will be difficult to read and use (example: how would you access a specific set score if you put all of the set scores in a single String? Why not use an array?)

How do I use a counter within body View to fill a list

I am new to Swift and try to get a feeling for it by trying new things. Now I got stuck with lists. I have two arrays, one is the product and the other the price for each product. I want to fill a list by using a ForEach loop. I use a HStack to have two columns.
To make it a bit more clear, here are the two arrays:
#state private var products = ["Coke", "Fanta", "Sprite", "Water]
private var prices = ["1,43$", "1,22$", "1,64$", "0,45$"]
Now this is a part of my ContentView.swift:
var body: some View {
List{
ForEach(products, id: \.self) { product in
HStack{
Text(product)
Spacer()
Text(price[products.firstIndex(of: product)])
}
}
}
So my plan here is, to fill each cell with the product name by looping through the product array. Then I want to display the corresponding price. For that I take my second Text and fill it with the price. To find the correct Index of the price array, I get the Index from my products array, since they are similar.
That was my solution, but now I am getting an Error for the products.firstIndex(of: product).
I am getting following Error:
Value of optional type 'Array.Index?' (aka 'Optional') must be unwrapped to a value of type 'Array.Index' (aka 'Int')
I am not really understanding what this Error is trying to tell me.
Can anybody help?
The correct code looks like this:
struct ContentView: View {
private var products = ["Coke", "Fanta", "Sprite", "Water"]
private var prices = ["1,43$", "1,22$", "1,64$", "0,45$"]
var body: some View {
List{
ForEach(products, id: \.self) { product in
HStack{
Text(product)
Spacer()
Text(self.prices[self.products.firstIndex(of: product)!])
}
}
}
}
}
The reason for your error is that the array could be empty at some point which leads to a crash if you try to read firstIndex in that moment.
Now you have two options:
Force-unwrap with an "!" as I did above if you are sure the array will never be cleared/ be empty.
Provide a default value with "?? value" if the value may be nil at some point.

Check if struct string array contains elements of another string array

I created an array of struct elements. These structs get to contain an array of strings. I want to check if these strings happen to be in another array of strings.
How can I do that or what tools should I look into?
I found that I can use a command called "Set", but it doesn't seem to work arrays within a struct.
import UIKit
// Define structure
struct Drink {
var name: String
var content: Array<String>
var amount: Array<Int>
var desc: String
}
// Define drinks
var mojito = Drink(name: "Mojito", content: ["Rum","Club soda"], amount: [4,20], desc: "Summer drink")
var vodkaJuice = Drink(name: "Vodka juice", content: ["Vodka","Juice"], amount: [4,20], desc: "Cheap alcohol")
var list = [mojito,vodkaJuice]
// Define what ingredients you have
var stock = ["Gin", "Vodka", "Juice", "Club soda"]
How do I make a list of drinks I can make from what I have?
Use a Set instead of an array so you can simply do a subset check:
import UIKit
// Define structure
struct drink {
var name : String
var content : Set<String> // we use a Set instead
var amount : Array<Int>
var desc : String
}
// Define drinks
var mojito = drink(name: "Mojito", content: ["Rum","Club soda"], amount: [4,20], desc: "Summer drink")
var vodkaJuice = drink(name: "Vodka juice", content: ["Vodka","Juice"], amount: [4,20], desc: "Cheap alcohol")
var list = [mojito,vodkaJuice]
// Define what ingredients you have
var stock = ["Gin", "Vodka", "Juice", "Club soda"]
// find all instances of drinks where their contents
// are subsets of what you have in stock
let drinks = list.filter { $0.content.isSubset(of: stock) }
The importance of using sets instead of "for-loops inside for-loops" is performance. Set uses an internal hash table to look up an item in an extremely fast fashion. So the overall complexity of your lookups would be O(N.logM) for N items in list and M items in stock.
If you had done it with for loops, its complexity would be O(N.M) which could take longer and consume more battery depending on the number of items you have.
That doesn't mean you should always use sets though. Sets have tradeoffs. They bring in performance but their initial construction is slower and they don't support duplicate items. Use them only in specific cases like this. Never use sets because "they are faster", use them when they solve your specific problem.
I strongly recommend skimming over those additional data structures provided by Swift's runtime so you'll know which one to use and when.

Swift - best way to handle a dictionary of dictionaries?

I have an array of people that all have a list of favourite books.
Each book has several properties, such as book title, book description, book author.
What I want to do is create a dictionary of dictionaries to be able to access a specific person, and then access each book of that person and extract the book name, book title author etc from that that.
currently what I am doing is the declaring a dictionary like so:
var dictToHoldBooKValue = [String : [String : AnyObject]]()
the first String is the key, which I will create as the name of the person, the second pard is a dictionary, which will have a key of type string (which I will set as the book property name like "book Title" or "book author" and the any object is the value of that key.
example: I modify the dictionary like so:
dictToHoldBooKValue["Mark"] = ["book Title" : (bookTitle as? String)]
However, this approach does not seem to be working because "Mark" has several books he likes and so when I print the dict count it only shows one. Also, I have no idea how I would use this dict to to get at the book author property or any other property.
Anybody know a more elegant solution?
Thanks!
Make a struct to represent a book. Make it contain all the book attributes you want to track.
struct Book {
var title: String
var author: String
var year: Int
}
Your outer dictionary will be exactly as you described.
Your inner dictionary will have its key as the name of the book and it will contain Book structs.
var dictToHoldBooKValue = [String : [String : Book]]()
dictToHoldBooKValue["Mark"] = [
"You Only Live Twice" : Book("You Only Live Twice", "Ian Flemming", 1961),
"Casino Royale" : Book("Casino Royale", "Ian Flemming", 1959)
]
Access it like this:
print(dictToHoldBooKValue["Mark"]["You Only Live Twice"].author)
This is all fine if you are loading this data from somewhere and want to show it in a specific way. If you need to query data in a more flexible way, you should consider using CoreData.
If you want to use key-value coding, you can use typealiases to simplify your code:
typealias Book = [String: AnyObject]
typealias Books = [Book]
typealias Person = [String: AnyObject]
var people = [Person]()
var mark = Person()
var marksBooks = Books()
var theLittlePrince = [
"title" : "The Little Prince",
"author": "Antoine de Saint-Exupéry"
]
marksBooks.append(theLittlePrince)
mark["books"] = marksBooks
people.append(mark)
print(people)
Prints:
[["books": (
{
author = "Antoine de Saint-Exup\U00e9ry";
title = "The Little Prince";
}
)]]

Resources