diff --git a/benchmarks/src/Benchmarks.elm b/benchmarks/src/Benchmarks.elm index a58c850..88b6ab0 100644 --- a/benchmarks/src/Benchmarks.elm +++ b/benchmarks/src/Benchmarks.elm @@ -20,6 +20,7 @@ import Benchmark.Runner.Alternative as BenchmarkRunner import List.Extra import List.Extra.DropRight import List.Extra.GroupsOf +import List.Extra.InsertAt import List.Extra.Lift import List.Extra.NotMember import List.Extra.TakeRight @@ -275,6 +276,30 @@ listExtra = , ( "reverse", List.Extra.TakeRight.takeRightReverse ) , ( "length", List.Extra.TakeRight.takeRightLength ) ] + , rank "insertAt negative index" + (\insertAt -> insertAt -3 999 intList) + [ ( "recursion", List.Extra.InsertAt.insertAtRecursion ) + , ( "takeDrop", List.Extra.InsertAt.insertAtTakeDrop ) + , ( "splitAt", List.Extra.InsertAt.insertAtSplitAt ) + , ( "recursion2", List.Extra.InsertAt.insertAtRecursion2 ) + , ( "recursion3", List.Extra.InsertAt.insertAtRecursion3 ) + ] + , rank "insertAt good positive index" + (\insertAt -> insertAt 50 999 intList) + [ ( "recursion", List.Extra.InsertAt.insertAtRecursion ) + , ( "takeDrop", List.Extra.InsertAt.insertAtTakeDrop ) + , ( "splitAt", List.Extra.InsertAt.insertAtSplitAt ) + , ( "recursion2", List.Extra.InsertAt.insertAtRecursion2 ) + , ( "recursion3", List.Extra.InsertAt.insertAtRecursion3 ) + ] + , rank "insertAt bad positive index" + (\insertAt -> insertAt 150 999 intList) + [ ( "recursion", List.Extra.InsertAt.insertAtRecursion ) + , ( "takeDrop", List.Extra.InsertAt.insertAtTakeDrop ) + , ( "splitAt", List.Extra.InsertAt.insertAtSplitAt ) + , ( "recursion2", List.Extra.InsertAt.insertAtRecursion2 ) + , ( "recursion3", List.Extra.InsertAt.insertAtRecursion3 ) + ] ] ++ List.concatMap toComparisonsGroupsOfWithStep (List.range 1 4) ) diff --git a/benchmarks/src/List/Extra/InsertAt.elm b/benchmarks/src/List/Extra/InsertAt.elm new file mode 100644 index 0000000..62974bb --- /dev/null +++ b/benchmarks/src/List/Extra/InsertAt.elm @@ -0,0 +1,115 @@ +module List.Extra.InsertAt exposing + ( insertAtRecursion + , insertAtRecursion2 + , insertAtRecursion3 + , insertAtSplitAt + , insertAtTakeDrop + ) + +import List.Extra + + +insertAtRecursion : Int -> a -> List a -> List a +insertAtRecursion index value list = + if index <= -1 then + list + + else + let + go : Int -> List a -> List a -> List a + go i rest acc = + if i == index then + List.reverse acc ++ (value :: rest) + + else + case rest of + [] -> + -- index > length list + list + + head :: newRest -> + go (i + 1) newRest (head :: acc) + in + go 0 list [] + + +insertAtRecursion2Help : Int -> a -> List a -> Int -> List a -> List a -> List a +insertAtRecursion2Help index value list i rest acc = + if i == index then + List.foldl (::) (value :: rest) acc + + else + case rest of + [] -> + -- index > length list + list + + head :: newRest -> + insertAtRecursion2Help index value list (i + 1) newRest (head :: acc) + + +insertAtRecursion2 : Int -> a -> List a -> List a +insertAtRecursion2 index value list = + if index <= -1 then + list + + else + insertAtRecursion2Help index value list 0 list [] + + +insertAtTakeDrop : Int -> a -> List a -> List a +insertAtTakeDrop index value list = + if index <= -1 then + list + + else + let + length = + List.length list + in + if length < index then + list + + else + List.take index list ++ (value :: List.drop index list) + + +insertAtSplitAt : Int -> a -> List a -> List a +insertAtSplitAt index value list = + if index <= -1 then + list + + else + let + ( before, after ) = + List.Extra.splitAt index list + in + if List.isEmpty after && List.length before < index then + list + + else + before ++ (value :: after) + + +insertAtRecursion3Help : a -> List a -> Int -> List a -> List a -> List a +insertAtRecursion3Help value list i rest acc = + if i == 0 then + List.foldl (::) (value :: rest) acc + + else + case rest of + [] -> + -- index > length list + list + + head :: newRest -> + insertAtRecursion3Help value list (i - 1) newRest (head :: acc) + + +insertAtRecursion3 : Int -> a -> List a -> List a +insertAtRecursion3 index value list = + if index <= -1 then + list + + else + insertAtRecursion3Help value list index list [] diff --git a/src/List/Extra.elm b/src/List/Extra.elm index e8449e3..fbab365 100644 --- a/src/List/Extra.elm +++ b/src/List/Extra.elm @@ -1,5 +1,5 @@ module List.Extra exposing - ( last, init, getAt, cons, uncons, unconsLast, push, appendTo, prependTo, maximumBy, maximumWith, minimumBy, minimumWith, andMap, andThen, reverseMap, takeWhile, dropWhile, unique, uniqueBy, allDifferent, allDifferentBy, setIf, setAt, remove, updateIf, updateAt, updateIfIndex, removeAt, removeIfIndex, removeWhen, swapAt, stableSortWith + ( last, init, getAt, cons, uncons, unconsLast, push, appendTo, prependTo, maximumBy, maximumWith, minimumBy, minimumWith, andMap, andThen, reverseMap, takeWhile, dropWhile, unique, uniqueBy, allDifferent, allDifferentBy, insertAt, setIf, setAt, remove, updateIf, updateAt, updateIfIndex, removeAt, removeIfIndex, removeWhen, swapAt, stableSortWith , intercalate, transpose, subsequences, permutations, interweave, cartesianProduct, uniquePairs , foldl1, foldr1, indexedFoldl, indexedFoldr, Step(..), stoppableFoldl , scanl, scanl1, scanr, scanr1, mapAccuml, mapAccumr, unfoldr, iterate, initialize, cycle, reverseRange @@ -17,7 +17,7 @@ module List.Extra exposing # Basics -@docs last, init, getAt, cons, uncons, unconsLast, push, appendTo, prependTo, maximumBy, maximumWith, minimumBy, minimumWith, andMap, andThen, reverseMap, takeWhile, dropWhile, unique, uniqueBy, allDifferent, allDifferentBy, setIf, setAt, remove, updateIf, updateAt, updateIfIndex, removeAt, removeIfIndex, removeWhen, swapAt, stableSortWith +@docs last, init, getAt, cons, uncons, unconsLast, push, appendTo, prependTo, maximumBy, maximumWith, minimumBy, minimumWith, andMap, andThen, reverseMap, takeWhile, dropWhile, unique, uniqueBy, allDifferent, allDifferentBy, insertAt, setIf, setAt, remove, updateIf, updateAt, updateIfIndex, removeAt, removeIfIndex, removeWhen, swapAt, stableSortWith # List transformations @@ -831,6 +831,43 @@ count predicate = 0 +{-| Insert an element at a given index. +If the index is out of bounds, nothing is changed. + + [ 'a', 'c' ] |> insertAt 1 'b' + --> [ 'a', 'b', 'c' ] + + [ 'a', 'c' ] |> insertAt -1 'b' + --> [ 'a', 'c' ] + + [ 'a', 'c' ] |> insertAt 100 'b' + --> [ 'a', 'c' ] + +-} +insertAt : Int -> a -> List a -> List a +insertAt index value list = + if index <= -1 then + list + + else + insertAtHelp value list index list [] + + +insertAtHelp : a -> List a -> Int -> List a -> List a -> List a +insertAtHelp value list i rest acc = + if i == 0 then + List.foldl (::) (value :: rest) acc + + else + case rest of + [] -> + -- index > length list + list + + head :: newRest -> + insertAtHelp value list (i - 1) newRest (head :: acc) + + {-| Replace all values that satisfy a predicate with a replacement value. -} setIf : (a -> Bool) -> a -> List a -> List a @@ -1596,7 +1633,8 @@ splitWhen predicate list = {-| Take the last _n_ members of a list. - takeRight 2 [ 1, 2, 3, 4, 5 ] == [ 4, 5 ] + takeRight 2 [ 1, 2, 3, 4, 5 ] + --> [ 4, 5 ] -} takeRight : Int -> List a -> List a @@ -1609,7 +1647,8 @@ takeRight n lst = {-| Drop the last _n_ members of a list. - dropRight 2 [ 1, 2, 3, 4, 5 ] == [ 1, 2, 3 ] + dropRight 2 [ 1, 2, 3, 4, 5 ] + --> [ 1, 2, 3 ] -} dropRight : Int -> List a -> List a diff --git a/tests/ListTests.elm b/tests/ListTests.elm index f9ff779..6ff7c74 100644 --- a/tests/ListTests.elm +++ b/tests/ListTests.elm @@ -1,7 +1,7 @@ module ListTests exposing (all) import Expect -import Fuzz +import Fuzz exposing (Fuzzer) import List.Extra exposing (Step(..)) import Test exposing (Test, describe, fuzz, fuzz2, fuzz3, test) @@ -1001,6 +1001,36 @@ all = Expect.equal (List.Extra.minimumBy (\x -> x.val) [ { id = 1, val = 2 }, { id = 2, val = 1 }, { id = 3, val = 1 } ]) (Just { id = 2, val = 1 }) ] + , describe "insertAt" + [ fuzz3 + negativeIntFuzzer + Fuzz.int + (Fuzz.list Fuzz.int) + "negative index returns original list" + <| + \negativeIndex value list -> + List.Extra.insertAt negativeIndex value list + |> Expect.equalLists list + , fuzz2 + (Fuzz.intRange 0 4) + (Fuzz.listOfLengthBetween 4 10 (Fuzz.intRange 0 10)) + "index in bounds (0 <= index <= length) inserts in the right place in the list" + <| + \goodIndex list -> + -- -1 is guaranteed to not be in the fuzzed input list + List.Extra.insertAt goodIndex -1 list + |> List.Extra.removeAt goodIndex + |> Expect.equalLists list + , fuzz3 + (Fuzz.intRange 5 10) + Fuzz.int + (Fuzz.listOfLengthBetween 0 4 Fuzz.int) + "index out of bounds returns original list" + <| + \badPositiveIndex value list -> + List.Extra.insertAt badPositiveIndex value list + |> Expect.equalLists list + ] , describe "setIf" [ test "empty list" <| \() -> @@ -1204,3 +1234,16 @@ all = |> Expect.equal 50 ] ] + + +negativeIntFuzzer : Fuzzer Int +negativeIntFuzzer = + Fuzz.int + |> Fuzz.map + (\n -> + if n == 0 then + -1 + + else + -(abs n) + )