【问题标题】:How do I make a for loop that scans through JSON in F#如何在 F# 中创建一个扫描 JSON 的 for 循环
【发布时间】:2020-04-18 12:14:16
【问题描述】:

使用 F# 我正在尝试扫描 JSON 文件并将其数组与单个(随机生成的)数字数组进行比较。 json的格式是:

{"1":[#,#,#,#,#],"2":[#,#,#,#,#],...}

等 121 个条目。我目前正在尝试 Json.NET。我的问题是:

  • 如何使用 Json.NET 导入本地文件?

  • 我将如何着手对 json 键进行简单调用,以返回适合通过 for 循环运行它的数组值?

这是我的代码:

open System
open System.IO
open Newtonsoft.Json

(*open FSharp.Data

type Provider = JsonProvider<"powernum.json">
let numbers = Provider.Load("powernum.json")

//numbers.``1`` gets me the array but can't scan through all the IDs with an iterating for loop
(*
if numbers.``3`` = [|29;30;41;48;64|] then
    printfn "True"
else printfn "False"
*)
(*numbers.JsonValue.Item "1" 
let test (a: int) = string a
let testPile = 
    for i = 1 to 10 do
    let goNum = numbers.JsonValue.Item (test i) 
    Console.Write goNum
    Console.WriteLine ""
testPile    // This works but is not usable for data comparison with an F# Array
*)
*)

let r = new StreamReader("\PATH\powernum.json")
let (json: string) = r.ReadToEnd();
let conv = JsonConvert.DeserializeObject<> (json)

Console.WriteLine("{0}",conv)//where I'm stuck with Json.NET

[<EntryPoint>]
let main argv =
    let rnd = Random()

    let numberGen = Set.empty.Add(rnd.Next(1,69)).Add(rnd.Next(1,69)).Add(rnd.Next(1,69)).Add(rnd.Next(1,69)).Add(rnd.Next(1,69)) |>Set.toArray |>Array.sort


    Console.ReadKey() |> ignore 
    0// return an integer exit code

Jsontocsharp.com 呈现无效。

我尝试过使用 F# 数据,但我发现不可能进行迭代循环,因为我必须拉起带有重音符号的“键”来封装数字以将其读取为像这样的 int numbers.``1`` .它不带变量。在仍然使用 F# Data 的同时尝试了另一种方法,但它仅作为字符串在我尝试转换时出错。

为了比较,这是我在 python 中制作原型的版本:

import random
import json
with open('/PATH/powernum.json') as pnumbers:
    data = json.load(pnumbers)

#makes an array with the range of numbers
Valid_numbers =[]

for x in range(69):
    Valid_numbers.append(x+1)
generated = []

def pulledNumber():
    generated[:]=[]
    #adds numbers to the array from 0-4
    while len(generated) !=5:
        #takes a random number from the range of numbers
        generate_number = random.choice(Valid_numbers)
        #check if the two arrays have the same values
        if generate_number not in generated:
            #add to the array if values don't match
            generated.append(generate_number)
    generated.sort()
    return generated

pulledNumber()

for x, y in data.items():
    if generated not in y:
        print("Id: %s passed" % x)
    else:
        print("Id: %s failed" % x)
        pulledNumber()
        break

print (pulledNumber())

【问题讨论】:

标签: json f# json.net f#-data


【解决方案1】:

f# 是一种静态类型语言——我们通常不会注意到它,因为它具有出色的类型推断能力。但是在从 JSON 文件反序列化时,在编写任何代码之前,确定 JSON 是否具有固定模式很有用,如果是,则创建或选择可以自动映射 JSON 的适当数据模型。

在您的情况下,您的 JSON 如下所示:

{
  "1": [29,30,41,48,64],
  "2": [29,320,441,548,11]
  // Additional items omitted
}

这里有一个具有可变属性名称的根对象,其值始终是整数数组。正如 Newtonsoft 文档 Deserialize a Dictionary 中所解释的,这样的 JSON 对象可以反序列化为一些 IDictionary&lt;string, T&gt; 以获得适当的值类型 T。正如 Deserialize a Collection 中所述,JSON 数组可以反序列化为适当项目类型的集合。

在 f# 中,我们使用 Map&lt;'Key,'Value&gt; 表示字典,使用 lists 表示静态类型值的具体化列表。因此,您的 JSON 对应于

Map<string, int list>

确定合适的数据模型后,引入以下函数以从文件中反序列化 JSON:

//https://www.newtonsoft.com/json/help/html/DeserializeWithJsonSerializerFromFile.htm
let jsonFromFile<'T>(fileName : string, settings : JsonSerializerSettings) = 
    use streamReader = new StreamReader(fileName)
    use jsonReader = new JsonTextReader(streamReader)
    let serializer = JsonSerializer.CreateDefault(settings)
    serializer.Deserialize<'T>(jsonReader)

现在您可以反序列化您的 JSON 并过滤匹配某些列表的值

let fileName = "\PATH\powernum.json"

let requiredValues = [29;30;41;48;64] // Or whatever list of values you are looking for

let map = jsonFromFile<Map<string, int list>>(fileName, null)

let filteredMap = 
    map |> Map.toSeq
        |> Seq.filter (fun (key, value) -> requiredValues = value)
        |> Map.ofSeq

// Print results
filteredMap |> Map.iter (fun key value ->
   printf "Key = %A has matching list of values = %A\n" key value)

打印出来的

Key = "1" has matching list of values = [29; 30; 41; 48; 64]

注意事项:

  • 请务必在使用完 disposable 资源(例如 StreamReader)后将其处理掉。 use 关键字可确保在资源超出范围时自动发生这种情况。

  • 如果您希望搜索值的无序集,可以使用set 而不是list

    let requiredValues = set [64;29;30;41;48] // Or whatever set of values you are looking for
    
    let map = jsonFromFile<Map<string, Set<int>>>(fileName, null)
    
    let filteredMap = 
        map |> Map.toSeq
            |> Seq.filter (fun (key, value) -> requiredValues = value)
            |> Map.ofSeq
    
  • 正如 Don Syme 在 Equality and Comparison Constraints in F# 中所解释的那样,listset 都支持结构相等比较,这就是为什么 requiredValues = value 检查集合是否具有相同的 内容

演示小提琴#1 here 用于list 和#2 here 用于set

【讨论】:

  • @Tripmodo - 如果您遇到问题,请尝试分享minimal reproducible example,通过在我的答案中分叉其中一个小提琴然后修改它以重现问题来重现您的问题。您最初的问题实际上并未包含示例 JSON,因此可能我的数据模型不完全匹配。
  • 到目前为止,地图似乎正在拉出与我的数组有共同数字的数组,以及其他一些随机选择,但你的例子让我对使用地图解决问题有所启发.谢谢你的帮助。我会尝试做更多的研究以获得我想要的结果,但是为了让 json 工作和可扫描,我认为这个问题已经解决了。
  • @Tripmodo - 您是使用 .Net 数组还是 f# list 来保存您的号码?结构相等比较适用于setlist,但不适用于非 f# 特定的集合,如数组。 minimal reproducible example 将大大有助于澄清您的问题。
  • 我将我的列表转换为val requiredValues : int list = [9; 18; 50; 63; 65] 并且地图给了我val map : Map&lt;string,int list&gt; = map [("1", [1; 2; 3; 7; 39]); ("10", [18; 36; 45; 47; 69]); ("100", [2; 9; 17; 36; 67]); ("101", [9; 12; 15; 31; 60]); ("102", [12; 33; 54; 57; 60]); ("103", [23; 30; 35; 41; 57]); ("104", [35; 49; 50; 59; 66]); ("105", [14; 47; 54; 55; 68]); ("106", [16; 32; 35; 36; 46]); ...] 不完全确定这是否是预期的结果,但我想我会使用一个与json中的一个匹配的列表然后从那里往上走。
猜你喜欢
  • 1970-01-01
  • 2015-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-05
  • 2017-03-30
  • 2016-01-14
相关资源
最近更新 更多