Skip to content

Commit 6dd9680

Browse files
authored
feat(stdlib)!: List.rotate wraparound for count > length (#1558)
Closes #1522
1 parent 1bffc82 commit 6dd9680

File tree

3 files changed

+49
-15
lines changed

3 files changed

+49
-15
lines changed

compiler/test/stdlib/list.test.gr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ assert rotate(0, list) == list
135135
assert rotate(1, list) == [2, 3, 1]
136136
assert rotate(2, list) == [3, 1, 2]
137137
assert rotate(-2, list) == [2, 3, 1]
138+
let l = [1, 2, 3, 4, 5]
139+
assert rotate(5, l) == l
140+
assert rotate(7, l) == [3, 4, 5, 1, 2]
141+
assert rotate(-18, l) == [3, 4, 5, 1, 2]
142+
assert rotate(1, []) == []
143+
assert rotate(0, []) == []
138144

139145
// List.unique
140146

stdlib/list.gr

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -523,14 +523,37 @@ export let part = (count, list) => {
523523
*
524524
* @example List.rotate(2, [1, 2, 3, 4, 5]) // [3, 4, 5, 1, 2]
525525
* @example List.rotate(-1, [1, 2, 3, 4, 5]) // [5, 1, 2, 3, 4]
526-
*
527-
* @throws Failure(String): When the list doesn't contain at least the required amount of elements
526+
* @example List.rotate(-7, [1, 2, 3, 4, 5]) // [4, 5, 1, 2, 3]
528527
*
529528
* @since v0.1.0
530-
*/
531-
export let rotate = (n, list) => {
532-
let (beginning, end) =
533-
if (n >= 0) part(n, list) else part(length(list) + n, list)
529+
*
530+
* @history v0.6.0: No longer throws if `count` outside list length bounds
531+
*/
532+
export let rotate = (count, list) => {
533+
// Optimization: only compute the list length (O(n)) if the count is negative
534+
// or if the entire list is exhausted when partitioning. This should improve
535+
// performance if the list is very long but the count is small
536+
let getSplitI = () => {
537+
let len = length(list)
538+
if (len == 0) 0 else count % len
539+
}
540+
let (beginning, end) = if (count >= 0) {
541+
let rec iter = (list1, list2, count) => {
542+
match (list2) {
543+
[] => if (count > 0) None else Some((list1, list2)),
544+
[first, ...rest] =>
545+
if (count > 0) iter([first, ...list1], rest, count - 1)
546+
else Some((list1, list2)),
547+
}
548+
}
549+
let res = iter([], list, count)
550+
match (res) {
551+
None => part(getSplitI(), list),
552+
Some((pt1, pt2)) => (reverse(pt1), pt2),
553+
}
554+
} else {
555+
part(getSplitI(), list)
556+
}
534557
append(end, beginning)
535558
}
536559

stdlib/list.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -764,9 +764,16 @@ Throws:
764764

765765
### List.**rotate**
766766

767-
<details disabled>
768-
<summary tabindex="-1">Added in <code>0.1.0</code></summary>
769-
No other changes yet.
767+
<details>
768+
<summary>Added in <code>0.1.0</code></summary>
769+
<table>
770+
<thead>
771+
<tr><th>version</th><th>changes</th></tr>
772+
</thead>
773+
<tbody>
774+
<tr><td><code>next</code></td><td>No longer throws if `count` outside list length bounds</td></tr>
775+
</tbody>
776+
</table>
770777
</details>
771778

772779
```grain
@@ -786,12 +793,6 @@ Parameters:
786793
|`n`|`Number`|The number of elements to rotate by|
787794
|`list`|`List<a>`|The list to be rotated|
788795

789-
Throws:
790-
791-
`Failure(String)`
792-
793-
* When the list doesn't contain at least the required amount of elements
794-
795796
Examples:
796797

797798
```grain
@@ -802,6 +803,10 @@ List.rotate(2, [1, 2, 3, 4, 5]) // [3, 4, 5, 1, 2]
802803
List.rotate(-1, [1, 2, 3, 4, 5]) // [5, 1, 2, 3, 4]
803804
```
804805

806+
```grain
807+
List.rotate(-7, [1, 2, 3, 4, 5]) // [4, 5, 1, 2, 3]
808+
```
809+
805810
### List.**unique**
806811

807812
<details>

0 commit comments

Comments
 (0)