Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add -to-head and -shuffle #404

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Copy link
Collaborator

Choose a reason for hiding this comment

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

README.md is autogenerated from function docstrings, the top three examples of each defexamples in dev/examples.el, and readme-template.md. So the docstrings and first three examples should be README.md-quality, and this file should not be edited by hand.

Expand Up @@ -294,6 +294,8 @@ Other list functions not fit to be classified elsewhere.
* [`-last-item`](#-last-item-list) `(list)`
* [`-butlast`](#-butlast-list) `(list)`
* [`-sort`](#-sort-comparator-list) `(comparator list)`
* [`-to-head`](#-to-head-n-list) `(n list)`
* [`-shuffle`](#-shuffle-list) `(list)`
* [`-list`](#-list-arg) `(arg)`
* [`-fix`](#-fix-fn-list) `(fn list)`

Expand Down Expand Up @@ -2317,6 +2319,29 @@ if the first element should sort before the second.
(--sort (< it other) '(3 1 2)) ;; => (1 2 3)
```

#### -to-head `(n list)`

Return a new list that move the element at `n`th to the head of old `list`.

```el
(-to-head 3 '(1 2 3 4 5)) ;; => (4 1 2 3 5)
(-to-head 5 '(1 2 3 4 5)) ;; peculiar error
Copy link
Collaborator

Choose a reason for hiding this comment

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

BTW, peculiar error means there is a bug in our code, so it should not happen or appear in our docs.

(let ((l '(1 2 3 4 5))) (list (-to-head 2 l) l)) ;; => ((3 1 2 4 5) (1 2 3 4 5))
```

#### -shuffle `(list)`

Return a new shuffled `list`.

The returned list is shuffled by using Fisher-Yates' Algorithm. See
https://en.wikipedia.org/wiki/Fisher-Yates_shuffle for more details.

```el
(progn (random "dash1") (-shuffle '(1 2 3 4 5 6 7))) ;; => (2 7 6 4 5 1 3)
(progn (random "dash2") (-shuffle '(1 2 3 4 5 6 7))) ;; => (1 5 2 4 3 7 6)
(let ((l '(1 2 3 4 5 6 7))) (random "dash3") (list (-shuffle '(1 2 3 4 5 6 7)) l)) ;; => ((3 4 1 5 7 6 2) (1 2 3 4 5 6 7))
```

#### -list `(arg)`

Ensure `arg` is a list.
Expand Down
24 changes: 24 additions & 0 deletions dash.el
Expand Up @@ -3238,6 +3238,30 @@ if the first element should sort before the second."
(declare (debug (def-form form)))
`(-sort (lambda (it other) (ignore it other) ,form) ,list))

(defun -to-head (n list)
"Return a new list that move the element at Nth to the head of old LIST."
Copy link
Collaborator

Choose a reason for hiding this comment

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

Grammatical nits:
move -> moves
element at Nth -> Nth element
head of old LIST -> head of the old LIST

But I think we can make this even shorter, to fit even within the default 65 column suggestion:

"Return a copy of LIST with its Nth item moved to the front."

WDYT?

(declare (pure t) (side-effect-free t))
(if (> n (1- (length list)))
(error "Index %d out of the range of list %S" n list))
Comment on lines +3244 to +3245
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it better to signal here, or return the original list unchanged? There is a lot of precedent for the latter behaviour, e.g. nth, -remove-at, etc.

BTW, there's a special args-out-of-range error for out-of-range sequence access.

(let* ((head (-take n list))
(rest (-drop n list))
Comment on lines +3246 to +3247
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about using -split-at here?

(target (pop rest)))
(cons target (nconc head rest))))

(defun -shuffle (list &optional rng)
"Return a new shuffled LIST, shuffling using RNG.

The returned list is shuffled by using Fisher-Yates' Algorithm. See
https://en.wikipedia.org/wiki/Fisher-Yates_shuffle for more details."
Comment on lines +3254 to +3255
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the implementation is better described in a comment than the docstring.

(declare (pure t) (side-effect-free t))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shuffling returns a different result each time, so it is not pure, and it is side-effect-free only if we remove the rng argument like I suggest.

(let* ((len (length list))
(random-nums (-map (or rng #'random) (number-sequence len 1 -1)))
result)
(--each random-nums
(setq list (-to-head it list))
(push (pop list) result))
(nreverse result)))

(defun -list (&optional arg &rest args)
"Ensure ARG is a list.
If ARG is already a list, return it as is (not a copy).
Expand Down
43 changes: 43 additions & 0 deletions dash.texi
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto re: autogenerating this file.

Expand Up @@ -3478,6 +3478,49 @@ if the first element should sort before the second.
@end example
@end defun

@anchor{-to-head}
@defun -to-head (n list)
Return a new list that move the element at Nth to the head of old @var{list}.

@example
@group
(-to-head 3 '(1 2 3 4 5))
@result{} (4 1 2 3 5)
@end group
@group
(-to-head 5 '(1 2 3 4 5))
@error{} peculiar error
@end group
@group
(let ((l '(1 2 3 4 5))) (list (-to-head 2 l) l))
@result{} ((3 1 2 4 5) (1 2 3 4 5))
@end group
@end example
@end defun

@anchor{-shuffle}
@defun -shuffle (list)
Return a new shuffled @var{list}.

The returned list is shuffled by using Fisher-Yates' Algorithm. See
https://en.wikipedia.org/wiki/Fisher-Yates_shuffle for more details.

@example
@group
(progn (random "dash1") (-shuffle '(1 2 3 4 5 6 7)))
@result{} (2 7 6 4 5 1 3)
@end group
@group
(progn (random "dash2") (-shuffle '(1 2 3 4 5 6 7)))
@result{} (1 5 2 4 3 7 6)
@end group
@group
(let ((l '(1 2 3 4 5 6 7))) (random "dash3") (list (-shuffle '(1 2 3 4 5 6 7)) l))
@result{} ((3 4 1 5 7 6 2) (1 2 3 4 5 6 7))
@end group
@end example
@end defun

@anchor{-list}
@defun -list (arg)
Ensure @var{arg} is a list.
Expand Down
24 changes: 24 additions & 0 deletions dev/examples.el
Expand Up @@ -53,6 +53,17 @@
(defun even? (num) (= 0 (% num 2)))
(defun square (num) (* num num))

(defun make-xorshift32-rng (seed)
(let ((state (list seed))
(uint32-max (- (expt 2 32) 1)))
(lambda (limit)
(let* ((seed (car state))
(step1 (logxor seed (logand uint32-max (ash seed 13))))
(step2 (logxor step1 (logand uint32-max (ash seed -17))))
(final (logxor step2 (logand uint32-max (ash step2 5)))))
(setcar state final)
(mod final limit)))))

(def-example-group "Maps"
"Functions in this category take a transforming function, which
is then applied sequentially to each or selected elements of the
Expand Down Expand Up @@ -1921,6 +1932,19 @@ related predicates."
(--sort (< it other) '(3 1 2)) => '(1 2 3)
(let ((l '(3 1 2))) (-sort '> l) l) => '(3 1 2))

(defexamples -to-head
(-to-head 3 '(1 2 3 4 5)) => '(4 1 2 3 5)
(-to-head 5 '(1 2 3 4 5)) !!> error
(let ((l '(1 2 3 4 5)))
(list (-to-head 2 l) l)) => '((3 1 2 4 5) (1 2 3 4 5)))

(defexamples -shuffle
(-shuffle '(1 2 3 4 5 6 7) (make-xorshift32-rng #xcafe)) => '(7 6 1 2 3 5 4)
(-shuffle '(1 2 3 4 5 6 7) (make-xorshift32-rng #xbeef)) => '(4 3 2 5 6 7 1)
(let ((l '(1 2 3 4 5 6 7)))
(list (-shuffle '(1 2 3 4 5 6 7) (make-xorshift32-rng #xdead)) l)) => '((3 4 6 5 1 2 7) (1 2 3 4 5 6 7))
Comment on lines +1942 to +1945
Copy link
Collaborator

Choose a reason for hiding this comment

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

make-xorshift32-rng is completely opaque to the user, so if we really can't do without it, it should at least not appear in our top examples/docs. Its name should also follow standard Elisp conventions with a file prefix and internal double hyphen --.

(-shuffle nil) => nil)

(defexamples -list
(-list 1) => '(1)
(-list '()) => '()
Expand Down