I'm trying to convert this json
{ "date": [2018, 2, 3] }
into this model
type alias MyModel = { date: Date }
I know how to decode it into a list
decoder =
decode MyModel (field "date" (list int))
but I can't figure out how to chain Decoders together.
You can use Json.Decode.index to pull out values at known indices. You'll want the values at indices 0, 1, and 2, and then you can convert them to a string for use in Date.fromString like this:
import Date exposing (Date)
import Html exposing (Html, text)
import Json.Decode exposing (..)
dateDecoder : Decoder Date
dateDecoder =
let
toDateString y m d =
String.join "-" (List.map toString [ y, m, d ])
in
map3 toDateString
(index 0 int)
(index 1 int)
(index 2 int)
|> andThen
(\str ->
case Date.fromString str of
Ok date ->
succeed date
Err err ->
fail err
)
You could use the decoder like this:
decoder =
decode MyModel (field "date" dateDecoder)
Related
I have the following data class:
data class Foo(val a: Int = 0, val b: Int = 0)
I have a list of Foo's with the following structure:
[ Foo(a = 1), Foo(a = 2), ..., Foo(b = 22), Foo(a = 5), Foo(a = 6), ... ]
(a group of items with a's, then one b, then a's again)
I would like to split above list into three sub-lists like that:
[ Foo(a = 1), Foo(a = 2), ...]
[ Foo(b = 22) ]
[ Foo(a = 5), Foo(a = 6), ...]
sublist of elements that have non-zero a property
list of one element that has non-zero b
remaining sublist of elements that have non-zero a property
Is it possible to achieve using groupBy or partition?
It is not possible to do it via groupBy or partition, because it is not possible to check the past state in those operations. However, you can do it via a fold operation and using mutable lists. Not sure if it fits to your needs but here it goes:
val input = listOf(Foo(a = 1), Foo(a = 2), Foo(b = 22), Foo(a = 5), Foo(a = 6))
val output: List<List<Foo>> = input.fold(mutableListOf<MutableList<Foo>>(mutableListOf())) { acc, foo ->
val lastList = acc.last()
val appendToTheLastList =
lastList.isEmpty() ||
(foo.a != 0 && lastList.last().a != 0) ||
(foo.b != 0 && lastList.last().b != 0)
when {
appendToTheLastList -> lastList.add(foo)
else -> acc.add(mutableListOf(foo))
}
return#fold acc
}
println(output)
outputs:
[[Foo(a=1, b=0), Foo(a=2, b=0)], [Foo(a=0, b=22)], [Foo(a=5, b=0),
Foo(a=6, b=0)]]
Note: I have to point out that this solution is not better than a solution with regular loops.
So you want to 1) ignore the first Foos where a=0, 2) start collecting them when you see Foos where a is non-zero, 3) when you hit a Foo where a=0, put that in another list, because b will be non-zero, 4) start collecting non-zero a's again in a third list?
If that's what you want (this is an extremely specific thing you want and you haven't been clear about it at all) you could do it this way:
data class Foo(val a: Int, val b: Int)
val stuff = listOf(Foo(0,1), Foo(1,2), Foo(3,0), Foo(0, 4), Foo(0, 5), Foo(6, 1), Foo(0,7))
fun main(args: Array<String>) {
fun aIsZero(foo: Foo) = foo.a == 0
// ignore initial zero a's if there are any
with(stuff.dropWhile(::aIsZero)) {
val bIndex = indexOfFirst(::aIsZero)
val listOne = take(bIndex)
val listTwo = listOf(elementAt(bIndex))
val listThree = drop(bIndex+1).filterNot(::aIsZero)
listOf(listOne, listTwo, listThree).forEach(::println)
}
}
You can't use partition or groupBy because your predicate depends on the value of a, but also on whether it happens to represent that one element you want to put in the b list, and for the others whether they appear before or after that b element. Which you don't know before you start processing the list.
You could mess around with indices and stuff, but honestly your use case seems so specific that it's probably better to just do it imperatively instead of trying to cram it into a functional approach.
I am in a need to compare 2 JSON objects where the order has retained while comparing. As Karate match ignores the order of an element, I am just curious to know if there is a way to do so in Karate.
Not directly, it is never needed, since JSON keys can be in any order, like a Map.
But you can do an exact match after converting to a (normalized) string:
* def foo = { a: 1, b: 2 }
* string str1 = foo
* string str2 = { "a": 1, "b": 2 }
* assert str1 == str2
You can also get an ordered list of keys / values at any time:
* def vals = karate.valuesOf(foo)
* match vals == [1, 2]
* def keys = karate.keysOf(foo)
* match keys == ['a', 'b']
I’m stuck with a decoder that should decode an array [ 9.34958, 48.87733, 1000 ] to a Point, where index 2 (elevation) is optional.
type alias Point =
{ elev : Maybe Float
, at : Float
, lng : Float
}
Therefore I created following decoder:
fromArrayDecoder : Decoder Point
fromArrayDecoder =
map3 Point
(index 2 Decode.float |> Decode.maybe)
(index 1 Decode.float)
(index 0 Decode.float)
My problem now is, that this decoder succeeds when index 2 is missing or is of any type like string etc. But I want it only to succeed if elev is missing, not if it has the wrong type. Is there any way of accomplishing this?
If by "missing" you mean the value can be null, you can just use nullable instead of maybe:
fromArrayDecoder : Decoder Point
fromArrayDecoder =
map3 Point
(index 2 Decode.float |> Decode.nullable)
(index 1 Decode.float)
(index 0 Decode.float)
If it's either a 3-element array or a two-element array you can use oneOf to try several decoders in order:
fromTwoArrayDecoder : Decoder Point
fromTwoArrayDecoder =
map3 (Point Nothing)
(index 1 Decode.float)
(index 0 Decode.float)
fromThreeArrayDecoder : Decoder Point
fromThreeArrayDecoder =
map3 Point
(index 2 Decode.float |> Decode.map Just)
(index 1 Decode.float)
(index 0 Decode.float)
fromArrayDecoder : Decoder Point
fromArrayDecoder =
oneOf
[ fromThreeArrayDecoder
, fromTwoArrayDecoder
]
Just remember to try the 3-element decoder first, as the 2-element decoder will succeed on a 3-element array as well, but the opposite does not.
I agree that the fact Json.Decode.maybe giving you Nothing on a wrong value rather than just a missing one is surprising.
elm-json-decode-pipeline can work in the way you want without getting too verbose.
> d = Decode.succeed Point
| |> optional "2" (Decode.float |> Decode.map Just) Nothing
| |> required "1" Decode.float
| |> required "0" Decode.float
|
> "[1, 2, \"3\"]" |> Decode.decodeString d
Err (Failure ("Json.Decode.oneOf failed in the following 2 ways:\n\n\n\n(1) Problem with the given value:\n \n \"3\"\n \n Expecting a FLOAT\n\n\n\n(2) Problem with the given value:\n \n \"3\"\n \n Expecting null") <internals>)
: Result Decode.Error Point
> "[1, 2, 3]" |> Decode.decodeString d
Ok { at = 2, elev = Just 3, lng = 1 }
: Result Decode.Error Point
> "[1, 2]" |> Decode.decodeString d
Ok { at = 2, elev = Nothing, lng = 1 }
: Result Decode.Error Point
(You can see from the error that under the hood it is using oneOf like in glennsl's answer.)
The only potentially surprising thing here is that you need to pass strings rather than int indexes, as there isn't a specific version for lists but you can access list indexes as though they are field names. This does mean that this version is subtly different in that it will not throw an error if you can an object with number field names rather than an array, but I can't imagine that really being an issue. The more real issue is it could make your error messages less accurate:
> "[0]" |> Decode.decodeString (Decode.field "0" Decode.int)
Ok 0 : Result Decode.Error Int
> "[]" |> Decode.decodeString (Decode.field "0" Decode.int)
Err (Failure ("Expecting an OBJECT with a field named `0`") <internals>)
: Result Decode.Error Int
> "[]" |> Decode.decodeString (Decode.index 0 Decode.int)
Err (Failure ("Expecting a LONGER array. Need index 0 but only see 0 entries") <internals>)
Note that you do still have to to avoid using Json.Decode.maybe. It may be tempting to write optional "2" (Decode.maybe Decode.float) Nothing which will result in the same behaviour as you originally got.
Say I have a List of records in elm:
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
and I want to get the record with the greatest magnitude value (300). What is a good way of doing this?
The docs gives an example of using the "maximum" -method, but it uses a simple list of integers. How is it done with records?
Update based on recommendation from #robertjlooby
There is a function called maximumBy which does exactly this in elm-community/list-extra. Example:
List.Extra.maximumBy .magnitude list
Original Answer
There are a few ways to achieve this.
This first way is more concise but it involves sorting the whole list, reversing it, then taking the head.
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
List.head << List.reverse << List.sortBy field
If you want something that's more efficient and only traverses the list once, here's a more efficient version:
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
let f x acc =
case acc of
Nothing -> Just x
Just y -> if field x > field y then Just x else Just y
in List.foldr f Nothing
An example of it in use:
list =
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
main =
text <| toString <| maxOfField .magnitude list
Here is a version that uses foldl and a default record:
bigger =
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose {id = 0, magnitude = 0} items
Sebastian's answer add an arbitrary start value which could cause a problem if all your magnitudes were negative. I would adjust to
bigger items =
case items of
[] -> []
(h :: []) -> h
(h :: tail) ->
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose h tail
I'd like to be able to split a list up into multiple lists.
I'm assuming this would need to be stored in a tuple - although not completely sure.
Say I have this group of 8 people
users =
["Steve", "Sally", "Barry", "Emma", "John", "Gustav", "Ankaran", "Gilly"]
I would like to split them up into a specific amount of groups.
For example, groups of 2, 3 or 4 people.
-- desired result
( ["Steve", "Sally", "Barry"]
, ["Emma", "John", "Gustav"]
, ["Ankaran", "Gilly"]
)
Part 2 of this question would be, How would you then iterate and render the results from a tuple of various lengths?
I was playing around with this example, using tuple-map
but it seems to only expect a tuple with 2 values.
import Html exposing (..)
import List
data = (
["Steve", "Sally", "Barry"]
, ["Emma", "John", "Gustav"]
, ["Ankaran", "Gilly"]
)
renderLI value =
li [] [ text value ]
renderUL list =
ul [] (List.map renderLI list)
main =
div [] (map renderUL data)
-- The following taken from zarvunk/tuple-map for examples sake
{-| Map over the tuple with two functions, one for each
element.
-}
mapEach : (a -> a') -> (b -> b') -> (a, b) -> (a', b')
mapEach f g (a, b) = (f a, g b)
{-| Apply the given function to both elements of the tuple.
-}
mapBoth : (a -> a') -> (a, a) -> (a', a')
mapBoth f = mapEach f f
{-| Synonym for `mapBoth`.
-}
map : (a -> a') -> (a, a) -> (a', a')
map = mapBoth
I'd like to be able to split a list up into multiple lists. I'm assuming this would need to be stored in a tuple - although not completely sure.
Tuples are fixed in the number of things they can carry. You can't have a function that accepts any size tuple.
It sounds like you'd like something more flexible, like a list of lists. You could define a split function like this:
import List exposing (..)
split : Int -> List a -> List (List a)
split i list =
case take i list of
[] -> []
listHead -> listHead :: split i (drop i list)
Now you've got a function that can split up any size list into a list containing lists of the requested size.
split 2 users == [["Steve","Sally"],["Barry","Emma"],["John","Gustav"],["Ankaran","Gilly"]]
split 3 users == [["Steve","Sally","Barry"],["Emma","John","Gustav"],["Ankaran","Gilly"]]
Your Html rendering now becomes simpler, since you only have to deal with lists of lists:
import Html exposing (..)
import List exposing (..)
split : Int -> List a -> List (List a)
split i list =
case take i list of
[] -> []
listHead -> listHead :: split i (drop i list)
users =
["Steve", "Sally", "Barry", "Emma", "John", "Gustav", "Ankaran", "Gilly"]
renderLI value =
li [] [ text value ]
renderUL list =
ul [] (List.map renderLI list)
main =
div [] (map renderUL <| split 3 users)
Updated answer for Elm 0.19
import List.Extra as E
E.groupsOf 3 (List.range 1 10)
--> [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]