optimize and document

master
Brian Hicks 2020-02-19 10:37:24 -06:00
parent e9fe1c90b2
commit 73d263e3b4
2 changed files with 59 additions and 47 deletions

View File

@ -1,15 +1,27 @@
module Constraint exposing (Model, focusOn, init, positions, unfocus, updateAttachments)
module Constraint exposing (Model, focusOn, init, positions, unfocus, updateIdealPositions)
import Dict exposing (Dict)
{-| Lay out comments according to these constraints:
1. if there is a focused comment, it must be at it's ideal position
2. comments may not overlap
3. comments should be as close as possible to their ideal position (but the
first comment in an otherwise-overlapping sequence should be at it's ideal position)
Notably missing here: the ability to add new comments. That's just not something
that we need to solve for yet. Once we do, it should not be too hard to add
(recalculate the `comments` ordering with respect to the new height and ideal
position data, and re-solve.)
-}
type Model
= Model
{ -- comment ID to comment height
heights : Dict Int Float
-- comment ID to ideal position
, attachments : Dict Int Float
{ -- comment ID to dimensions. This needs to be sorted by idealPosition
-- since this algorithm will be walking the data that way several
-- times.
comments : List ( Int, { height : Float, idealPosition : Float } )
-- comment ID to actual position
, positions : Dict Int Float
@ -24,14 +36,23 @@ type Model
init :
{ heights : Dict Int Float
, attachments : Dict Int Float
, idealPositions : Dict Int Float
, margin : Float
}
-> Model
init { heights, attachments, margin } =
init { heights, idealPositions, margin } =
Model
{ heights = heights
, attachments = attachments
{ comments =
-- take the intersection of the heights and ideal positions. Gotta have both for a comment to do this algorithm!
Dict.merge
(\_ _ result -> result)
(\key height idealPosition -> Dict.insert key { height = height, idealPosition = idealPosition })
(\_ _ result -> result)
heights
idealPositions
Dict.empty
|> Dict.toList
|> List.sortBy (\( _, { idealPosition } ) -> idealPosition)
, positions = Dict.empty
, margin = margin
, focus = Nothing
@ -53,16 +74,9 @@ solveWithoutFocus (Model guts) =
Model
{ guts
| positions =
guts.attachments
|> Dict.toList
|> List.sortBy Tuple.second
guts.comments
|> List.foldl
(\( id, idealPosition ) ( finalPositions, progressLine ) ->
let
height =
Dict.get id guts.heights
|> Maybe.withDefault 0
in
(\( id, { idealPosition, height } ) ( finalPositions, progressLine ) ->
if idealPosition >= progressLine then
( Dict.insert id idealPosition finalPositions
, idealPosition + height + guts.margin
@ -87,20 +101,13 @@ solveWithFocus (Model guts) =
solveWithoutFocus (Model guts)
( goUp, goDown ) =
newGuts.attachments
|> Dict.toList
|> List.sortBy Tuple.second
-- == 0 is a trick to get the compiler to generate
-- more efficient code. Will not always be needed!
|> splitAtReversing (\( curId, _ ) -> curId - id == 0)
-- == 0 is a trick to get the compiler to generate
-- more efficient code. Will not always be needed!
splitAtReversing (\( curId, _ ) -> curId - id == 0) newGuts.comments
( downwardPositions, _ ) =
List.foldl
(\( curId, idealPosition ) ( finalPositions, progressLine ) ->
let
height =
Dict.get curId newGuts.heights |> Maybe.withDefault 0
in
(\( curId, { height, idealPosition } ) ( finalPositions, progressLine ) ->
if idealPosition >= progressLine then
( Dict.insert curId idealPosition finalPositions
, idealPosition + height + newGuts.margin
@ -116,11 +123,8 @@ solveWithFocus (Model guts) =
( downwardAndUpwardPositions, _ ) =
List.foldl
(\( curId, idealPosition ) ( finalPositions, progressLine ) ->
(\( curId, { height, idealPosition } ) ( finalPositions, progressLine ) ->
let
height =
Dict.get curId newGuts.heights |> Maybe.withDefault 0
currentPosition =
Dict.get curId newGuts.positions |> Maybe.withDefault idealPosition
in
@ -140,8 +144,8 @@ solveWithFocus (Model guts) =
)
( downwardPositions
, case goDown of
( _, start ) :: _ ->
start
( _, { idealPosition } ) :: _ ->
idealPosition
_ ->
-- infinity
@ -155,13 +159,21 @@ solveWithFocus (Model guts) =
solveWithFocus (Model guts)
updateAttachments : Dict Int Float -> Model -> Model
updateAttachments attachments (Model guts) =
if attachments == guts.attachments then
Model guts
else
Model { guts | attachments = attachments } |> solve
updateIdealPositions : Dict Int Float -> Model -> Model
updateIdealPositions attachments (Model guts) =
Model
{ guts
| comments =
guts.comments
|> List.filterMap
(\( id, metrics ) ->
Maybe.map
(\newIdealPosition -> ( id, { metrics | idealPosition = newIdealPosition } ))
(Dict.get id attachments)
)
|> List.sortBy (\( _, { idealPosition } ) -> idealPosition)
}
|> solve
focusOn : Int -> Model -> Model

View File

@ -97,7 +97,7 @@ update msg model =
( { model
| commentPositions =
Maybe.map
(Constraint.updateAttachments (finalAttachments attachments))
(Constraint.updateIdealPositions (idealPositionsFor attachments))
model.commentPositions
}
, Cmd.none
@ -109,7 +109,7 @@ update msg model =
Just <|
Constraint.init
{ heights = Dict.fromList commentHeights
, attachments = finalAttachments attachments
, idealPositions = idealPositionsFor attachments
, margin = 10
}
}
@ -133,8 +133,8 @@ update msg model =
)
finalAttachments : List ( Int, Float ) -> Dict Int Float
finalAttachments =
idealPositionsFor : List ( Int, Float ) -> Dict Int Float
idealPositionsFor =
List.foldl
(\( commentId, top ) soFar ->
Dict.update commentId