Skip to content

Commit

Permalink
O(1) concatenation of singly- and doubly linked lists. (nim-lang#16362)
Browse files Browse the repository at this point in the history
* O(1) concatenation of singly- and doubly linked lists.

There is also new `toSinglyLinkedList` and `toDoublyLinkedList`
functions for conversion from `openArray`s, similarly
to `toHashSet` or `toTable`.

* Add `sequtils` import to runnable examples with `toSeq`.

* Added missing call to runnable examples.

* Add .since annotation, changelog, and tests.

* Rename `lists.concat` as an overload to `lists.append`.

* Renamed `append` to `add` in lists.

* Remove faulty `add` for `DoublyLinkedList`s.

* Improved tests for lists.

* `lists.add` moves the second list; added `lists.copy` for shallow copy.

* More tests for `lists.add` and `lists.copy`.

* More compact tests for lists with templates.

* List concatenation with copying (`add`) and moving (tentatively `addMove`)

* Renamed `addMove` to `addMoved`; renamed arguments according to the style guide.

* Added extended example to `lists.copy`.

* Corrected .since annotations to 1.6

* Add .since annotation, changelog, and tests.

* Rename `lists.concat` as an overload to `lists.append`.

* Renamed `append` to `add` in lists.

* Remove faulty `add` for `DoublyLinkedList`s.

* `lists.add` moves the second list; added `lists.copy` for shallow copy.

* More tests for `lists.add` and `lists.copy`.

* List concatenation with copying (`add`) and moving (tentatively `addMove`)

* Renamed `addMove` to `addMoved`; renamed arguments according to the style guide.

* Since declarations changed to (1,5,1).

* Add .since annotation, changelog, and tests.

* Rename `lists.concat` as an overload to `lists.append`.

* Renamed `append` to `add` in lists.

* Remove faulty `add` for `DoublyLinkedList`s.

* `lists.add` moves the second list; added `lists.copy` for shallow copy.

* More tests for `lists.add` and `lists.copy`.

* List concatenation with copying (`add`) and moving (tentatively `addMove`)

* Renamed `addMove` to `addMoved`; renamed arguments according to the style guide.

* Changelog update.

* Fix rebasing errors.

* Self-adding with `lists.addMove` results in a cycle now.
Added some extra tests.
  • Loading branch information
salvipeter authored and ardek66 committed Mar 26, 2021
1 parent 285e81b commit 97d52a1
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 2 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
- `echo` and `debugEcho` will now raise `IOError` if writing to stdout fails. Previous behavior
silently ignored errors. See #16366. Use `-d:nimLegacyEchoNoRaise` for previous behavior.

- Added new operations for singly- and doubly linked lists: `lists.toSinglyLinkedList`
and `lists.toDoublyLinkedList` convert from `openArray`s; `lists.copy` implements
shallow copying; `lists.add` concatenates two lists - an O(1) variation that consumes
its argument, `addMoved`, is also supplied.

## Language changes

- `nimscript` now handles `except Exception as e`.
Expand Down
148 changes: 147 additions & 1 deletion lib/pure/collections/lists.nim
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
## * `deques module <deques.html>`_ for double-ended queues
## * `sharedlist module <sharedlist.html>`_ for shared singly-linked lists

import std/private/since

when not defined(nimhygiene):
{.pragma: dirty.}
Expand Down Expand Up @@ -178,6 +179,26 @@ proc newSinglyLinkedNode*[T](value: T): <//>(SinglyLinkedNode[T]) =
new(result)
result.value = value

func toSinglyLinkedList*[T](elems: openArray[T]): SinglyLinkedList[T] {.since: (1, 5, 1).} =
## Creates a new `SinglyLinkedList` from members of `elems`.
runnableExamples:
import sequtils
let a = [1, 2, 3, 4, 5].toSinglyLinkedList
assert a.toSeq == [1, 2, 3, 4, 5]
result = initSinglyLinkedList[T]()
for elem in elems.items:
result.append(elem)

func toDoublyLinkedList*[T](elems: openArray[T]): DoublyLinkedList[T] {.since: (1, 5, 1).} =
## Creates a new `DoublyLinkedList` from members of `elems`.
runnableExamples:
import sequtils
let a = [1, 2, 3, 4, 5].toDoublyLinkedList
assert a.toSeq == [1, 2, 3, 4, 5]
result = initDoublyLinkedList[T]()
for elem in elems.items:
result.append(elem)

template itemsListImpl() {.dirty.} =
var it = L.head
while it != nil:
Expand Down Expand Up @@ -435,7 +456,60 @@ proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} =
assert a.contains(9)
prepend(L, newSinglyLinkedNode(value))


func copy*[T](a: SinglyLinkedList[T]): SinglyLinkedList[T] {.since: (1, 5, 1).} =
## Creates a shallow copy of `a`.
runnableExamples:
import sequtils
type Foo = ref object
x: int
var
f = Foo(x: 1)
a = [f].toSinglyLinkedList
let b = a.copy
a.add [f].toSinglyLinkedList
assert a.toSeq == [f, f]
assert b.toSeq == [f] # b isn't modified...
f.x = 42
assert a.head.value.x == 42
assert b.head.value.x == 42 # ... but the elements are not deep copied

let c = [1, 2, 3].toSinglyLinkedList
assert $c == $c.copy
result = initSinglyLinkedList[T]()
for x in a.items:
result.append(x)

proc addMoved*[T](a, b: var SinglyLinkedList[T]) {.since: (1, 5, 1).} =
## Moves `b` to the end of `a`. Efficiency: O(1).
## Note that `b` becomes empty after the operation unless it has the same address as `a`.
## Self-adding results in a cycle.
##
## See also:
## * `add proc <#add,T,T>`_
## for adding a copy of a list
runnableExamples:
import sequtils, std/enumerate, std/sugar
var
a = [1, 2, 3].toSinglyLinkedList
b = [4, 5].toSinglyLinkedList
c = [0, 1].toSinglyLinkedList
a.addMoved b
assert a.toSeq == [1, 2, 3, 4, 5]
assert b.toSeq == []
c.addMoved c
let s = collect:
for i, ci in enumerate(c):
if i == 6: break
ci
assert s == [0, 1, 0, 1, 0, 1]
if a.tail != nil:
a.tail.next = b.head
a.tail = b.tail
if a.head == nil:
a.head = b.head
if a.addr != b.addr:
b.head = nil
b.tail = nil

proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1).
Expand Down Expand Up @@ -523,6 +597,78 @@ proc prepend*[T](L: var DoublyLinkedList[T], value: T) =
assert a.contains(9)
prepend(L, newDoublyLinkedNode(value))

func copy*[T](a: DoublyLinkedList[T]): DoublyLinkedList[T] {.since: (1, 5, 1).} =
## Creates a shallow copy of `a`.
runnableExamples:
type Foo = ref object
x: int
var f = Foo(x: 1)
let
a = [f].toDoublyLinkedList
b = a.copy
f.x = 42
assert a.head.value.x == 42
assert b.head.value.x == 42

let c = [1, 2, 3].toDoublyLinkedList
assert $c == $c.copy
result = initDoublyLinkedList[T]()
for x in a.items:
result.append(x)

proc addMoved*[T](a, b: var DoublyLinkedList[T]) {.since: (1, 5, 1).} =
## Moves `b` to the end of `a`. Efficiency: O(1).
## Note that `b` becomes empty after the operation unless it has the same address as `a`.
## Self-adding results in a cycle.
##
## See also:
## * `add proc <#add,T,T>`_
## for adding a copy of a list
runnableExamples:
import sequtils, std/enumerate, std/sugar
var
a = [1, 2, 3].toDoublyLinkedList
b = [4, 5].toDoublyLinkedList
c = [0, 1].toDoublyLinkedList
a.addMoved b
assert a.toSeq == [1, 2, 3, 4, 5]
assert b.toSeq == []
c.addMoved c
let s = collect:
for i, ci in enumerate(c):
if i == 6: break
ci
assert s == [0, 1, 0, 1, 0, 1]
if b.head != nil:
b.head.prev = a.tail
if a.tail != nil:
a.tail.next = b.head
a.tail = b.tail
if a.head == nil:
a.head = b.head
if a.addr != b.addr:
b.head = nil
b.tail = nil

proc add*[T: SomeLinkedList](a: var T, b: T) {.since: (1, 5, 1).} =
## Appends a shallow copy of `b` to the end of `a`.
##
## See also:
## * `addMoved proc <#addMoved,SinglyLinkedList[T],SinglyLinkedList[T]>`_
## * `addMoved proc <#addMoved,DoublyLinkedList[T],DoublyLinkedList[T]>`_
## for moving the second list instead of copying
runnableExamples:
import sequtils
var a = [1, 2, 3].toSinglyLinkedList
let b = [4, 5].toSinglyLinkedList
a.add b
assert a.toSeq == [1, 2, 3, 4, 5]
assert b.toSeq == [4, 5]
a.add a
assert a.toSeq == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
var tmp = b.copy
a.addMoved tmp

proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
## Removes a node `n` from `L`. Efficiency: O(1).
runnableExamples:
Expand Down
69 changes: 68 additions & 1 deletion tests/stdlib/tlists.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ discard """
targets: "c js"
"""

import lists
import lists, sequtils, std/enumerate, std/sugar

const
data = [1, 2, 3, 4, 5, 6]
Expand Down Expand Up @@ -81,3 +81,70 @@ block tlistsToString:
l.append('2')
l.append('3')
doAssert $l == """['1', '2', '3']"""

template testCommon(initList, toList) =

block: # toSinglyLinkedList, toDoublyLinkedList
let l = seq[int].default
doAssert l.toList.toSeq == []
doAssert [1].toList.toSeq == [1]
doAssert [1, 2, 3].toList.toSeq == [1, 2, 3]

block copy:
doAssert array[0, int].default.toList.copy.toSeq == []
doAssert [1].toList.copy.toSeq == [1]
doAssert [1, 2].toList.copy.toSeq == [1, 2]
doAssert [1, 2, 3].toList.copy.toSeq == [1, 2, 3]
type Foo = ref object
x: int
var f0 = Foo(x: 0)
let f1 = Foo(x: 1)
var a = [f0].toList
var b = a.copy
b.append f1
doAssert a.toSeq == [f0]
doAssert b.toSeq == [f0, f1]
f0.x = 42
assert a.head.value.x == 42
assert b.head.value.x == 42

block: # add, addMoved
block:
var
l0 = initList[int]()
l1 = [1].toList
l2 = [2, 3].toList
l3 = [4, 5, 6].toList
l0.add l3
l1.add l3
l2.addMoved l3
doAssert l0.toSeq == [4, 5, 6]
doAssert l1.toSeq == [1, 4, 5, 6]
doAssert l2.toSeq == [2, 3, 4, 5, 6]
doAssert l3.toSeq == []
l2.add l3 # re-adding l3 that was destroyed is now a no-op
doAssert l2.toSeq == [2, 3, 4, 5, 6]
doAssert l3.toSeq == []
block:
var
l0 = initList[int]()
l1 = [1].toList
l2 = [2, 3].toList
l3 = [4, 5, 6].toList
l3.addMoved l0
l2.addMoved l1
doAssert l3.toSeq == [4, 5, 6]
doAssert l2.toSeq == [2, 3, 1]
l3.add l0
doAssert l3.toSeq == [4, 5, 6]
block:
var c = [0, 1].toList
c.addMoved c
let s = collect:
for i, ci in enumerate(c):
if i == 6: break
ci
doAssert s == [0, 1, 0, 1, 0, 1]

testCommon initSinglyLinkedList, toSinglyLinkedList
testCommon initDoublyLinkedList, toDoublyLinkedList

0 comments on commit 97d52a1

Please sign in to comment.