Skip to content

Commit 97d52a1

Browse files
salvipeterardek66
authored andcommitted
O(1) concatenation of singly- and doubly linked lists. (nim-lang#16362)
* 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.
1 parent 285e81b commit 97d52a1

File tree

3 files changed

+220
-2
lines changed

3 files changed

+220
-2
lines changed

changelog.md

+5
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@
6666
- `echo` and `debugEcho` will now raise `IOError` if writing to stdout fails. Previous behavior
6767
silently ignored errors. See #16366. Use `-d:nimLegacyEchoNoRaise` for previous behavior.
6868

69+
- Added new operations for singly- and doubly linked lists: `lists.toSinglyLinkedList`
70+
and `lists.toDoublyLinkedList` convert from `openArray`s; `lists.copy` implements
71+
shallow copying; `lists.add` concatenates two lists - an O(1) variation that consumes
72+
its argument, `addMoved`, is also supplied.
73+
6974
## Language changes
7075

7176
- `nimscript` now handles `except Exception as e`.

lib/pure/collections/lists.nim

+147-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
## * `deques module <deques.html>`_ for double-ended queues
7373
## * `sharedlist module <sharedlist.html>`_ for shared singly-linked lists
7474

75+
import std/private/since
7576

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

182+
func toSinglyLinkedList*[T](elems: openArray[T]): SinglyLinkedList[T] {.since: (1, 5, 1).} =
183+
## Creates a new `SinglyLinkedList` from members of `elems`.
184+
runnableExamples:
185+
import sequtils
186+
let a = [1, 2, 3, 4, 5].toSinglyLinkedList
187+
assert a.toSeq == [1, 2, 3, 4, 5]
188+
result = initSinglyLinkedList[T]()
189+
for elem in elems.items:
190+
result.append(elem)
191+
192+
func toDoublyLinkedList*[T](elems: openArray[T]): DoublyLinkedList[T] {.since: (1, 5, 1).} =
193+
## Creates a new `DoublyLinkedList` from members of `elems`.
194+
runnableExamples:
195+
import sequtils
196+
let a = [1, 2, 3, 4, 5].toDoublyLinkedList
197+
assert a.toSeq == [1, 2, 3, 4, 5]
198+
result = initDoublyLinkedList[T]()
199+
for elem in elems.items:
200+
result.append(elem)
201+
181202
template itemsListImpl() {.dirty.} =
182203
var it = L.head
183204
while it != nil:
@@ -435,7 +456,60 @@ proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} =
435456
assert a.contains(9)
436457
prepend(L, newSinglyLinkedNode(value))
437458

438-
459+
func copy*[T](a: SinglyLinkedList[T]): SinglyLinkedList[T] {.since: (1, 5, 1).} =
460+
## Creates a shallow copy of `a`.
461+
runnableExamples:
462+
import sequtils
463+
type Foo = ref object
464+
x: int
465+
var
466+
f = Foo(x: 1)
467+
a = [f].toSinglyLinkedList
468+
let b = a.copy
469+
a.add [f].toSinglyLinkedList
470+
assert a.toSeq == [f, f]
471+
assert b.toSeq == [f] # b isn't modified...
472+
f.x = 42
473+
assert a.head.value.x == 42
474+
assert b.head.value.x == 42 # ... but the elements are not deep copied
475+
476+
let c = [1, 2, 3].toSinglyLinkedList
477+
assert $c == $c.copy
478+
result = initSinglyLinkedList[T]()
479+
for x in a.items:
480+
result.append(x)
481+
482+
proc addMoved*[T](a, b: var SinglyLinkedList[T]) {.since: (1, 5, 1).} =
483+
## Moves `b` to the end of `a`. Efficiency: O(1).
484+
## Note that `b` becomes empty after the operation unless it has the same address as `a`.
485+
## Self-adding results in a cycle.
486+
##
487+
## See also:
488+
## * `add proc <#add,T,T>`_
489+
## for adding a copy of a list
490+
runnableExamples:
491+
import sequtils, std/enumerate, std/sugar
492+
var
493+
a = [1, 2, 3].toSinglyLinkedList
494+
b = [4, 5].toSinglyLinkedList
495+
c = [0, 1].toSinglyLinkedList
496+
a.addMoved b
497+
assert a.toSeq == [1, 2, 3, 4, 5]
498+
assert b.toSeq == []
499+
c.addMoved c
500+
let s = collect:
501+
for i, ci in enumerate(c):
502+
if i == 6: break
503+
ci
504+
assert s == [0, 1, 0, 1, 0, 1]
505+
if a.tail != nil:
506+
a.tail.next = b.head
507+
a.tail = b.tail
508+
if a.head == nil:
509+
a.head = b.head
510+
if a.addr != b.addr:
511+
b.head = nil
512+
b.tail = nil
439513

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

600+
func copy*[T](a: DoublyLinkedList[T]): DoublyLinkedList[T] {.since: (1, 5, 1).} =
601+
## Creates a shallow copy of `a`.
602+
runnableExamples:
603+
type Foo = ref object
604+
x: int
605+
var f = Foo(x: 1)
606+
let
607+
a = [f].toDoublyLinkedList
608+
b = a.copy
609+
f.x = 42
610+
assert a.head.value.x == 42
611+
assert b.head.value.x == 42
612+
613+
let c = [1, 2, 3].toDoublyLinkedList
614+
assert $c == $c.copy
615+
result = initDoublyLinkedList[T]()
616+
for x in a.items:
617+
result.append(x)
618+
619+
proc addMoved*[T](a, b: var DoublyLinkedList[T]) {.since: (1, 5, 1).} =
620+
## Moves `b` to the end of `a`. Efficiency: O(1).
621+
## Note that `b` becomes empty after the operation unless it has the same address as `a`.
622+
## Self-adding results in a cycle.
623+
##
624+
## See also:
625+
## * `add proc <#add,T,T>`_
626+
## for adding a copy of a list
627+
runnableExamples:
628+
import sequtils, std/enumerate, std/sugar
629+
var
630+
a = [1, 2, 3].toDoublyLinkedList
631+
b = [4, 5].toDoublyLinkedList
632+
c = [0, 1].toDoublyLinkedList
633+
a.addMoved b
634+
assert a.toSeq == [1, 2, 3, 4, 5]
635+
assert b.toSeq == []
636+
c.addMoved c
637+
let s = collect:
638+
for i, ci in enumerate(c):
639+
if i == 6: break
640+
ci
641+
assert s == [0, 1, 0, 1, 0, 1]
642+
if b.head != nil:
643+
b.head.prev = a.tail
644+
if a.tail != nil:
645+
a.tail.next = b.head
646+
a.tail = b.tail
647+
if a.head == nil:
648+
a.head = b.head
649+
if a.addr != b.addr:
650+
b.head = nil
651+
b.tail = nil
652+
653+
proc add*[T: SomeLinkedList](a: var T, b: T) {.since: (1, 5, 1).} =
654+
## Appends a shallow copy of `b` to the end of `a`.
655+
##
656+
## See also:
657+
## * `addMoved proc <#addMoved,SinglyLinkedList[T],SinglyLinkedList[T]>`_
658+
## * `addMoved proc <#addMoved,DoublyLinkedList[T],DoublyLinkedList[T]>`_
659+
## for moving the second list instead of copying
660+
runnableExamples:
661+
import sequtils
662+
var a = [1, 2, 3].toSinglyLinkedList
663+
let b = [4, 5].toSinglyLinkedList
664+
a.add b
665+
assert a.toSeq == [1, 2, 3, 4, 5]
666+
assert b.toSeq == [4, 5]
667+
a.add a
668+
assert a.toSeq == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
669+
var tmp = b.copy
670+
a.addMoved tmp
671+
526672
proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
527673
## Removes a node `n` from `L`. Efficiency: O(1).
528674
runnableExamples:

tests/stdlib/tlists.nim

+68-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ discard """
22
targets: "c js"
33
"""
44

5-
import lists
5+
import lists, sequtils, std/enumerate, std/sugar
66

77
const
88
data = [1, 2, 3, 4, 5, 6]
@@ -81,3 +81,70 @@ block tlistsToString:
8181
l.append('2')
8282
l.append('3')
8383
doAssert $l == """['1', '2', '3']"""
84+
85+
template testCommon(initList, toList) =
86+
87+
block: # toSinglyLinkedList, toDoublyLinkedList
88+
let l = seq[int].default
89+
doAssert l.toList.toSeq == []
90+
doAssert [1].toList.toSeq == [1]
91+
doAssert [1, 2, 3].toList.toSeq == [1, 2, 3]
92+
93+
block copy:
94+
doAssert array[0, int].default.toList.copy.toSeq == []
95+
doAssert [1].toList.copy.toSeq == [1]
96+
doAssert [1, 2].toList.copy.toSeq == [1, 2]
97+
doAssert [1, 2, 3].toList.copy.toSeq == [1, 2, 3]
98+
type Foo = ref object
99+
x: int
100+
var f0 = Foo(x: 0)
101+
let f1 = Foo(x: 1)
102+
var a = [f0].toList
103+
var b = a.copy
104+
b.append f1
105+
doAssert a.toSeq == [f0]
106+
doAssert b.toSeq == [f0, f1]
107+
f0.x = 42
108+
assert a.head.value.x == 42
109+
assert b.head.value.x == 42
110+
111+
block: # add, addMoved
112+
block:
113+
var
114+
l0 = initList[int]()
115+
l1 = [1].toList
116+
l2 = [2, 3].toList
117+
l3 = [4, 5, 6].toList
118+
l0.add l3
119+
l1.add l3
120+
l2.addMoved l3
121+
doAssert l0.toSeq == [4, 5, 6]
122+
doAssert l1.toSeq == [1, 4, 5, 6]
123+
doAssert l2.toSeq == [2, 3, 4, 5, 6]
124+
doAssert l3.toSeq == []
125+
l2.add l3 # re-adding l3 that was destroyed is now a no-op
126+
doAssert l2.toSeq == [2, 3, 4, 5, 6]
127+
doAssert l3.toSeq == []
128+
block:
129+
var
130+
l0 = initList[int]()
131+
l1 = [1].toList
132+
l2 = [2, 3].toList
133+
l3 = [4, 5, 6].toList
134+
l3.addMoved l0
135+
l2.addMoved l1
136+
doAssert l3.toSeq == [4, 5, 6]
137+
doAssert l2.toSeq == [2, 3, 1]
138+
l3.add l0
139+
doAssert l3.toSeq == [4, 5, 6]
140+
block:
141+
var c = [0, 1].toList
142+
c.addMoved c
143+
let s = collect:
144+
for i, ci in enumerate(c):
145+
if i == 6: break
146+
ci
147+
doAssert s == [0, 1, 0, 1, 0, 1]
148+
149+
testCommon initSinglyLinkedList, toSinglyLinkedList
150+
testCommon initDoublyLinkedList, toDoublyLinkedList

0 commit comments

Comments
 (0)