Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/Skiplists.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ include("list.jl")
Exports
===========================#

export Skiplist, height
export Skiplist, SkiplistSet, height

end # module
10 changes: 6 additions & 4 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const IS_SENTINEL = FLAG_IS_LEFT_SENTINEL | FLAG_IS_RIGHT_SENTINEL
Typedefs
===========================#

mutable struct SkiplistNode{T}
mutable struct SkiplistNode{T,M}
val :: T
next :: Vector{SkiplistNode{T}}
marked_for_deletion :: Bool
Expand All @@ -30,7 +30,7 @@ mutable struct SkiplistNode{T}
lock :: ReentrantLock
end

struct Skiplist{T}
struct Skiplist{T,M}
height_p :: Float64
max_height :: Int64

Expand All @@ -40,8 +40,10 @@ struct Skiplist{T}
length :: Atomic{Int64}
end

abstract type LeftSentinel{T} end
abstract type RightSentinel{T} end
abstract type LeftSentinel{T,M} end
abstract type RightSentinel{T,M} end

SkiplistSet{T} = Skiplist{T,:Set}

#===========================
Simple function definitions
Expand Down
154 changes: 85 additions & 69 deletions src/list.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,23 @@ using Logging
Constructors
===========================#

function Skiplist{T}(; max_height = DEFAULT_MAX_HEIGHT, p = DEFAULT_P) where T
left_sentinel = LeftSentinel{T}(; max_height=max_height)
right_sentinel = RightSentinel{T}(; max_height=max_height)
Skiplist{T}(args...; kws...) where T = Skiplist{T,:List}(args...; kws...)

function Skiplist{T,M}(; max_height = DEFAULT_MAX_HEIGHT, p = DEFAULT_P) where {T,M}
if M != :List && M != :Set
"Skiplist mode $M is not recognized. Valid options are :List and :Set" |>
ErrorException |>
throw
end

left_sentinel = LeftSentinel{T,M}(; max_height=max_height)
right_sentinel = RightSentinel{T,M}(; max_height=max_height)

for ii = 1:max_height
link_nodes!(left_sentinel, right_sentinel, ii)
end

Skiplist{T}(
Skiplist{T,M}(
p,
max_height,
left_sentinel,
Expand All @@ -42,13 +50,13 @@ macro validate(predecessors, successors, node, expr, type = :(:strong))
#
# Weak validation (used by Base.remove!) drops the second condition, since
# the successor is supposed to be marked for deletion.
local check_valid = if eval(type) == :strong
local check_valid = if type == :(:strong)
:(!is_marked_for_deletion(pred) &&
!is_marked_for_deletion(succ) &&
next(pred, level) == succ)
elseif eval(type) == :weak
next(pred, level) === succ)
elseif type == :(:weak)
:(!is_marked_for_deletion(pred) &&
next(pred, level) == succ)
next(pred, level) === succ)
else
"Validation type '$(type)' is not defined" |>
ErrorException |>
Expand Down Expand Up @@ -98,25 +106,6 @@ Base.string(list :: Skiplist) = "Skiplist(length = $(length(list)), height = $(h
Base.show(list :: Skiplist) = println(string(list))
Base.display(list :: Skiplist) = println(string(list))

"""
vec(list :: Skiplist{T}) where T

Convert the skip list `list` into a one-dimensional `Vector{T}` containing all of the elements
of the skip list, sorted in ascending order.
"""
function Base.vec(list :: Skiplist{T}) where T
results = Vector{T}(undef, 0)
current_node = list.left_sentinel
current_node = next(current_node, 1)

while !is_right_sentinel(current_node)
push!(results, current_node.val)
current_node = next(current_node, 1)
end

results
end

function Base.in(val, list :: Skiplist)
level_found, predecessors, successors = find_node(list, val)

Expand All @@ -125,37 +114,66 @@ function Base.in(val, list :: Skiplist)
!is_marked_for_deletion(successors[level_found])
end

Base.insert!(list :: Skiplist, val) =
insert!(list, SkiplistNode(val; p=list.height_p, max_height=list.max_height))

function Base.insert!(list :: Skiplist, node :: SkiplistNode)
while true
level_found, predecessors, successors = find_node(list, node)

# Update the list height.
#
# If the height of the list is greater than the old height, then we
# will need to replace the connections between the left and right
# sentinel nodes.
old_height = atomic_max!(list.height, height(node))
for ii = old_height+1:height(node)
push!(predecessors, list.left_sentinel)
push!(successors, list.right_sentinel)
Base.insert!(list :: Skiplist{_,M}, val) where {_,M} =
insert!(list, SkiplistNode{M}(val; p=list.height_p, max_height=list.max_height))

@generated function Base.insert!(list :: Skiplist{T,M}, node :: SkiplistNode) where {T,M}
local check_exists = if M == :Set
quote
if level_found != -1
node_found = successors[level_found]

# If the node is in the process of being deleted, wait until it
# is deleted before performing insertion again
if is_marked_for_deletion(node)
# TODO: use Event or Condition to wait until node is deleted?
continue
end

# If the node is _not_ in the process of being deleted, we wait
# until it's fully linked before we return.
while !is_fully_linked(node_found)
# TODO: use Event or Condition instead of spinning
sleep(0.001)
end
return false
end
end
else
:()
end

# Acquire locks to predecessor nodes to ensure that they're still
# connected to their corresponding successors
valid = @validate(predecessors, successors, node, begin
for ii = 1:height(node)
link_nodes!(predecessors[ii], node, ii)
link_nodes!(node, successors[ii], ii)
quote
while true
level_found, predecessors, successors = find_node(list, node)

$check_exists

# Update the list height.
#
# If the height of the list is greater than the old height, then we
# will need to replace the connections between the left and right
# sentinel nodes.
old_height = atomic_max!(list.height, height(node))
for ii = old_height+1:height(node)
push!(predecessors, list.left_sentinel)
push!(successors, list.right_sentinel)
end
mark_fully_linked!(node)
end)

if valid
atomic_add!(list.length, 1)
break
# Acquire locks to predecessor nodes to ensure that they're still
# connected to their corresponding successors
valid = @validate(predecessors, successors, node, begin
for ii = 1:height(node)
link_nodes!(predecessors[ii], node, ii)
link_nodes!(node, successors[ii], ii)
end
mark_fully_linked!(node)
end)

if valid
atomic_add!(list.length, 1)
break
end
end
end
end
Expand Down Expand Up @@ -215,27 +233,25 @@ Skiplist internal API
===========================#

function find_node(list :: Skiplist{T}, val) where T
h = height(list)
predecessors = Vector{SkiplistNode{T}}(undef, h)
successors = Vector{SkiplistNode{T}}(undef, h)
predecessors = Vector{SkiplistNode{T}}(undef, height(list))
successors = Vector{SkiplistNode{T}}(undef, height(list))

layer_found = -1
level_found = -1

current_node = list.left_sentinel
ii = h
while ii > 0
for ii = height(list):-1:1
next_node = next(current_node, ii)
if next_node < val
while next_node < val
current_node = next_node
else
if layer_found == -1 && next_node == val
layer_found = ii
end
predecessors[ii] = current_node
successors[ii] = next_node
ii -= 1
next_node = next(current_node, ii)
end

if level_found == -1 && next_node == val
level_found = ii
end
predecessors[ii] = current_node
successors[ii] = next_node
end

layer_found, predecessors, successors
level_found, predecessors, successors
end
45 changes: 30 additions & 15 deletions src/node.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,34 @@ using Base.Threads
Constructors
===========================#

SkiplistNode(val :: T; kws...) where T =
SkiplistNode{T}(val; kws...)
SkiplistNode{M}(val :: T; kws...) where {T,M} =
SkiplistNode{T,M}(val; kws...)

SkiplistNode(val :: T, height; kws...) where T =
SkiplistNode{T}(val, height; kws...)
SkiplistNode{M}(val :: T, height; kws...) where {T,M} =
SkiplistNode{T,M}(val, height; kws...)

SkiplistNode{T}(val; p = DEFAULT_P, max_height = DEFAULT_MAX_HEIGHT, kws...) where T =
SkiplistNode{T}(val, random_height(p; max_height=max_height); kws...)
SkiplistNode{T,M}(val; p = DEFAULT_P, max_height = DEFAULT_MAX_HEIGHT, kws...) where {T,M} =
SkiplistNode{T,M}(val, random_height(p; max_height=max_height); kws...)

function SkiplistNode{T}(val, height; flags = 0x0, max_height = DEFAULT_MAX_HEIGHT) where T
function SkiplistNode{T,M}(val, height; flags = 0x0, max_height = DEFAULT_MAX_HEIGHT) where {T,M}
height = min(height, max_height)
next = Vector{SkiplistNode{T}}(undef, height)
lock = ReentrantLock()

SkiplistNode{T}(val, next, false, false, flags, lock)
SkiplistNode{T,M}(val, next, false, false, flags, lock)
end

LeftSentinel{T}(; max_height = DEFAULT_MAX_HEIGHT, kws...) where T =
SkiplistNode{T}(zero(T), max_height; flags = FLAG_IS_LEFT_SENTINEL, kws...)
RightSentinel{T}(; max_height = DEFAULT_MAX_HEIGHT, kws...) where T =
SkiplistNode{T}(zero(T), max_height; flags = FLAG_IS_RIGHT_SENTINEL, kws...)
function LeftSentinel{T,M}(; max_height = DEFAULT_MAX_HEIGHT, kws...) where {T,M}
node = SkiplistNode{T,M}(zero(T), max_height; flags = FLAG_IS_LEFT_SENTINEL, kws...)
mark_fully_linked!(node)
node
end

function RightSentinel{T,M}(; max_height = DEFAULT_MAX_HEIGHT, kws...) where {T,M}
node = SkiplistNode{T,M}(zero(T), max_height; flags = FLAG_IS_RIGHT_SENTINEL, kws...)
mark_fully_linked!(node)
node
end

#===========================
External API
Expand All @@ -46,8 +53,13 @@ External API
@inline mark_for_deletion!(node) = (node.marked_for_deletion = true)
@inline mark_fully_linked!(node) = (node.fully_linked = true)

Base.string(node :: SkiplistNode) =
"SkiplistNode($(key(node)), height = $(height(node)))"
function Base.string(node :: SkiplistNode)
result = "key = $(key(node)), height = $(height(node)), "
result *= "marked_for_deletion = $(is_marked_for_deletion(node)), "
result *= "fully_linked = $(is_fully_linked(node))"
"SkiplistNode($result)"
end

Base.show(node :: SkiplistNode) = println(string(node))
Base.display(node :: SkiplistNode) = println(string(node))

Expand Down Expand Up @@ -87,7 +99,10 @@ end

Base.:(==)(node :: SkiplistNode, val) = is_sentinel(node) ? false : key(node) == val
Base.:(==)(val, node :: SkiplistNode) = (node == val)
Base.:(==)(node_1 :: SkiplistNode, node_2 :: SkiplistNode) = (node_1 === node_2)
Base.:(==)(node_1 :: SkiplistNode, node_2 :: SkiplistNode) =
(is_sentinel(node_1) || is_sentinel(node_2)) ?
false :
key(node_1) == key(node_2)

# Node links

Expand Down
Loading