2016-03-08 21 views
5

To pytanie pochodzi od kogoś, kto pracuje nad przejściem z R na F #. W pełni zgadzam się, że moje podejście tutaj może być błędne, więc szukam sposobu F # na to. Mam sytuację, w której chcę iterować za pomocą zestawu plików XML, analizować je i wyodrębniać kilka wartości, aby określić, które z nich wymagają dalszego przetwarzania. Moją naturalną skłonnością jest Mapowanie na tablicy danych XML, exampleData w tym przypadku, przeanalizowanie każdego z nich za pomocą dostawcy typu RawDataProvider i wreszcie utworzenie obiektu Map dla każdego pliku zawierającego sparsowany XML, wartość Status z XML i ItemId wartość.Typ kolekcji F dla typów mieszanych

Okazuje się, że typ mapy w F # nie jest podobny do listy w R. Listy w R są zasadniczo hashmaps, które mogą obsługiwać mieszane typy. Wygląda na to, że typ mapy w F # nie obsługuje przechowywania typów mieszanych. Odkryłem, że jest to niesłychanie przydatne w mojej pracy R i szukam odpowiedniej kolekcji F # do tego.

Czy też myślę o tym wszystko źle? Jest to bardzo naturalny sposób przetwarzania danych w R, więc oczekiwałbym, że będzie sposób, aby to zrobić również w F #. Zakładam, że zamierzam przeprowadzić dalszą analizę i dodać do tych kolekcji dodatkowe elementy danych.

Aktualizacja: To wydaje się tak prosty przypadek użycia, że ​​musi istnieć idiomatyczne to sposobem w F #, bez konieczności definiowania typu rekordu na każdym etapie analizy. Zaktualizowałem mój przykład, aby dokładniej zilustrować to, co próbuję zrobić. Chcę zwrócić tablicę obiektów Mapa, że ​​mam analizowanych:

type RawDataProvider = XmlProvider<"""<product Status="Good" ItemId="123" />""">   

let exampleData = [| """<product Status="Good" ItemId="123" />"""; """<product Status="Bad" ItemId="456" />"""; """<product Status="Good" ItemId="789" />"""|] 

let dataResult = 
      exampleData 
      |> Array.map(fun fileData -> RawDataProvider.Parse(fileData)) 
      |> Array.map(fun xml -> Map.empty.Add("xml", xml).Add("Status", xml.Status).Add("ItemId", xml.ItemId)) 
      |> Array.map(fun elem -> elem.["calc1Value"] = calc1 elem["itemId"]) 
      |> Array.map(fun elem -> elem.["calc2"] = calc2 elem.["ItemId"] elem.["calc1Value"]) 
+0

Nie o co prosiłeś, ale myślę, że 'File.ReadAllLines (File) |> Array.reduce (+)' może być zastąpiony 'File.ReadAllText (pliku)' – CoderDennis

+0

@CoderDennis dzięki za info –

+0

Powinieneś użyj typu rekordu zamiast luźno wpisanego skrótu. –

Odpowiedz

4

To co uważam za niemal idiomatyczne tutaj - Jestem utrzymując ten sam kształt, jak w przykładzie, dzięki czemu można dopasować dwa:

let dataResult = 
    exampleData 
    |> Array.map(fun fileData -> RawDataProvider.Parse(fileData)) 
    |> Array.map(fun xml -> xml, calc1 xml.ItemId) 
    |> Array.map(fun (xml, calcedValue1) -> xml, calcedValue1, calc2 xml.ItemId calcedValue1) 

Co XmlProvider naprawdę daje ci to nie po prostu parsowania xml, ale fakt, że generuje silnie typami reprezentacji xml. To jest lepsze niż umieszczanie danych na mapie, ponieważ daje ci silniejsze gwarancje, czy twój program robi to, co trzeba. Na przykład nie pozwoliłoby to na wymieszanie itemId i ItemId, jak to się stało w fragmencie kodu;)

Dla wartości obliczanych w poniższych krokach można użyć krotek zamiast rekordu. Ogólnie rzecz biorąc, rekordy są preferowane do krotek, ponieważ prowadzą do bardziej czytelnego kodu, ale łączenie powiązanych wartości różnych typów w agregaty ad-hoc jest tak naprawdę scenariuszem, w którym używa się krotek.

Teraz powiedziałem prawie idiomatyczne - Chciałbym zerwać analizowania i przetwarzania analizowany plików XML do oddzielnych funkcji i obliczyć zarówno calc1 i calc2 wyniki w jednej funkcji zamiast komponować dwa Array.maps takiego:

let dataResult = 
    parsedData 
    |> Array.map(fun xml -> 
     let calced1 = calc1 xml.ItemId 
     xml, calced1, calc2 xml.ItemId calced1) 

Jeśli pochodzisz z tła R, możesz wypróbować alternatywne podejście, korzystając z metody Deedle. Daje ci to przepływ pracy podobny do R w F #.