【问题标题】:Parsing values from string array从字符串数组解析值
【发布时间】:2014-01-24 01:16:26
【问题描述】:

我正在尝试为已绘制到表单中的 2d 对象创建保存/加载功能。

type circle = { X : int; Y : int; Diameter : int; Brush : Brush}
type Square = { X : int; Y : int; Length : int; Height: int; Brush : Brush}

当我创建对象时,我将它们放入 2 个列表中,每种类型 1 个。 我最初的想法是读取这些对象并将其写入文本文件,见下文:

saveFile.Click.Add(fun _ ->
 for c in listOfCircles do 
   myfile.WriteLine("Circle," + c.X.ToString() + "," + c.Y.ToString() + "," + c.Diameter.ToString() + "," + c.Brush.ToString())
 for s in listOfSquares do
   myfile.WriteLine("Square," + s.X.ToString() + "," + s.Y.ToString() + "," + s.Height.ToString() + "," + s.Length.ToString() + "," + s.Brush.ToString())
 myfile.Close() // close the file

在文本文件中它看起来像这样

Circle,200,200,50,System.Drawing.SolidBrush
Square,50,55,45,55,System.Drawing.SolidBrush

从这里我想读取这些值,然后能够通过将对象添加到列表中并重新绘制它们来解析它们并重新创建对象。

let readCircle =
  System.IO.File.ReadAllLines path
  |> Array.choose (fun s ->
    match s.Split ',' with
    | [|x; y ; z ; b ; _|] when x = "Circle" -> Some (y, z, b)
    | _ -> None )

let readSquare =  
  System.IO.File.ReadAllLines path
  |> Array.choose (fun s ->
    match s.Split ',' with
    | [|x; y ; z ; b ; a ; _|] when x = "Square" -> Some (y, z, b, a)
    | _ -> None )

这些功能给了我

val readCircle : (string * string * string) [] = [|("200", "200", "50")|]
val readSquare : (string * string * string * string) [] = [|("50", "55", "45", "55")|]

我现在遇到的问题是我不确定如何从数组中获取值。下面是多个圆圈的示例。

val readCircle : (string * string * string) [] =  [|("200", "200", "50"); ("200", "200","50")|]

非常感谢有关如何从这里开始/如何解决此问题的任何想法或 cmets!问题摘要:我如何从数组中获取值并将它们放入例如我已经创建的添加函数,见下文:

 listOfCircles.Add({ X = 200; Y = 200; Diameter = 50; Brush = Brushes.Black})

【问题讨论】:

  • 我不确定我是否理解这个问题。但假设您需要将值数组转换为形状列表,您可以使用values |> Seq.map createCircle |> Seq.toList
  • 我想从数组中获取值并放入listOfCircles.Add函数,见编辑底部。

标签: f#


【解决方案1】:

您可以使用Array.map 转换您拥有的字符串元组数组,例如

[|("200", "200", "50"); ("200", "200","50")|]
|> Array.map (fun (x,y,d) -> {X = int32 x; Y = int32 y; Diameter = int32 d; Brush = Brushes.Black})

如果您在解析文件时转换为 circlesquare 可能会更清楚一些,那么您将有一个 circle 数组或 square 数组,您可以直接将其添加到您的列表。

let readCircle =
  System.IO.File.ReadAllLines path
  |> Array.choose (fun s ->
    match s.Split ',' with
    | [|t; x; y; d; _|] when t = "Circle" -> 
        Some {X = int32 x; Y = int32 y; Diameter = int32 d; Brush = Brushes.Red}
    | _ -> None )

但是...如果您想进行更大的更改,您可以使用可区分联合来表示您的形状,然后它们将共享一个公共类型 Shape,您可以在同一个函数中解析圆形和正方形。

type Shape = 
| Circle of X : int * Y : int * Diameter : int * Brush : Brush
| Square of X : int * Y : int * Length : int * Height: int * Brush : Brush 

let readShapes (data: string array) =
  data
  |> Array.choose (fun s ->
    match s.Split ',' with
    | [|t; x; y; d; _|] when t = "Circle" -> 
        Some (Circle(X = int32 x, Y = int32 y, Diameter = int32 d, Brush = Brushes.Red))
    | [|t; x; y; l; h; _|] when t = "Square" -> 
        Some (Square(X = int32 x, Y = int32 y, Length = int32 l, Height = int32 h, Brush = Brushes.Red))
    | _ -> None )

let listOfShapes = ResizeArray<_>()

let testInput = """
Circle,200,200,50,System.Drawing.SolidBrush
Square,50,55,45,55,System.Drawing.SolidBrush"""

testInput.Split('\n') // System.IO.File.ReadAllLines path
|> readShapes
|> Array.iter (listOfShapes.Add)

这会导致

val it : System.Collections.Generic.List<Shape> =
  seq
    [Circle (200,200,50,System.Drawing.SolidBrush {Color = Color [Red];});
     Square (50,55,45,55,System.Drawing.SolidBrush {Color = Color [Red];})]

然后您可以使用模式匹配来绘制每种类型的形状

let drawShape shape =
    match shape with
    | Circle(x,y,d,b) -> 
        printfn "Pretend I just drew a circle at %d,%d with diameter %d." x y d
    | Square(x,y,l,h,b) -> 
        printfn "Pretend I just drew a rectangle at %d,%d that was %d long and %d high." x y l h

listOfShapes |> Seq.iter drawShape

给予

Pretend I just drew a circle at 200,200 with diameter 50.
Pretend I just drew a rectangle at 50,55 that was 45 long and 55 high.

【讨论】:

    【解决方案2】:

    如果我了解您的目标,我会这样做。我只实现了Circle;您需要对其进行修改以处理Square

    open System
    open System.Collections.Generic
    open System.Drawing
    open System.IO
    
    let memoize f =
      let cache = Dictionary()
      fun key ->
        match cache.TryGetValue(key) with
        | true, value -> value
        | _ ->
          let value = f key
          cache.Add(key, value)
          value
    
    let getBrush =
      memoize (fun name -> typeof<Brushes>.GetProperty(name).GetValue(null) :?> SolidBrush)
    
    type Circle = 
      { X : int
        Y : int
        Diameter : int
        Brush : SolidBrush } with
      override this.ToString() = 
        sprintf "Circle,%d,%d,%d,%s" this.X this.Y this.Diameter this.Brush.Color.Name
      static member Parse(s: string) =
        match s.Split(',') with
        | [|"Circle";x;y;diameter;brushName|] -> {X=int x; Y=int y; Diameter=int diameter; Brush=getBrush brushName}
        | _ -> invalidArg "s" "Cannot parse string"
    
    let writeShapesToFile fileName shapes =
      File.WriteAllLines(fileName, Seq.map (sprintf "%O") shapes)
    
    let readShapesFromFile fileName =
      File.ReadAllLines(fileName) |> Array.map Circle.Parse
    

    此外,您可以考虑使用类层次结构而不是记录,因为CircleSquare 的大部分结构和行为是共享的。

    【讨论】:

      【解决方案3】:

      这很有趣——我用一种与 Daniel 完全不同的方式来处理它(但我同意他的观点,你的类可能是你的形状的更好方法)。相反,我利用了有区别的工会(并且有更好的方法来做到这一点 - 稍后会更多):

      首先,我为制作形状的参数列表定义一个类型:

      type Parameter =
          | Label of string
          | Number of int
      

      现在让我们将字符串转换为参数:

      let toParameter s =
          match Int32.TryParse(s) with
          | (true, i) -> Number(i)
          | (_, _) -> Label(s)
      

      现在将字符串列表转换为参数列表:

      let stringListToParameterList stringlist = stringlist |> List.map(function s -> toParameter s)
      

      现在将逗号分隔的字符串转换为字符串列表:

      let commastringToList (s:string) = s.Split(',') |> Array.toList
      

      好的 - 太好了 - 让我们定义您的记录和主形状:

      type circlerec = { X : int; Y : int; Diameter : int; Brush : Brush}
      type squarerec = { X : int; Y : int; Length : int; Height: int; Brush : Brush}
      type Shape =
          | Circle of circlerec
          | Square of squarerec
      

      有了这个,我们需要一种从参数列表中创建形状的方法。这是蛮力,但读起来还不错:

      let toShape list =
          match list with
          | Label("Circle") :: Number(x) :: Number(y) :: Number(diam) :: Label(colorName) :: [] ->
              Circle({X = x; Y = y; Diameter = diam; Brush = new SolidBrush(Color.FromName(colorName)); })
          | Label("Circle") :: rest -> raise <| new ArgumentException("parse error:expected Circle num num num color but got " + list.ToString())
          | Label("Square") :: Number(x) :: Number(y) :: Number(length) :: Number(height) :: Label(colorName) :: [] ->
              Square({X = x; Y = y; Length = length; Height = height; Brush = new SolidBrush(Color.FromName(colorName)); })
          | Label("Square") :: rest -> raise <| new ArgumentException("parse error:expected Square num num num num color but got " + list.ToString())
          | _ -> raise <| new ArgumentException("parse error: unknown shape: " + list.ToString())
      

      它很密集,但我使用 F# 的模式匹配来发现每个形状的各种参数。请注意,您现在可以执行诸如在文件中添加 Square,x,y,size,colorName 之类的操作,并通过添加模式来制作一个长度和高度等于大小的正方形。

      终于来了,将你的文件转换成形状:

      let toShapes path =
          System.IO.File.ReadAllLines path |> Array.toList |>
              List.map(function s -> s |> commastringToList |>
              stringListToParameterList |> toShape)
      

      它将文件中的每一行映射到一个字符串列表,然后将每一行映射到一个形状,但将逗号字符串传递到列表转换器,然后通过参数列表,然后到一个形状。

      现在不好的地方是错误检查非常可怕,并且参数类型应该真正包含Pigment of Color,这将允许您查看传入的字符串,如果它是有效的颜色名称,请将其映射到一个颜料或者一个标签。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-06
        • 1970-01-01
        • 1970-01-01
        • 2023-03-10
        • 1970-01-01
        • 2020-06-21
        相关资源
        最近更新 更多