Хаскелл, Эсон - Есть ли лучший способ анализа исторических данных? - PullRequest
0 голосов
/ 17 ноября 2018

Под «историческими данными» я имею в виду даты в качестве ключа и значение в этот день в качестве значения.

Например, часто правительственные институты или исследовательское подразделение универа собирают данные о землетрясениях, ливнях, движении рынка и т. Д. В этом формате


 {
        "Meta Data": {
            "1: Country": "SomeCountry",
            "2: Region": "SomeRegion",
            "3: Latest Recording": "2018-11-16"
        },
        "EarthQuakes": {
            "2018-11-16": {
                "Richter": "5.2508"
            },
            "2018-11-09": {
                "Richter": "4.8684"
            },
            "2018-11-02": {
                "Richter": "1.8399"
            },
    ...
    ...
    ...
            "1918-11-02": {
                "Richter": "1.8399"
            }
}

Обычно в нем будет раздел «Метаданные», а другой будет содержать значения / данные.


Я, как новичок, знаю два способа разбора документов такого типа.

Либо вы используете общий анализ, показанный в документации Aeson, где вы определяете типы данных, подобные этому

Data MetaData = MetaData { country :: String, region :: String, latestRec :: String } deriving (Show, Eq, Generic)

Сделайте это экземпляром FromJSON

instance FromJSON MetaData where
  parseJSON = withObject "MetaData" $
    \v -> do
       metaData  <- v        .: pack "Meta Data"
       country   <- metaData .: pack "1: Country"
       region    <- metaData .: pack "2: Region"
       latestRec <- metaData .: pack "3: Latest Recording"
       return MetaData{..}

С включенными расширениями RecordWildCard и DeriveGenerics.


Проблема, с которой я сталкиваюсь при таком подходе, заключается в том, что его нелегко реализовать в разделе «EarthQuakes».

Мне нужно определить каждую дату

earthQuakes <- v .: "EarthQuakes"
date1 <- earthQuakes .: "2018-11-16"
date2 <- earthQuakes .: "2018-11-06"
date3 <- earthQuakes .: "2018-11-02"
...
...
dateInfinity <- earthQuakes .: "1918-11-16"

Лучшим подходом было бы просто проанализировать все данные как значения JSON по умолчанию, расшифровав ссылку в Object type

thisFunction = do
    linksContents <- simpleHttp "somelink"
    let y = fromJust (decode linksContents :: Object)
        z = aLotOfFunctionCompositions y
    return z

где aLotOfFunctionCompositions вначале преобразует Object в HashMap с парами [(k, v)]. Затем я бы отобразил функцию unConstruct, чтобы получить значение из конструкторов по умолчанию, таких как

unConstruct (DefaultType value) = case (DefaultType value) of
             DefaultType x -> x

и, наконец, вы получите хороший список!

Проблема этого подхода заключается в aLotOfFunctionComposition.

Это всего лишь пример! Но на самом деле это может выглядеть так же безобразно и нечитаемо, как этот

let y = Prelude.map (\(a, b) -> (decode (encode a) :: Maybe String, decode (encode (snd (Prelude.head b))) :: Maybe String)) x
      z = Prelude.map (\(a, b) -> (fromJust a, fromJust b)) y
      a = Prelude.map (\(a, b) -> (a, read b :: Double)) z
      b = Prelude.map (\(a, b) -> (Prelude.filter (/= '-') a, b)) a
      c = Prelude.map (\(a, b) -> (read a :: Int, b)) b

Это фрагмент рабочего кода, который я сделал.


Итак, мой вопрос заключается в следующем: есть ли лучший / более чистый способ декодирования таких JSON-файлов, где у вас много ключей «даты», и вам нужно проанализировать их в работоспособные типы данных?

1 Ответ

0 голосов
/ 18 ноября 2018

Введите Map в вашем типе данных.Aeson переводит Map k v s в / из объектов, где v s кодируются / декодируются через их собственные To - / From - JSON экземпляров, а k s - To- / From - JSONKey s.Оказывается, что Day (из пакета time) имеет совершенно подходящие To - / From - JSONKey экземпляры.

data EarthquakeData = EarthquakeData {
    metaData :: MetaData,
    earthquakes :: Map Day Earthquake
} deriving (Eq, Show, Generic)

instance FromJSON EarthquakeData where
    parseJSON = withObject "EarthquakeData $ \v ->
        EarthquakeData <$> v .: "Meta Data"
        -- Map k v has a FromJSON instance that just does the right thing
        -- so just get the payloads with (.:)
        -- all this code is actually just because your field names are really !#$@~??
        -- not an Aeson expert, maybe there's a better way
                       <*> v .: "EarthQuakes"
instance ToJSON EarthquakeData where
    toJSON EarthquakeData{..} = object [ "Meta Data"   .= metaData
                                       , "EarthQuakes" .= earthquakes
                                       ]

data MetaData = MetaData { country :: String, region :: String, latestRec :: Day } deriving (Eq, Show)
instance FromJSON MetaData where
    parseJSON = withObject "MetaData" $ \v ->
        -- if you haven't noticed, applicative style is much neater than do
        -- using OverloadedStrings avoids all the pack-ing static
        MetaData <$> v .: "1: Country"
                 <*> v .: "2: Region"
                 <*> v .: "3: Latest Recording"
instance ToJSON MetaData where
    toJSON MetaData{..} = object [ "1: Country"          .= country
                                 , "2: Region"           .= region
                                 , "3: Latest Recording" .= latestRec
                                 ]
    toEncoding MetaData{..} = pairs $ "1: Country"          .= country
                                   <> "2: Region"           .= region
                                   <> "3: Latest Recording" .= latestRec

data Earthquake = Earthquake { richter :: Double } deriving (Eq, Show)
-- Earthquake is a bit funky because your JSON apparently has
-- numbers inside strings?
-- only here do you actually need monadic operations
instance FromJSON Earthquake where
    parseJSON = withObject "Earthquake" $ \v ->
        do string <- v .: "Richter"
           stringNum <- parseJSON string
           case readMaybe stringNum of
             Just num -> return $ Earthquake num
             Nothing -> typeMismatch "Double inside a String" string
instance ToJSON Earthquake where
    toJSON = object . return . ("Richter" .=) . show . richter
    toEncoding = pairs . ("Richter" .=) . show . richter

Я проверял это на вашем примере JSONи кажется, что туда и обратно encode и decode успешно.

...