Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions benchmarks/src/Benchmarks.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
)
Expand Down
115 changes: 115 additions & 0 deletions benchmarks/src/List/Extra/InsertAt.elm
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Collaborator

@lue-bird lue-bird Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot to mention that in my benchmarks this instead counts down from index and checks when it goes to 0. This avoids the extra i argument but should not be that much faster (not sure if comparisons with a literal are only optimized in eol2 or elm make)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 []
47 changes: 43 additions & 4 deletions src/List/Extra.elm
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
45 changes: 44 additions & 1 deletion tests/ListTests.elm
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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" <|
\() ->
Expand Down Expand Up @@ -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)
)
Loading