diff --git a/compiler/test/stdlib/list.test.gr b/compiler/test/stdlib/list.test.gr index 5b24ea1cf..69b35a22e 100644 --- a/compiler/test/stdlib/list.test.gr +++ b/compiler/test/stdlib/list.test.gr @@ -88,6 +88,19 @@ assert filteri((x, i) => i + x > 2, list) == [2, 3] assert filteri((x, i) => x == 3, list) == [3] assert filteri((x, i) => x == 3, []) == [] +// List.filterMap + +assert List.filterMap(x => Some(x), list) == list +assert List.filterMap(x => if (x == 3) Some(4) else None, list) == [4] +assert List.filterMap(x => None, list) == [] + +// List.filterMapi +assert List.filterMapi((x, i) => Some(x), list) == list +assert List.filterMapi((x, i) => if (x == 3) Some(4) else None, list) == [4] +assert List.filterMapi((x, i) => None, list) == [] +assert List.filterMapi((x, i) => if (i != 0) Some(i) else None, list) == [1, 2] +assert List.filterMapi((x, i) => if (i != 0) Some(x) else None, list) == [2, 3] + // List.reject assert reject(x => x > 0, list) == [] @@ -207,6 +220,13 @@ assert findIndex(x => x == 1, list) == Some(0) assert findIndex(x => x == 2, list) == Some(1) assert findIndex(x => false, list) == None +// List.findMap +let duplicateList = [(1, 'a'), (2, 'b'), (1, 'c')] +assert List.findMap(((k, v)) => if (k == 1) Some(v) else None, duplicateList) == + Some('a') +assert List.findMap(x => if (x == 2) Some(x) else None, list) == Some(2) +assert List.findMap(x => None, list) == None + // List.product let listA = [1, 2] diff --git a/stdlib/list.gr b/stdlib/list.gr index 83ddd99a7..f469e08a2 100644 --- a/stdlib/list.gr +++ b/stdlib/list.gr @@ -217,6 +217,56 @@ provide let mapi = (fn, list) => { iter(fn, list, 0) } +/** + * Produces a new list initialized with the results of a mapper function + * called on each element of the input list. + * The mapper function can return `None` to exclude the element from the new list. + * + * @param fn: The mapper function to call on each element, where the value returned will be used to initialize the element in the new list + * @param list: The list to iterate + * @returns The new list with filtered mapped values + * + * @example List.filterMap(x => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"] + * + * @since v0.7.0 + */ +provide let rec filterMap = (fn, list) => { + match (list) { + [] => [], + [first, ...rest] => match (fn(first)) { + Some(v) => [v, ...filterMap(fn, rest)], + None => filterMap(fn, rest), + }, + } +} + +/** + * Produces a new list initialized with the results of a mapper function + * called on each element of the input list and its index. + * The mapper function can return `None` to exclude the element from the new list. + * + * @param fn: The mapper function to call on each element, where the value returned will be used to initialize the element in the new list + * @param list: The list to iterate + * @returns The new list with filtered mapped values + * + * @example List.filterMapi((x, i) => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"] + * @example List.filterMapi((x, i) => if (i == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["1"] + * + * @since v0.7.0 + */ +provide let filterMapi = (fn, list) => { + let rec iter = (fn, list, index) => { + match (list) { + [] => [], + [first, ...rest] => match (fn(first, index)) { + Some(v) => [v, ...iter(fn, rest, index + 1)], + None => iter(fn, rest, index + 1), + }, + } + } + iter(fn, list, 0) +} + /** * Produces a new list by calling a function on each element * of the input list. Each iteration produces an intermediate @@ -242,7 +292,7 @@ provide let rec flatMap = (fn, list) => { * * @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition * @param list: The list to check - * @returns `true` if all elements satify the condition or `false` otherwise + * @returns `true` if all elements satisfy the condition or `false` otherwise * * @since v0.1.0 */ @@ -260,7 +310,7 @@ provide let rec every = (fn, list) => { * * @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition * @param list: The list to iterate - * @returns `true` if one or more elements satify the condition or `false` otherwise + * @returns `true` if one or more elements satisfy the condition or `false` otherwise * * @since v0.1.0 */ @@ -769,7 +819,7 @@ provide let rec takeWhile = (fn, list) => { } /** - * Finds the first element in a list that satifies the given condition. + * Finds the first element in a list that satisfies the given condition. * * @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition * @param list: The list to search @@ -787,7 +837,7 @@ provide let rec find = (fn, list) => { } /** - * Finds the first index in a list where the element satifies the given condition. + * Finds the first index in a list where the element satisfies the given condition. * * @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition * @param list: The list to search @@ -808,6 +858,31 @@ provide let findIndex = (fn, list) => { findItemIndex(list, 0) } +/** + * Finds the first element in a list that satisfies the given condition and + * returns the result of applying a mapper function to it. + * + * @param fn: The function to call on each element, where the returned value indicates if the element satisfies the condition + * @param list: The list to search + * @returns `Some(mapped)` containing the first value found with the given mapping or `None` otherwise + * + * @example + * let jsonObject = [(1, 'a'), (2, 'b'), (1, 'c')] + * let getItem = (key, obj) => List.findMap(((k, v)) => if (k == key) Some(v) else None, obj) + * assert getItem(1, jsonObject) == Some('a') + * + * @since v0.7.0 + */ +provide let rec findMap = (fn, list) => { + match (list) { + [] => None, + [first, ...rest] => match (fn(first)) { + None => findMap(fn, rest), + Some(v) => Some(v), + }, + } +} + /** * Combines two lists into a Cartesian product of tuples containing * all ordered pairs `(a, b)`. diff --git a/stdlib/list.md b/stdlib/list.md index 62b755974..e5a48b070 100644 --- a/stdlib/list.md +++ b/stdlib/list.md @@ -343,6 +343,78 @@ Returns: |----|-----------| |`List`|The new list with mapped values| +### List.**filterMap** + +
+Added in next +No other changes yet. +
+ +```grain +filterMap : (fn: (a => Option), list: List) => List +``` + +Produces a new list initialized with the results of a mapper function +called on each element of the input list. +The mapper function can return `None` to exclude the element from the new list. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`fn`|`a => Option`|The mapper function to call on each element, where the value returned will be used to initialize the element in the new list| +|`list`|`List`|The list to iterate| + +Returns: + +|type|description| +|----|-----------| +|`List`|The new list with filtered mapped values| + +Examples: + +```grain +List.filterMap(x => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"] +``` + +### List.**filterMapi** + +
+Added in next +No other changes yet. +
+ +```grain +filterMapi : (fn: ((a, Number) => Option), list: List
) => List +``` + +Produces a new list initialized with the results of a mapper function +called on each element of the input list and its index. +The mapper function can return `None` to exclude the element from the new list. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`fn`|`(a, Number) => Option`|The mapper function to call on each element, where the value returned will be used to initialize the element in the new list| +|`list`|`List`|The list to iterate| + +Returns: + +|type|description| +|----|-----------| +|`List`|The new list with filtered mapped values| + +Examples: + +```grain +List.filterMapi((x, i) => if (x % 2 == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["2", "4"] +``` + +```grain +List.filterMapi((x, i) => if (i == 0) Some(toString(x)) else None, [1, 2, 3, 4]) == ["1"] +``` + ### List.**flatMap**
@@ -397,7 +469,7 @@ Returns: |type|description| |----|-----------| -|`Bool`|`true` if all elements satify the condition or `false` otherwise| +|`Bool`|`true` if all elements satisfy the condition or `false` otherwise| ### List.**some** @@ -424,7 +496,7 @@ Returns: |type|description| |----|-----------| -|`Bool`|`true` if one or more elements satify the condition or `false` otherwise| +|`Bool`|`true` if one or more elements satisfy the condition or `false` otherwise| ### List.**forEach** @@ -1124,7 +1196,7 @@ Returns: find : (fn: (a => Bool), list: List) => Option ``` -Finds the first element in a list that satifies the given condition. +Finds the first element in a list that satisfies the given condition. Parameters: @@ -1158,7 +1230,7 @@ Returns: findIndex : (fn: (a => Bool), list: List) => Option ``` -Finds the first index in a list where the element satifies the given condition. +Finds the first index in a list where the element satisfies the given condition. Parameters: @@ -1173,6 +1245,41 @@ Returns: |----|-----------| |`Option`|`Some(index)` containing the index of the first element found or `None` otherwise| +### List.**findMap** + +
+Added in next +No other changes yet. +
+ +```grain +findMap : (fn: (a => Option), list: List
) => Option +``` + +Finds the first element in a list that satisfies the given condition and +returns the result of applying a mapper function to it. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`fn`|`a => Option`|The function to call on each element, where the returned value indicates if the element satisfies the condition| +|`list`|`List`|The list to search| + +Returns: + +|type|description| +|----|-----------| +|`Option`|`Some(mapped)` containing the first value found with the given mapping or `None` otherwise| + +Examples: + +```grain +let jsonObject = [(1, 'a'), (2, 'b'), (1, 'c')] +let getItem = (key, obj) => List.findMap(((k, v)) => if (k == key) Some(v) else None, obj) +assert getItem(1, jsonObject) == Some('a') +``` + ### List.**product**