Hone logo
Hone
Problems

Implementing Generic Map Operations in Go

This challenge focuses on implementing common functional programming-style map operations for Go slices, but with a twist: you'll need to leverage Go's generics to make these operations work with any data type. This is a fundamental skill for writing more flexible and reusable Go code, mirroring capabilities found in other languages.

Problem Description

Your task is to implement two generic functions for operating on slices in Go:

  1. Map: This function should take a slice and a mapping function as input. It should apply the mapping function to each element of the input slice and return a new slice containing the results.
  2. Filter: This function should take a slice and a predicate function (a function that returns a boolean) as input. It should return a new slice containing only the elements for which the predicate function returns true.

Key Requirements

  • Genericity: Both Map and Filter must be implemented using Go's generic type parameters. This means they should work with slices of any comparable type (e.g., int, string, float64, custom structs, etc.).
  • Immutability: The original input slices should not be modified. Both Map and Filter must return new slices.
  • Clear Function Signatures:
    • Map should have a signature like func Map[T, U any](slice []T, f func(T) U) []U.
    • Filter should have a signature like func Filter[T any](slice []T, p func(T) bool) []T.
  • Error Handling (Implicit): For this challenge, assume valid inputs. No explicit error handling for invalid function types or nil slices is required, but consider how your implementation would behave in such cases.

Expected Behavior

  • Map: For each element x in the input slice, f(x) is computed and added to the resulting slice. The order of elements in the output slice must match the order in the input slice.
  • Filter: For each element x in the input slice, if p(x) evaluates to true, x is added to the resulting slice. The order of elements in the output slice must match the order in the input slice.

Edge Cases

  • Empty Slices: Both functions should handle empty input slices gracefully, returning an empty slice.
  • Mapping to a Different Type: The Map function should be able to transform elements from one type to another (e.g., []int to []string).
  • Predicate returning false for all elements: Filter should return an empty slice if the predicate is false for every element.
  • Predicate returning true for all elements: Filter should return a new slice identical to the input slice if the predicate is true for every element.

Examples

Example 1: Mapping Integers to Strings

Input Slice: []int{1, 2, 3, 4, 5}
Mapping Function: func(i int) string { return fmt.Sprintf("Number: %d", i) }

Output: []string{"Number: 1", "Number: 2", "Number: 3", "Number: 4", "Number: 5"}
Explanation: Each integer in the input slice is converted into a formatted string.

Example 2: Filtering Even Numbers

Input Slice: []int{1, 2, 3, 4, 5, 6}
Predicate Function: func(i int) bool { return i%2 == 0 }

Output: []int{2, 4, 6}
Explanation: Only the even numbers from the input slice are included in the output slice.

Example 3: Mapping and Filtering Strings

Input Slice: []string{"apple", "banana", "cherry", "date", "elderberry"}
Mapping Function (to length): func(s string) int { return len(s) }
Predicate Function (length > 5): func(l int) bool { return l > 5 }

Intermediate (after Map): []int{5, 6, 6, 4, 10}
Output (after Filter): []int{6, 6, 10}
Explanation: First, the length of each string is calculated. Then, lengths greater than 5 are kept.

Example 4: Empty Slice Input

Input Slice: []float64{}
Mapping Function: func(f float64) float64 { return f * 2 }

Output: []float64{}
Explanation: An empty input slice results in an empty output slice.

Constraints

  • The input slices will be of any valid Go type for which generics can be applied (e.g., primitive types, structs).
  • The provided mapping and predicate functions will be valid for the element types of the input slices.
  • The size of the input slices will not exceed 1 million elements.
  • The time complexity of your Map and Filter functions should be O(n), where n is the number of elements in the input slice.
  • The space complexity for both functions should be O(k), where k is the number of elements in the output slice (which can be up to O(n)).

Notes

  • Remember to import the fmt package if you plan to use fmt.Sprintf in your testing or example functions.
  • Think about how to create and populate the new slices efficiently.
  • Consider the use of append and pre-allocating slice capacity for performance.
  • This exercise is about understanding and implementing Go's generic capabilities for common data manipulation patterns.
Loading editor...
go