Skip to content

srwiley/listsv

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 

Repository files navigation

listsv

A Nim language module that implements singly and doubly linked lists with features including insertions, finding, deleting, extracting, and forward and reverse iterators.

The source code includes tests and examples. Html docs for the module can be generated using the nim doc command.

LinkedList objects are defined on creation as using either singly (type Link) or doubly (type DoubleLink) linked links. Any procs that differ between the link types are set as proc fields in the LinkedList when instantiated to produce the correct behavior without overhead or problems related to using generics with dynamic method dispatching.

import listsv, future

let # Both of these are type LinkedList[int]
  singleList = createLinkedList[int](ltSingle)
  doubleList = createLinkedList[int](ltDouble)
  
# But the newlink proc generates different Link types.
# That is because the newlink proc field in the 
# LinkedList object was set by the createLinkedList
# procedure.
assert singleList.newLink(1) of Link[int]
assert doubleList.newLink(1) of DoubleLink[int]

All procs work for both singly and doubly linked lists, but programmers should be aware that procs requiring reverse transversal through the links are more efficient for doubly linked lists, because singly linked lists require searching from the start of the list to find the upstream link.

Here are some examples:

Let's define a type "birdScore" which will be the value type for example linked lists, although basic types such as int would work as well for the value type. Now define an example array of birdScores:

type birdScore = tuple[score: float, name :string]
const scores : array[7,birdScore] = 
    [  ( 1.0, "Sparrows"), 
       ( 2.5, "Gulls"), 
       ( 4.5, "Hawks"),
       ( 2.3, "Crows"),
       ( 4.8, "Falcons"),
       ( 1.5, "Jays"),
       ( 3.2, "Juncos")  ]

To create and fill a singly linked list with the scores do this:

let allscores = newLinkedList(scores[0..scores.high])

and this will assert:

assert allscores.len == 7

The above fills the allscores list with each birdScore in the order of the scores array. But what if we want to capture and sort by the highest 5 scores?

This makes an empty doubly linked list and then collects the highest 5 scores into the list sorted from high to low:

let doubleScores = newDoubleLinkedList[birdScore]()
for s in scores:
   let added = doubleScores.insertBeforeHere(s, t => t.score < s.score)
   if doubleScores.len >= 5:
      doubleScores.remove(doubleScores.tail)
   elif not added :
      doubleScores.append(s)
      
echo "Double Scores: " , $doubleScores, " len " , doubleScores.len

The above code block also works for singly linked lists, but the 'insertBeforeHere' and 'remove' proc is inherently slow for a singly linked list; it needs to scan the list from the beginning to find the upstream link.

The next code block does the same job but is more efficient for singly linked lists since it only uses procs which are efficient for both singly and doubly linked lists:

let singleScores = newLinkedList[birdScore]()
for s in scores:
   var
      added = false
      blink : Link[birdScore] = nil
   if singleScores.head == nil or singleScores.head.value.score < s.score :
      singleScores.prepend(s)
      blink = singleScores.head
      added = true
   else:
      blink = singleScores.newLink(s)
      added = singleScores.insertAfterHere(
                 blink,
                 link => link.next != nil and link.next.value.score < s.score)
   if singleScores.len > 5:
      # Find the link that preceeds the tail
      while blink.next != singleScores.tail:
         blink = blink.next
      discard singleScores.removeNext(blink)
   elif not added:
      singleScores.append(s)
      
echo "Single Scores: " , $singleScores, " len " , singleScores.len

Links of both types can be extracted into new lists without creating new link objects and refs, thus avoiding GC overhead, using the "extract" proc:

# It turns out that every team whose name's second letter is 'a' 
# has been found guilty of deflating the game ball.
# The cheaters must be removed fromm the list!

let cheaters = singleScores.extract( t => t.name[1] == 'a')
echo "Cheaters: " , $cheaters
echo "Clean Scores: " , $singleScores
echo "Winners are the " , singleScores.head.value.name, "!"

Output of the above code blocks combined is:

Double Scores: {(score: 4.8, name: Falcons), (score: 4.5, name: Hawks), (score: 3.2, name: Juncos), (score: 2.5, name: Gulls)} len 4
Single Scores: [(score: 4.8, name: Falcons), (score: 4.5, name: Hawks), (score: 3.2, name: Juncos), (score: 2.5, name: Gulls)] len 4
Cheaters: [(score: 4.8, name: Falcons), (score: 4.5, name: Hawks)]
Clean Scores: [(score: 3.2, name: Juncos), (score: 2.5, name: Gulls)]
Winners are the Juncos!

About

Singly and doubly linked list implementation in Nim language

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages