La máquina expendedora
Vamos a trabajar con un modelo para los items de nuestra máquina expendedora, que nos han llegado como archivo. El modelo es el siguiente:
[<Measure>]
type oz
type FoodType =
FoodType of string
type FoodProduct =
| Eat of FoodType
| Drink of FoodType
type Brand =
| Brand of string
type FoodMachineItem =
{
Brand: Brand
ProductType: FoodProduct
Price: float
Weight: float<oz>
}
Algunas funciones de conveniencia:
let prettyPrint item =
let (Brand nameBrand) = item.Brand
let foodType =
match item.ProductType with
| Eat (FoodType t) -> t
| Drink (FoodType t) -> t
printfn $"{foodType} ({nameBrand}, {item.Weight} oz) costs {item.Price}$"
let readFile(fileName: string) =
let lines = File.ReadAllLines(fileName)
lines
Ahora podemos leer los datos:
let foodAndDrinkData = "../data/FoodAndDrinks.csv"
let products = readFile(foodAndDrinkData)
products.GetType()
printfn "%A" products[0]
printfn "%A" products[1]
"Food,Brand,Weight/Size,Price,Drinks,Brand,Weight/Size,Price"
"Chips,Lay's,1 oz,$1.00,Bottled Water,Aquafina,16 oz,$1.50"
products[0..20]
|> Seq.iteri (fun i s -> printfn $"{i}: {s}")
0: Food,Brand,Weight/Size,Price,Drinks,Brand,Weight/Size,Price
1: Chips,Lay's,1 oz,$1.00,Bottled Water,Aquafina,16 oz,$1.50
2: Chips,Doritos,2 oz,$2.00,Bottled Water,Dasani,20 oz,$2.00
3: Candy Bars,Snickers,1.5 oz,$1.25,Soda,Coca-Cola,12 oz,$1.50
4: Candy Bars,Kit Kat,1.5 oz,$1.25,Soda,Pepsi,20 oz,$2.00
5: Granola Bars,Nature Valley,1.5 oz,$1.50,Sports Drinks,Gatorade,20 oz,$2.50
6: Cookies,Chips Ahoy,1.5 oz,$1.25,Juice Boxes,Capri Sun,6 oz,$1.00
7: Crackers,Cheez-Its,2 oz,$1.50,Energy Drinks,Red Bull,8.4 oz,$3.00
8: Nuts,Planters,1 oz,$1.50,Iced Tea,Lipton,16 oz,$1.50
9: Popcorn,Pop-Secret,2 oz,$2.00,Lemonade,Minute Maid,16 oz,$1.50
10: Beef Jerky,Jack Link's,1 oz,$3.50,Iced Coffee,Starbucks,12 oz,$2.50
11: Cup Noodles,Maruchan,3 oz,$1.50,Chocolate,Swiss Miss,8 oz,$2.00
12: Pretzels,Rold Gold,1 oz,$1.00,Milk,Nesquik,8 oz,$1.00
13: Trail Mix,Planters,1 oz,$1.50,Chocolate Milk,Yoo-hoo,8 oz,$1.00
14: Rice Krispie Treats,Kellogg's,1.5 oz,$1.25,Fruit Smoothies,Naked Juice,16 oz,$4.00
15: Protein Bars,Quest,2.1 oz,$3.00,,
Qué tenemos que hacer? Veamos un poco cómo nos llega el dato:
let items = products[2].Split(',')
printfn "%A" items
[|"Chips"; "Doritos"; "2 oz"; "$2.00"; "Bottled Water"; "Dasani"; "20 oz";
"$2.00"|]
Esto es una secuencia (seq
) que tiene dos items. Podemos hacer
- una función que dado este string nos devuelva dos datos
FoodMachineItem
- una función que transforme el peso en un tipo
float<oz>
- una función que transforme el precio en un tipo
float
let price (s:string) =
s.Split('$')[1] |> float
price "$2.00"
2
let weight (s:string) =
s.Split('o')[0]
|> float
|> (fun v -> v*1.0<oz>)
printfn $"""{weight "20 oz"}"""
20
let createItems (s: string) =
let itemList =
s.Split(',')
|> Seq.toList
printfn "%A" itemList
let eat =
{
Brand = Brand (itemList[1])
ProductType = Eat (FoodType itemList[0])
Price = price itemList[3]
Weight = weight itemList[2]
}
let drink =
{
Brand = Brand (itemList[5])
ProductType = Eat (FoodType itemList[4])
Price = price itemList[7]
Weight = weight itemList[6]
}
(eat,drink)
let items = products[2].Split(',')
printfn "%A" items
createItems products[2]
[|"Chips"; "Doritos"; "2 oz"; "$2.00"; "Bottled Water"; "Dasani"; "20 oz";
"$2.00"|]
["Chips"; "Doritos"; "2 oz"; "$2.00"; "Bottled Water"; "Dasani"; "20 oz";
"$2.00"]
({ Brand = Brand "Doritos"\n ProductType = Eat (FoodType "Chips")\n Price = 2.0\n Weight = 2.0 }, { Brand = Brand "Dasani"\n ProductType = Eat (FoodType "Bottled Water")\n Price = 2.0\n Weight = 20.0 })
Item1 |
|
Brand |
|
Item | Doritos |
Eat (FoodType "Chips")
Item |
|
Item | Chips |
2
2
{ Brand = Brand "Dasani"\n ProductType = Eat (FoodType "Bottled Water")\n Price = 2.0\n Weight = 20.0 }
Brand |
|
Item | Dasani |
Eat (FoodType "Bottled Water")
Item |
|
Item | Bottled Water |
2
20
products[1..]
|> Seq.map (fun p -> createItems p)
["Chips"; "Lay's"; "1 oz"; "$1.00"; "Bottled Water"; "Aquafina"; "16 oz";
"$1.50"]
["Chips"; "Doritos"; "2 oz"; "$2.00"; "Bottled Water"; "Dasani"; "20 oz";
"$2.00"]
["Candy Bars"; "Snickers"; "1.5 oz"; "$1.25"; "Soda"; "Coca-Cola"; "12 oz";
"$1.50"]
["Candy Bars"; "Kit Kat"; "1.5 oz"; "$1.25"; "Soda"; "Pepsi"; "20 oz"; "$2.00"]
["Granola Bars"; "Nature Valley"; "1.5 oz"; "$1.50"; "Sports Drinks"; "Gatorade";
"20 oz"; "$2.50"]
["Cookies"; "Chips Ahoy"; "1.5 oz"; "$1.25"; "Juice Boxes"; "Capri Sun"; "6 oz";
"$1.00"]
["Crackers"; "Cheez-Its"; "2 oz"; "$1.50"; "Energy Drinks"; "Red Bull"; "8.4 oz";
"$3.00"]
["Nuts"; "Planters"; "1 oz"; "$1.50"; "Iced Tea"; "Lipton"; "16 oz"; "$1.50"]
["Popcorn"; "Pop-Secret"; "2 oz"; "$2.00"; "Lemonade"; "Minute Maid"; "16 oz";
"$1.50"]
["Beef Jerky"; "Jack Link's"; "1 oz"; "$3.50"; "Iced Coffee"; "Starbucks";
"12 oz"; "$2.50"]
["Cup Noodles"; "Maruchan"; "3 oz"; "$1.50"; "Chocolate"; "Swiss Miss"; "8 oz";
"$2.00"]
["Pretzels"; "Rold Gold"; "1 oz"; "$1.00"; "Milk"; "Nesquik"; "8 oz"; "$1.00"]
["Trail Mix"; "Planters"; "1 oz"; "$1.50"; "Chocolate Milk"; "Yoo-hoo"; "8 oz";
"$1.00"]
["Rice Krispie Treats"; "Kellogg's"; "1.5 oz"; "$1.25"; "Fruit Smoothies";
"Naked Juice"; "16 oz"; "$4.00"]
["Protein Bars"; "Quest"; "2.1 oz"; "$3.00"; ""; ""]
System.ArgumentException: The index was outside the range of elements in the list. (Parameter 'n')
at Microsoft.FSharp.Collections.PrivateListHelpers.nth[a](FSharpList`1 l, Int32 n) in D:\a\_work\1\s\src\FSharp.Core\prim-types.fs:line 4026
at FSI_0015.createItems(String s)
at FSI_0017.it@2-1.Invoke(String p)
at Microsoft.FSharp.Collections.Internal.IEnumerator.map@99.DoMoveNext(b& curr) in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 105
at Microsoft.FSharp.Collections.Internal.IEnumerator.MapEnumerator`1.System.Collections.IEnumerator.MoveNext() in D:\a\_work\1\s\src\FSharp.Core\seq.fs:line 88
at System.Linq.Enumerable.SelectIterator[TSource,TResult](IEnumerable`1 source, Func`3 selector)+MoveNext()
at Microsoft.DotNet.Interactive.Formatting.EnumerableExtensions.TakeAndCountRemaining[T](IEnumerable`1 source, Int32 count, Boolean forceCountRemainder) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\EnumerableExtensions.cs:line 23
at Microsoft.DotNet.Interactive.Formatting.HtmlFormatter`1.<>c__DisplayClass7_0.<CreateTableFormatterForAnyEnumerable>g__BuildTable|4(T source, FormatContext context, Boolean summarize) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\HtmlFormatter{T}.cs:line 99
at Microsoft.DotNet.Interactive.Formatting.HtmlFormatter`1.<>c__DisplayClass7_0.<CreateTableFormatterForAnyEnumerable>b__3(T value, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\HtmlFormatter{T}.cs:line 83
at Microsoft.DotNet.Interactive.Formatting.HtmlFormatter`1.Format(T value, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\HtmlFormatter{T}.cs:line 53
at Microsoft.DotNet.Interactive.Formatting.TypeFormatter`1.Microsoft.DotNet.Interactive.Formatting.ITypeFormatter.Format(Object instance, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\TypeFormatter{T}.cs:line 23
at Microsoft.DotNet.Interactive.Formatting.HtmlFormatter.<>c.<.cctor>b__0_12(IEnumerable value, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\HtmlFormatter.cs:line 198
at Microsoft.DotNet.Interactive.Formatting.HtmlFormatter`1.Format(T value, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\HtmlFormatter{T}.cs:line 53
at Microsoft.DotNet.Interactive.Formatting.TypeFormatter`1.Microsoft.DotNet.Interactive.Formatting.ITypeFormatter.Format(Object instance, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\TypeFormatter{T}.cs:line 23
at Microsoft.DotNet.Interactive.Formatting.Formatter.<>c__DisplayClass53_0.<TryInferPreferredFormatter>b__4(Object value, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\Formatter.cs:line 702
at Microsoft.DotNet.Interactive.Formatting.AnonymousTypeFormatter`1.Format(T instance, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\AnonymousTypeFormatter{T}.cs:line 30
at Microsoft.DotNet.Interactive.Formatting.TypeFormatter`1.Microsoft.DotNet.Interactive.Formatting.ITypeFormatter.Format(Object instance, FormatContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\TypeFormatter{T}.cs:line 23
at Microsoft.DotNet.Interactive.Formatting.Formatter`1.FormatTo(T obj, FormatContext context, String mimeType) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\Formatter{T}.cs:line 79
at Microsoft.DotNet.Interactive.Formatting.Formatter.FormatTo[T](T obj, FormatContext context, String mimeType) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\Formatter.cs:line 308
at Microsoft.DotNet.Interactive.Formatting.Formatter.ToDisplayString(Object obj, String mimeType) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.Formatting\Formatter.cs:line 265
at Microsoft.DotNet.Interactive.FormattedValue.<>c__DisplayClass7_0.<FromObject>b__0(String mimeType) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive\FormattedValue.cs:line 37
at System.Linq.Enumerable.SelectArrayIterator`2.ToArray()
at Microsoft.DotNet.Interactive.FormattedValue.FromObject(Object value, String[] mimeTypes) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive\FormattedValue.cs:line 36
at <StartupCode$Microsoft-DotNet-Interactive-FSharp>.$FSharpKernel.clo@196-5.MoveNext() in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive.FSharp\FSharpKernel.fs:line 223
at Microsoft.DotNet.Interactive.Kernel.HandleAsync(KernelCommand command, KernelInvocationContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive\Kernel.cs:line 325
at Microsoft.DotNet.Interactive.KernelCommandPipeline.<BuildPipeline>b__6_0(KernelCommand command, KernelInvocationContext context, KernelPipelineContinuation _) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive\KernelCommandPipeline.cs:line 60
at Microsoft.DotNet.Interactive.KernelCommandPipeline.SendAsync(KernelCommand command, KernelInvocationContext context) in D:\a\_work\1\s\src\Microsoft.DotNet.Interactive\KernelCommandPipeline.cs:line 41
Tratando con datos que no están
La forma que F# nos ofrece para tratar con datos que no existen es el tipo option
:
type Option<'a> =
| Some of 'a // valid value
| None // missing
let emptyStringAsOption (s:string) =
if (String.IsNullOrEmpty(s)) then
None
else
Some s
printfn "%A" (emptyStringAsOption "hola")
printfn "%A" (emptyStringAsOption "")
Some "hola"
None
El tipo option
tiene métodos similares a los del tipo Result
let tryParseInt (s:string) =
if (s |> Seq.forall Char.IsDigit) then
Some (int s)
else
None
printfn "%A" (tryParseInt "43")
printfn "%A" (tryParseInt "Messi")
Some 43
None
let divisionBy2 s =
s
|> tryParseInt
|> Option.map (fun n -> n/2)
printfn "%A" (divisionBy2 "43")
printfn "%A" (divisionBy2 "Messi")
Some 21
None
Luego de esta introducción, volvamos a mirar nuestros datos. El problema lo tenemos en la última línea:
products[15]
Protein Bars,Quest,2.1 oz,$3.00,,
products[15].Split(',')
[ Protein Bars, Quest, 2.1 oz, $3.00, , ]
let createItemsRobust (s: string) =
let itemList =
s.Split(',')
|> Seq.toList
// printfn "%A" itemList
let eat =
{
Brand = Brand (itemList[1])
ProductType = Eat (FoodType itemList[0])
Price = price itemList[3]
Weight = weight itemList[2]
}
let drink =
if (String.IsNullOrEmpty(itemList[4])) then
None
else
{
Brand = Brand (itemList[5])
ProductType = Eat (FoodType itemList[4])
Price = price itemList[7]
Weight = weight itemList[6]
} |> Some
(eat,drink)
let mixedSeq =
products[1..]
|> Seq.map (fun p -> createItemsRobust p)
mixedSeq
|> Seq.iter (fun i -> printfn "%A" i)
({ Brand = Brand "Lay's"
ProductType = Eat (FoodType "Chips")
Price = 1.0
Weight = 1.0 }, Some { Brand = Brand "Aquafina"
ProductType = Eat (FoodType "Bottled Water")
Price = 1.5
Weight = 16.0 })
({ Brand = Brand "Doritos"
ProductType = Eat (FoodType "Chips")
Price = 2.0
Weight = 2.0 }, Some { Brand = Brand "Dasani"
ProductType = Eat (FoodType "Bottled Water")
Price = 2.0
Weight = 20.0 })
({ Brand = Brand "Snickers"
ProductType = Eat (FoodType "Candy Bars")
Price = 1.25
Weight = 1.5 }, Some { Brand = Brand "Coca-Cola"
ProductType = Eat (FoodType "Soda")
Price = 1.5
Weight = 12.0 })
({ Brand = Brand "Kit Kat"
ProductType = Eat (FoodType "Candy Bars")
Price = 1.25
Weight = 1.5 }, Some { Brand = Brand "Pepsi"
ProductType = Eat (FoodType "Soda")
Price = 2.0
Weight = 20.0 })
({ Brand = Brand "Nature Valley"
ProductType = Eat (FoodType "Granola Bars")
Price = 1.5
Weight = 1.5 }, Some { Brand = Brand "Gatorade"
ProductType = Eat (FoodType "Sports Drinks")
Price = 2.5
Weight = 20.0 })
({ Brand = Brand "Chips Ahoy"
ProductType = Eat (FoodType "Cookies")
Price = 1.25
Weight = 1.5 }, Some { Brand = Brand "Capri Sun"
ProductType = Eat (FoodType "Juice Boxes")
Price = 1.0
Weight = 6.0 })
({ Brand = Brand "Cheez-Its"
ProductType = Eat (FoodType "Crackers")
Price = 1.5
Weight = 2.0 }, Some { Brand = Brand "Red Bull"
ProductType = Eat (FoodType "Energy Drinks")
Price = 3.0
Weight = 8.4 })
({ Brand = Brand "Planters"
ProductType = Eat (FoodType "Nuts")
Price = 1.5
Weight = 1.0 }, Some { Brand = Brand "Lipton"
ProductType = Eat (FoodType "Iced Tea")
Price = 1.5
Weight = 16.0 })
({ Brand = Brand "Pop-Secret"
ProductType = Eat (FoodType "Popcorn")
Price = 2.0
Weight = 2.0 }, Some { Brand = Brand "Minute Maid"
ProductType = Eat (FoodType "Lemonade")
Price = 1.5
Weight = 16.0 })
({ Brand = Brand "Jack Link's"
ProductType = Eat (FoodType "Beef Jerky")
Price = 3.5
Weight = 1.0 }, Some { Brand = Brand "Starbucks"
ProductType = Eat (FoodType "Iced Coffee")
Price = 2.5
Weight = 12.0 })
({ Brand = Brand "Maruchan"
ProductType = Eat (FoodType "Cup Noodles")
Price = 1.5
Weight = 3.0 }, Some { Brand = Brand "Swiss Miss"
ProductType = Eat (FoodType "Chocolate")
Price = 2.0
Weight = 8.0 })
({ Brand = Brand "Rold Gold"
ProductType = Eat (FoodType "Pretzels")
Price = 1.0
Weight = 1.0 }, Some { Brand = Brand "Nesquik"
ProductType = Eat (FoodType "Milk")
Price = 1.0
Weight = 8.0 })
({ Brand = Brand "Planters"
ProductType = Eat (FoodType "Trail Mix")
Price = 1.5
Weight = 1.0 }, Some { Brand = Brand "Yoo-hoo"
ProductType = Eat (FoodType "Chocolate Milk")
Price = 1.0
Weight = 8.0 })
({ Brand = Brand "Kellogg's"
ProductType = Eat (FoodType "Rice Krispie Treats")
Price = 1.25
Weight = 1.5 }, Some { Brand = Brand "Naked Juice"
ProductType = Eat (FoodType "Fruit Smoothies")
Price = 4.0
Weight = 16.0 })
({ Brand = Brand "Quest"
ProductType = Eat (FoodType "Protein Bars")
Price = 3.0
Weight = 2.1 }, None)
let eats =
mixedSeq
|> Seq.map fst
let drinks =
mixedSeq
|> Seq.map snd
|> Seq.choose id
let all =
seq {
yield! eats
yield! drinks
}
all
|> Seq.iter prettyPrint
Chips (Lay's, 1 oz) costs 1$
Chips (Doritos, 2 oz) costs 2$
Candy Bars (Snickers, 1,5 oz) costs 1,25$
Candy Bars (Kit Kat, 1,5 oz) costs 1,25$
Granola Bars (Nature Valley, 1,5 oz) costs 1,5$
Cookies (Chips Ahoy, 1,5 oz) costs 1,25$
Crackers (Cheez-Its, 2 oz) costs 1,5$
Nuts (Planters, 1 oz) costs 1,5$
Popcorn (Pop-Secret, 2 oz) costs 2$
Beef Jerky (Jack Link's, 1 oz) costs 3,5$
Cup Noodles (Maruchan, 3 oz) costs 1,5$
Pretzels (Rold Gold, 1 oz) costs 1$
Trail Mix (Planters, 1 oz) costs 1,5$
Rice Krispie Treats (Kellogg's, 1,5 oz) costs 1,25$
Protein Bars (Quest, 2,1 oz) costs 3$
Bottled Water (Aquafina, 16 oz) costs 1,5$
Bottled Water (Dasani, 20 oz) costs 2$
Soda (Coca-Cola, 12 oz) costs 1,5$
Soda (Pepsi, 20 oz) costs 2$
Sports Drinks (Gatorade, 20 oz) costs 2,5$
Juice Boxes (Capri Sun, 6 oz) costs 1$
Energy Drinks (Red Bull, 8,4 oz) costs 3$
Iced Tea (Lipton, 16 oz) costs 1,5$
Lemonade (Minute Maid, 16 oz) costs 1,5$
Iced Coffee (Starbucks, 12 oz) costs 2,5$
Chocolate (Swiss Miss, 8 oz) costs 2$
Milk (Nesquik, 8 oz) costs 1$
Chocolate Milk (Yoo-hoo, 8 oz) costs 1$
Fruit Smoothies (Naked Juice, 16 oz) costs 4$