Skip to content

Commit b6dd5e3

Browse files
authored
implement Iter::group_by (#1998)
* implement Iter::group_by * added suggested improvements * new version using a Map
1 parent b7a1f17 commit b6dd5e3

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

builtin/builtin.mbti

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ impl Iter {
233233
flat_map[T, R](Self[T], (T) -> Self[R]) -> Self[R]
234234
flatten[T](Self[Self[T]]) -> Self[T]
235235
fold[T, B](Self[T], init~ : B, (B, T) -> B) -> B
236+
group_by[T, K : Eq + Hash](Self[T], (T) -> K) -> Map[K, Array[T]]
236237
head[A](Self[A]) -> A?
237238
intersperse[A](Self[A], A) -> Self[A]
238239
iter[T](Self[T]) -> Self[T]

builtin/iter.mbt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,3 +998,41 @@ pub fn Iter::minimum[T : Compare](self : Iter[T]) -> T? {
998998
}
999999
res
10001000
}
1001+
1002+
///|
1003+
/// Groups elements of an iterator according to a discriminator function.
1004+
///
1005+
/// # Parameters
1006+
///
1007+
/// * `self` - The input iterator.
1008+
/// * `f` - The discriminator function that maps elements to keys.
1009+
///
1010+
/// # Returns
1011+
///
1012+
/// A Map where keys are the result of applying the discriminator function to elements,
1013+
/// and values are arrays containing all elements that share the same key.
1014+
///
1015+
/// # Example
1016+
///
1017+
/// ```moonbit
1018+
/// test "group_by" {
1019+
/// let iter = [1, 1, 2, 3, 2, 2, 1].iter()
1020+
/// let result = iter.group_by(fn(x) { x })
1021+
/// assert_eq!(result.get(1), Some([1, 1, 1]))
1022+
/// assert_eq!(result.get(2), Some([2, 2, 2]))
1023+
/// assert_eq!(result.get(3), Some([3]))
1024+
/// }
1025+
pub fn Iter::group_by[T, K : Eq + Hash](
1026+
self : Iter[T],
1027+
f : (T) -> K
1028+
) -> Map[K, Array[T]] {
1029+
let result = Map::new()
1030+
for element in self {
1031+
let key = f(element)
1032+
match result.get(key) {
1033+
Some(arr) => result.set(key, arr + [element])
1034+
None => result.set(key, [element])
1035+
}
1036+
}
1037+
result
1038+
}

builtin/iter_test.mbt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,3 +758,70 @@ test "Float::until negative step" {
758758
let result = 10.0.until(0.0, step=-2.0).collect()
759759
inspect!(result, content="[10, 8, 6, 4, 2]")
760760
}
761+
762+
///|
763+
test "group_by with consecutive identical elements" {
764+
let iter = [1, 1, 2, 2, 3, 3].iter()
765+
let grouped = iter.group_by(fn(x) { x })
766+
assert_eq!(grouped.get(1), Some([1, 1]))
767+
assert_eq!(grouped.get(2), Some([2, 2]))
768+
assert_eq!(grouped.get(3), Some([3, 3]))
769+
}
770+
771+
///|
772+
test "group_by with non-consecutive identical elements" {
773+
let iter = [1, 2, 1, 3, 2, 1].iter()
774+
let grouped = iter.group_by(fn(x) { x })
775+
assert_eq!(grouped.get(1), Some([1, 1, 1]))
776+
assert_eq!(grouped.get(2), Some([2, 2]))
777+
assert_eq!(grouped.get(3), Some([3]))
778+
}
779+
780+
///|
781+
test "group_by with empty input" {
782+
let iter : Iter[Int] = Iter::empty()
783+
let grouped = iter.group_by(fn(x) { x })
784+
assert_eq!(grouped.size(), 0)
785+
}
786+
787+
///|
788+
test "group_by with single element input" {
789+
let iter = [42].iter()
790+
let grouped = iter.group_by(fn(x) { x })
791+
assert_eq!(grouped.get(42), Some([42]))
792+
}
793+
794+
///|
795+
test "group_by with custom key function" {
796+
let iter = [1, 2, 3, 4].iter()
797+
let grouped = iter.group_by(fn(x) { x % 2 })
798+
assert_eq!(grouped.get(0), Some([2, 4]))
799+
assert_eq!(grouped.get(1), Some([1, 3]))
800+
}
801+
802+
///|
803+
test "group_by with strings" {
804+
let iter = ["apple", "avocado", "banana", "cherry", "blueberry"].iter()
805+
let grouped = iter.group_by(fn(s) { s[0] })
806+
assert_eq!(grouped.get('a'), Some(["apple", "avocado"]))
807+
assert_eq!(grouped.get('b'), Some(["banana", "blueberry"]))
808+
assert_eq!(grouped.get('c'), Some(["cherry"]))
809+
}
810+
811+
///|
812+
test "group_by with complex objects" {
813+
struct Person {
814+
name : String
815+
age : Int
816+
}
817+
let people = [
818+
Person::{ name: "Alice", age: 25 },
819+
Person::{ name: "Bob", age: 25 },
820+
Person::{ name: "Charlie", age: 30 },
821+
Person::{ name: "Dave", age: 35 },
822+
Person::{ name: "Eve", age: 30 },
823+
].iter()
824+
let grouped = people.group_by(fn(p) { p.age })
825+
let groups = grouped.values().map(fn(a) { a.map(fn(p) { p.name }) }).collect()
826+
assert_eq!(groups, [["Alice", "Bob"], ["Charlie", "Eve"], ["Dave"]])
827+
}

0 commit comments

Comments
 (0)