Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
datovky
assignments
Commits
21f7f710
Commit
21f7f710
authored
Mar 15, 2022
by
David Mareček
Browse files
ab experiment
parent
94d79774
Changes
6
Hide whitespace changes
Inline
Side-by-side
05-ab_experiment/cpp/Makefile
0 → 100644
View file @
21f7f710
STUDENT_ID
?=
PLEASE_SET_STUDENT_ID
.PHONY
:
test
test
:
ab_experiment
@
rm
-rf
out
&&
mkdir
out
@
for
test
in
insert min random
;
do
\
for
mode
in
'2-3'
'2-4'
;
do
\
echo
t-
$$
test-
$$
mode
;
\
./ab_experiment
$$
test
$(STUDENT_ID)
$$
mode
>
out/t-
$$
test-
$$
mode
;
\
done
;
\
done
INCLUDE
?=
.
CXXFLAGS
=
-std
=
c++11
-O2
-Wall
-Wextra
-g
-Wno-sign-compare
-I
$(INCLUDE)
ab_experiment
:
ab_tree.h ab_experiment.cpp $(INCLUDE)/random.h
$(CXX)
$(CPPFLAGS)
$(CXXFLAGS)
$^
-o
$@
.PHONY
:
clean
clean
::
rm
-f
ab_experiment
rm
-rf
out
05-ab_experiment/cpp/ab_experiment.cpp
0 → 100644
View file @
21f7f710
#include <algorithm>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include <iostream>
#include <cmath>
#include "ab_tree.h"
#include "random.h"
using
namespace
std
;
void
expect_failed
(
const
string
&
message
)
{
cerr
<<
"Test error: "
<<
message
<<
endl
;
exit
(
1
);
}
/*
* A modified Splay tree for benchmarking.
*
* We inherit the implementation of operations from the Tree class
* and extend it by keeping statistics on the number of splay operations
* and the total number of rotations. Also, if naive is turned on,
* splay uses only single rotations.
*
* Please make sure that your Tree class defines the rotate() and splay()
* methods as virtual.
*/
class
BenchmarkingABTree
:
public
ab_tree
{
public:
int
num_operations
;
int
num_struct_changes
;
BenchmarkingABTree
(
int
a
,
int
b
)
:
ab_tree
(
a
,
b
)
{
reset
();
}
void
reset
()
{
num_operations
=
0
;
num_struct_changes
=
0
;
}
pair
<
ab_node
*
,
int
>
split_node
(
ab_node
*
node
,
int
size
)
override
{
num_struct_changes
++
;
return
ab_tree
::
split_node
(
node
,
size
);
}
void
insert
(
int
key
)
override
{
num_operations
++
;
ab_tree
::
insert
(
key
);
}
// Return the average number of rotations per operation.
double
struct_changes_per_op
()
{
if
(
num_operations
>
0
)
return
(
double
)
num_struct_changes
/
num_operations
;
else
return
0
;
}
// Delete key from the tree. Does nothing if the key is not in the tree.
void
remove
(
int
key
){
num_operations
+=
1
;
// Find the key to be deleted
ab_node
*
node
=
root
;
int
i
;
bool
found
=
node
->
find_branch
(
key
,
i
);
while
(
!
found
){
node
=
node
->
children
[
i
];
if
(
!
node
)
return
;
// Key is not in the tree
found
=
node
->
find_branch
(
key
,
i
);
}
// If node is not a leaf, we need to swap the key with its successor
if
(
node
->
children
[
0
]
!=
nullptr
){
// Only leaves have nullptr as children
// Successor is leftmost key in the right subtree of key
ab_node
*
succ
=
min
(
node
->
children
[
i
+
1
]);
swap
(
node
->
keys
[
i
],
succ
->
keys
[
0
]);
node
=
succ
;
}
// Now run the main part of the delete
remove_leaf
(
key
,
node
);
}
private:
// Main part of the remove
void
remove_leaf
(
int
key
,
ab_node
*
node
)
{
EXPECT
(
node
!=
nullptr
,
"Trying to delete key from nullptr"
);
EXPECT
(
node
->
children
[
0
]
==
nullptr
,
"Leaf's child must be nullptr"
);
while
(
1
){
// Find the key in the node
int
key_position
;
bool
found
=
node
->
find_branch
(
key
,
key_position
);
EXPECT
(
found
,
"Trying to delete key that is not in the node."
);
// Start with the deleting itself
node
->
keys
.
erase
(
node
->
keys
.
cbegin
()
+
key_position
);
node
->
children
.
erase
(
node
->
children
.
cbegin
()
+
key_position
+
1
);
// No underflow means we are done
if
(
node
->
children
.
size
()
>=
a
)
return
;
// Root may underflow, but cannot have just one child (unless tree is empty)
if
(
node
==
root
){
if
((
node
->
children
.
size
()
==
1
)
&&
(
root
->
children
[
0
]
!=
nullptr
)){
ab_node
*
old_root
=
root
;
root
=
root
->
children
[
0
];
root
->
parent
=
nullptr
;
delete_node
(
old_root
);
}
return
;
}
ab_node
*
brother
;
int
separating_key_pos
;
bool
tmp
;
tie
(
brother
,
separating_key_pos
,
tmp
)
=
get_brother
(
node
);
int
separating_key
=
node
->
parent
->
keys
[
separating_key_pos
];
// First check whether we can steal brother's child
if
(
brother
->
children
.
size
()
>
a
){
steal_child
(
node
);
return
;
}
// If the brother is too small, we merge with him and propagate the delete
node
=
merge_node
(
node
);
node
=
node
->
parent
;
key
=
separating_key
;
key_position
=
separating_key_pos
;
}
}
// Return the leftmost node of a subtree rooted at node.
ab_node
*
min
(
ab_node
*
node
)
{
EXPECT
(
node
!=
nullptr
,
"Trying to search for minimum of nullptr"
);
while
(
node
->
children
[
0
])
{
node
=
node
->
children
[
0
];
}
return
node
;
}
// Return the left brother if it exists, otherwise return right brother.
// Returns tuple (brother, key_position, is_left_brother), where
// key_position is a position of the key that separates node and brother in their parent.
tuple
<
ab_node
*
,
int
,
bool
>
get_brother
(
ab_node
*
node
)
{
ab_node
*
parent
=
node
->
parent
;
EXPECT
(
parent
!=
nullptr
,
"Node without parent has no brother"
);
// Find node in parent's child list
int
i
;
for
(
i
=
0
;
i
<
parent
->
children
.
size
();
++
i
){
ab_node
*
c
=
parent
->
children
[
i
];
if
(
c
==
node
)
break
;
}
EXPECT
(
i
<
parent
->
children
.
size
(),
"Node is not inside its parent"
);
if
(
i
==
0
){
return
make_tuple
(
parent
->
children
[
1
],
0
,
false
);
}
else
{
return
make_tuple
(
parent
->
children
[
i
-
1
],
i
-
1
,
true
);
}
}
// Transfer one child from node's left brother to the node.
// If node has no left brother, use right brother instead.
void
steal_child
(
ab_node
*
node
)
{
ab_node
*
brother
;
int
separating_key_pos
;
bool
is_left_brother
;
tie
(
brother
,
separating_key_pos
,
is_left_brother
)
=
get_brother
(
node
);
int
separating_key
=
node
->
parent
->
keys
[
separating_key_pos
];
EXPECT
(
brother
->
children
.
size
()
>
a
,
"Stealing child causes underflow in brother!"
);
EXPECT
(
node
->
children
.
size
()
<
b
,
"Stealing child causes overflow in the node!"
);
// We steal either from front or back
int
steal_position
,
target_position
;
if
(
is_left_brother
){
steal_position
=
brother
->
children
.
size
()
-
1
;
target_position
=
0
;
}
else
{
steal_position
=
0
;
target_position
=
node
->
children
.
size
();
}
// Steal the child
ab_node
*
stolen_child
=
brother
->
children
[
steal_position
];
if
(
stolen_child
!=
nullptr
){
stolen_child
->
parent
=
node
;
}
node
->
children
.
insert
(
node
->
children
.
cbegin
()
+
target_position
,
stolen_child
);
brother
->
children
.
erase
(
brother
->
children
.
cbegin
()
+
steal_position
);
// List of keys is shorter than list of children
if
(
is_left_brother
)
steal_position
-=
1
;
else
target_position
-=
1
;
// Update keys
node
->
keys
.
insert
(
node
->
keys
.
cbegin
()
+
target_position
,
separating_key
);
node
->
parent
->
keys
[
separating_key_pos
]
=
brother
->
keys
[
steal_position
];
brother
->
keys
.
erase
(
brother
->
keys
.
cbegin
()
+
steal_position
);
}
public:
// Merge node with its left brother and destroy the node. Must not cause overflow!
// Returns result of the merge.
// If node has no left brother, use right brother instead.
ab_node
*
merge_node
(
ab_node
*
node
){
num_struct_changes
+=
1
;
ab_node
*
brother
;
int
separating_key_pos
;
bool
is_left_brother
;
tie
(
brother
,
separating_key_pos
,
is_left_brother
)
=
get_brother
(
node
);
int
separating_key
=
node
->
parent
->
keys
[
separating_key_pos
];
// We swap brother and node if necessary so that the node is always on the right
if
(
!
is_left_brother
)
swap
(
brother
,
node
);
for
(
auto
c
:
node
->
children
)
brother
->
children
.
push_back
(
c
);
brother
->
keys
.
push_back
(
separating_key
);
for
(
auto
k
:
node
->
keys
)
brother
->
keys
.
push_back
(
k
);
EXPECT
(
brother
->
children
.
size
()
<=
b
,
"Merge caused overflow!"
);
// Update parent pointers in non-leaf
if
(
brother
->
children
[
0
]
!=
nullptr
){
for
(
auto
c
:
brother
->
children
)
c
->
parent
=
brother
;
}
delete_node
(
node
);
return
brother
;
}
};
int
a
,
b
;
RandomGen
*
rng
;
// Random generator object
// An auxiliary function for generating a random permutation.
vector
<
int
>
random_permutation
(
int
n
)
{
vector
<
int
>
perm
;
for
(
int
i
=
0
;
i
<
n
;
i
++
)
perm
.
push_back
(
i
);
for
(
int
i
=
0
;
i
<
n
-
1
;
i
++
)
swap
(
perm
[
i
],
perm
[
i
+
rng
->
next_range
(
n
-
i
)]);
return
perm
;
}
void
test_insert
()
{
for
(
int
e
=
32
;
e
<=
64
;
e
++
)
{
int
n
=
(
int
)
pow
(
2
,
e
/
4.
);
BenchmarkingABTree
tree
=
BenchmarkingABTree
(
a
,
b
);
vector
<
int
>
perm
=
random_permutation
(
n
);
for
(
int
x
:
perm
)
tree
.
insert
(
x
);
cout
<<
n
<<
" "
<<
tree
.
struct_changes_per_op
()
<<
endl
;
}
}
void
test_random
()
{
for
(
int
e
=
32
;
e
<=
64
;
e
++
)
{
int
n
=
(
int
)
pow
(
2
,
e
/
4.
);
BenchmarkingABTree
tree
=
BenchmarkingABTree
(
a
,
b
);
// We keep track of elements present and not present in the tree
vector
<
int
>
elems
;
vector
<
int
>
anti_elems
;
elems
.
reserve
(
n
);
anti_elems
.
reserve
(
n
+
1
);
for
(
int
x
=
0
;
x
<
2
*
n
;
x
+=
2
){
tree
.
insert
(
x
);
elems
.
push_back
(
x
);
}
for
(
int
i
=
-
1
;
i
<
2
*
n
+
1
;
i
+=
2
)
anti_elems
.
push_back
(
i
);
for
(
int
i
=
0
;
i
<
n
;
i
++
){
int
r
,
x
;
// Delete random element
r
=
rng
->
next_range
(
elems
.
size
());
x
=
elems
[
r
];
tree
.
remove
(
x
);
elems
.
erase
(
elems
.
cbegin
()
+
r
);
anti_elems
.
push_back
(
x
);
// Insert random "anti-element"
r
=
rng
->
next_range
(
anti_elems
.
size
());
x
=
anti_elems
[
r
];
tree
.
insert
(
x
);
elems
.
push_back
(
x
);
anti_elems
.
erase
(
anti_elems
.
cbegin
()
+
r
);
}
cout
<<
n
<<
" "
<<
tree
.
struct_changes_per_op
()
<<
endl
;
}
}
void
test_min
()
{
for
(
int
e
=
32
;
e
<=
64
;
e
++
)
{
int
n
=
(
int
)
pow
(
2
,
e
/
4.
);
BenchmarkingABTree
tree
=
BenchmarkingABTree
(
a
,
b
);
for
(
int
x
=
0
;
x
<
n
;
x
++
)
tree
.
insert
(
x
);
for
(
int
i
=
0
;
i
<
n
;
i
++
){
tree
.
remove
(
0
);
tree
.
insert
(
0
);
}
cout
<<
n
<<
" "
<<
tree
.
struct_changes_per_op
()
<<
endl
;
}
}
vector
<
pair
<
string
,
function
<
void
()
>>>
tests
=
{
{
"insert"
,
test_insert
},
{
"random"
,
test_random
},
{
"min"
,
test_min
},
};
int
main
(
int
argc
,
char
**
argv
)
{
if
(
argc
!=
4
)
{
cerr
<<
"Usage: "
<<
argv
[
0
]
<<
" <test> <student-id> (2-3|2-4)"
<<
endl
;
return
1
;
}
string
which_test
=
argv
[
1
];
string
id_str
=
argv
[
2
];
string
mode
=
argv
[
3
];
try
{
rng
=
new
RandomGen
(
stoi
(
id_str
));
}
catch
(...)
{
cerr
<<
"Invalid student ID"
<<
endl
;
return
1
;
}
a
=
2
;
if
(
mode
==
"2-3"
)
b
=
3
;
else
if
(
mode
==
"2-4"
)
b
=
4
;
else
{
cerr
<<
"Last argument must be either '2-3' or '2-4'"
<<
endl
;
return
1
;
}
for
(
const
auto
&
test
:
tests
)
{
if
(
test
.
first
==
which_test
)
{
cout
.
precision
(
12
);
test
.
second
();
return
0
;
}
}
cerr
<<
"Unknown test "
<<
which_test
<<
endl
;
return
1
;
return
0
;
}
05-ab_experiment/cpp/random.h
0 → 100644
View file @
21f7f710
#define DS1_RANDOM_H
#include <cstdint>
/*
* This is the xoroshiro128+ random generator, designed in 2016 by David Blackman
* and Sebastiano Vigna, distributed under the CC-0 license. For more details,
* see http://vigna.di.unimi.it/xorshift/.
*
* Rewritten to C++ by Martin Mares, also placed under CC-0.
*/
class
RandomGen
{
uint64_t
state
[
2
];
uint64_t
rotl
(
uint64_t
x
,
int
k
)
{
return
(
x
<<
k
)
|
(
x
>>
(
64
-
k
));
}
public:
// Initialize the generator, set its seed and warm it up.
RandomGen
(
unsigned
int
seed
)
{
state
[
0
]
=
seed
*
0xdeadbeef
;
state
[
1
]
=
seed
^
0xc0de1234
;
for
(
int
i
=
0
;
i
<
100
;
i
++
)
next_u64
();
}
// Generate a random 64-bit number.
uint64_t
next_u64
(
void
)
{
uint64_t
s0
=
state
[
0
],
s1
=
state
[
1
];
uint64_t
result
=
s0
+
s1
;
s1
^=
s0
;
state
[
0
]
=
rotl
(
s0
,
55
)
^
s1
^
(
s1
<<
14
);
state
[
1
]
=
rotl
(
s1
,
36
);
return
result
;
}
// Generate a random 32-bit number.
uint32_t
next_u32
(
void
)
{
return
next_u64
()
>>
11
;
}
// Generate a number between 0 and range-1.
unsigned
int
next_range
(
unsigned
int
range
)
{
/*
* This is not perfectly uniform, unless the range is a power of two.
* However, for 64-bit random values and 32-bit ranges, the bias is
* insignificant.
*/
return
next_u64
()
%
range
;
}
};
05-ab_experiment/python/Makefile
0 → 100644
View file @
21f7f710
STUDENT_ID
?=
PLEASE_SET_STUDENT_ID
.PHONY
:
test
test
:
ab_experiment.py ab_tree.py
@
rm
-rf
out
&&
mkdir
out
@
for
test
in
insert min random
;
do
\
for
mode
in
'2-3'
'2-4'
;
do
\
echo
t-
$$
test-
$$
mode
;
\
./ab_experiment.py
$$
test
$(STUDENT_ID)
$$
mode
>
out/t-
$$
test-
$$
mode
;
\
done
;
\
done
.PHONY
:
clean
clean
::
rm
-rf
out __pycache__
05-ab_experiment/python/ab_experiment.py
0 → 100755
View file @
21f7f710
#!/usr/bin/env python3
import
sys
import
random
from
ab_tree
import
ABTree
class
BenchmarkingABTree
(
ABTree
):
"""A modified ABTree for benchmarking.
We inherit the implementation of operations from the ABTree class
and extend it by delete operation and by keeping statistics on the number
of operations and the total number of structural changes.
"""
def
__init__
(
self
,
a
,
b
):
ABTree
.
__init__
(
self
,
a
,
b
)
self
.
reset
()
def
reset
(
self
):
""" Reset statistics """
self
.
num_operations
=
0
self
.
num_struct_changes
=
0
def
struct_changes_per_op
(
self
):
"""Return the average number of struct. changes per operation."""
if
self
.
num_operations
>
0
:
return
self
.
num_struct_changes
/
self
.
num_operations
else
:
return
0
def
insert
(
self
,
key
):
self
.
num_operations
+=
1
ABTree
.
insert
(
self
,
key
)
def
split_node
(
self
,
node
,
size
):
self
.
num_struct_changes
+=
1
return
ABTree
.
split_node
(
self
,
node
,
size
)
def
remove
(
self
,
key
):
""" Delete key from the tree. Does nothing if the key is not in the tree. """
self
.
num_operations
+=
1
# Find the key to be deleted
node
=
self
.
root
found
,
i
=
node
.
find_branch
(
key
)
while
not
found
:
node
=
node
.
children
[
i
]
if
not
node
:
return
# Key is not in the tree
found
,
i
=
node
.
find_branch
(
key
)
# If node is not a leaf, we need to swap the key with its successor
if
node
.
children
[
0
]
is
not
None
:
# Only leaves have None as children
# Successor is leftmost key in the right subtree of key
succ
=
self
.
_min
(
node
.
children
[
i
+
1
])
node
.
keys
[
i
],
succ
.
keys
[
0
]
=
succ
.
keys
[
0
],
node
.
keys
[
i
]
node
=
succ
# Now run the main part of the delete
self
.
_remove_leaf
(
key
,
node
)
def
_remove_leaf
(
self
,
key
,
node
):
""" Main part of the delete.
"""
assert
node
is
not
None
,
"Trying to delete key from None"
assert
node
.
children
[
0
]
is
None
,
"Leaf's child must be None"
while
True
:
# Find the key in the node
found
,
key_position
=
node
.
find_branch
(
key
)
assert
found
,
"Trying to delete key that is not in the node."
# Start with the deleting itself
del
node
.
keys
[
key_position
]
del
node
.
children
[
key_position
+
1
]
# No underflow means we are done
if
len
(
node
.
children
)
>=
self
.
a
:
return
# Root may underflow, but cannot have just one child (unless tree is empty)
if
node
==
self
.
root
:
if
(
len
(
node
.
children
)
==
1
)
and
(
self
.
root
.
children
[
0
]
is
not
None
):
self
.
root
=
self
.
root
.
children
[
0
]
self
.
root
.
parent
=
None
return
brother
,
separating_key_pos
,
_
=
self
.
_get_brother
(
node
)
separating_key
=
node
.
parent
.
keys
[
separating_key_pos
]
# First check whether we can steal brother's child
if
len
(
brother
.
children
)
>
self
.
a
:
self
.
_steal_child
(
node
)
return
# If the brother is too small, we merge with him and propagate the delete
node
=
self
.
merge_node
(
node
)
node
,
key
,
key_position
=
node
.
parent
,
separating_key
,
separating_key_pos
def
_min
(
self
,
node
):
""" Return the leftmost node of a subtree rooted at node."""
assert
node
is
not
None
while
node
.
children
[
0
]
is
not
None
:
node
=
node
.
children
[
0
]
return
node
def
_get_brother
(
self
,
node
):
""" Return the left brother if it exists, otherwise return right brother.
returns tuple (brother, key_position, is_left_brother), where
key_position is a position of the key that separates node and brother in their parent.
"""
parent
=
node
.
parent
assert
parent
is
not
None
,
"Node without parent has no brother"
# Find node in parent's child list
i
=
0
for
c
in
parent
.
children
:
if
c
is
node
:
break