Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions documentation/source/sequence-utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Overview

The sequence-utilities module implements some useful methods on sequences.

- :func:`zip`
- :func:`zip-all`
- :func:`zip-with`


Reference
---------
Expand Down Expand Up @@ -363,3 +367,148 @@ far. https://github.com/dylan-lang/collection-extensions/issues/2
:parameter list: An instance of :drm:`<list>`.
:parameter elt: An instance of :drm:`<object>`.
:value new-list: An instance of :drm:`<list>`.

zip
^^^

.. function:: zip

The `zip` function combines multiple iterables element-wise into
tuples, stopping at the shortest iterable.

:signature:

zip (*sequence1* *sequence2* #key *key1* *key2*) => (*zipped*)

:parameter sequence1:

An instance of :drm:`<sequence>`.

:parameter sequence2:

An instance of :drm:`<sequence>`.

:parameter #key key1:

An instance of :drm:`<function>`. Default value:
:drm:`identity`.

:parameter #key key2:

An instance of :drm:`<function>`. Default value:
:drm:`identity`.

:value zipped:

An instance of :drm:`<sequence>`.

:description:

The `zip` function is often classified as a *high-order function* and
a *sequence transformation function*.

The function operates on two sequences (like :drm:`range`,
`array`, :drm:`list`, `deque` or `string`) and
transforms them in a :drm:`<sequence>` of pairs, each element from
Copy link
Member

Choose a reason for hiding this comment

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

I would make these references to the types: :drm:`<range>`, :drm:`<vector>`, :drm:`<list>`, :drm:`<deque>`, or :drm:`<string>`.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did it, in a previous commit, but wrong. Thanks.

one of the sequences.

If *key1* or *key2* are not provided, they default to the function
:drm:`identity` returning the object. If provided, this function
transforms the element before collecting it.

If the sequences provided have different lengths, func:`zip`
automatically truncates the output to the length of the shorter
sequence.


:example:

.. code-block:: dylan

let a = #(1, 2);
let b = #('a', 'b');

let zipped = zip(a, b);
format-out("%=", zipped); // Output: #(#(1, 'a'), #(2, 'b'))
Copy link
Member

Choose a reason for hiding this comment

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

This is fine, but I prefer the following style, because it doesn't have the distraction of the format-out call.

let a = #(1, 2);
let b = #('a', 'b');
zip(a, b)
// => #(#(1, 'a'), #(2, 'b'))


:example:

.. code-block:: dylan

let a = #('a', 'b');
let b = #(3, 4);

let zipped = zip(a, b, key2: odd?);
format-out("%=", zipped); // Output: #(#('a', #t), ('b', #f))

zip-with
^^^^^^^^

.. function:: zip-with


:signature:

zip-with (function collection #rest more-collections) ⇒ new-collection

:parameter function:

An instance of :drm:`<function>`.

:parameter collection:

An instance of :drm:`<collection>`.

:parameter more-collections:

An instance of :drm:`<collection>`.

:value new-collection:

An instance of :drm:`<collection>`.

:example:

.. code-block:: dylan

let a = #(100, 200, 300);
let b = #(1, 2, 3);

let zipped = zip-with(\+, a, b);
format-out("%=", zipped); // Output: #(101, 202, 303)

:seealso:

:drm:`map`

zip-all
^^^^^^^

.. function:: zip-all

A :func:`zip` function that can take any number of iterables as
parameters.

:signature:

zip-all (sequences) => (zipped)

Copy link
Member

Choose a reason for hiding this comment

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

zip-all #rest *sequences* => *zipped-sequence*

:parameter sequences:

An instance of :drm:`<sequence>`.

:value zipped:

An instance of :drm:`<sequence>`.

:example:

.. code-block:: dylan

let a = #(1, 2, 3);
let b = #('a', 'b', 'c');
let c = #(#t, #f, #t);

let zipped = zip-all(a, b, c);

format-out("%=", zipped); // Output: #(#(1, 'a', #t), #(2, 'b', #f), #(3, 'c', #t))
3 changes: 3 additions & 0 deletions library.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module: dylan-user
define library collection-extensions
use dylan;
use common-dylan, import: { byte-vector };
use collections;
export heap, self-organizing-list, vector-search, subseq, sequence-diff;
export sde-vector;
export collection-utilities;
Expand Down Expand Up @@ -91,6 +92,7 @@ end module collection-utilities;

define module sequence-utilities
use dylan;
use collectors;
export push!, pop!;
export pair?, null?, list?;
export xpair, tabulate, list*, take, drop, last-pair;
Expand All @@ -100,5 +102,6 @@ define module sequence-utilities
export concatenate-map, pair-do, choose-map;
export partition, assoc, apair, alist-copy, alist-delete;
export satisfies, index, find, find-tail, precedes?;
export zip, zip-with, zip-all;
end module sequence-utilities;

26 changes: 26 additions & 0 deletions sequence-utils.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -588,3 +588,29 @@ define method precedes?(elt-1, elt-2, seq :: <sequence>,
not-found;
end block;
end method precedes?;

define function zip
Copy link
Member

Choose a reason for hiding this comment

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

Naming is hard, so I won't claim that this is definitely better, but one potential alternative is to call this function zip2 and call zip-all just zip so that it matches other popular languages (Python).

(seq1 :: <sequence>, seq2 :: <sequence>, #key key1 = identity, key2 = identity)
=> (zipped :: <sequence>)
collecting (as <sequence>)
for (e1 in seq1, e2 in seq2)
collect(list(key1(e1), key2(e2)))
end
end;
end;

define constant zip-with
= map;

define function zip-all
(#rest sequences) => (zipped :: <sequence>)
Copy link
Member

Choose a reason for hiding this comment

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

The name "sequences" is misleading here since this function will only work with lists. Should it work with sequences in general? (I think so.) Perhaps you can do it by calling forward-iteration-protocol on each sequence directly.

local method recur (seqs, zipped :: <list>)
if (any?(empty?, seqs))
reverse(zipped)
else
recur(map(tail, seqs),
add(zipped, map(head, seqs)))
end
end;
if (empty?(sequences)) #() else recur(sequences, #()) end
Copy link
Member

Choose a reason for hiding this comment

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

I am a big fan of the iterate macro, which would look something like this:

define function zip-all (#rest lists) => (zipped :: <list>)
  iterate loop (lists = lists, zipped = #())
    if (any?(empty?, lists))
      reverse!(zipped)
    else
      loop(map(tail, lists), pair(map(head, lists)))
    end if
  end iterate
end function

It basically combines the definition of the local method and the initial call to it.

end;
1 change: 1 addition & 0 deletions tests/collection-extensions-test-suite.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ define suite collection-extensions-test-suite
suite sequence-diff-suite;
suite collection-utilities-suite;
suite sequence-utilities-suite;
suite zip-suite;
end suite collection-extensions-test-suite;

65 changes: 65 additions & 0 deletions tests/sequence-utils-suite.dylan
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,68 @@ define suite sequence-utilities-suite
test push-test;
test pop-test;
end suite sequence-utilities-suite;

define test test-zip-all-empty-values
(description: "Testing zip-all with no arguments returns an empty list")
expect-equal(#(), zip-all())
end test;

define test test-zip-empty-sequences
(description: "Zip with empty sequences returns an empty list")
expect-equal(#(), zip(#(), #()))
end test;

define test test-zip-single-element
(description: "Test zip with single element sequences")
expect-equal(#(#(10, 20)), zip(#(10), #(20)))
end test;

define test test-zip-two-sequences-equal-length
(description: "Test zip with two sequences of equal length")
expect-equal(#(#(1, 'a'), #(2, 'b'), #(3, 'c')),
zip(#(1, 2, 3), #('a', 'b', 'c')))
end test;

define test test-zip-with-keys
(description: "Test zip passing key functons")
expect-equal(#(#(#f, #t), #(#t, #f)),
zip(#(1, 2), #(3, 4),
key1: even?, key2: odd?))
end;

define test test-zip-two-sequences-unequal-length
(description: "Test zip with two sequences of unequal length")
let expected = #(#(1, 'a'), #(2, 'b'));
expect-equal(expected, zip(#(1, 2), #('a', 'b', 'c')));
expect-equal(expected, zip(#(1, 2, 3), #('a', 'b')));
end test;

define test test-zip-with
(description: "Test zip with a math function")
expect-equal(#(7, 10, 13, 16),
zip-with(method (x, y) 2 * x + y end,
range(from: 1, to: 4),
range(from: 5, to: 8)))
end test;

define test test-zip-all
(description: "Testing zip with more than two sequences")
expect-equal(#(#(1, 'x', #t), #(2, 'y', #f)),
zip-all(#(1, 2, 3), #('x', 'y', 'z'), #(#t, #f)),
"Testing zip with three sequences of unequal length");
expect-equal(#(#(1, 'x', #t), #(2, 'y', #f)),
zip-all(#(1, 2), #('x', 'y', 'z'), #(#t, #f)),
"Testing zip with three sequences of unequal length");
end test;


define suite zip-suite
(description: "Test suite for zip function")
test test-zip-all-empty-values;
test test-zip-empty-sequences;
test test-zip-single-element;
test test-zip-two-sequences-equal-length;
test test-zip-two-sequences-unequal-length;
test test-zip-with;
test test-zip-all;
end suite;
Loading