Skip to content

Feedback#1

Open
github-classroom[bot] wants to merge 78 commits intofeedbackfrom
main
Open

Feedback#1
github-classroom[bot] wants to merge 78 commits intofeedbackfrom
main

Conversation

@github-classroom
Copy link
Contributor

@github-classroom github-classroom bot commented Mar 18, 2024

👋! GitHub Classroom created this pull request as a place for your teacher to leave feedback on your work. It will update automatically. Don’t close or merge this pull request, unless you’re instructed to do so by your teacher.
In this pull request, your teacher can leave comments and feedback on your code. Click the Subscribe button to be notified if that happens.
Click the Files changed or Commits tab to see all of the changes pushed to main since the assignment started. Your teacher can see this too.

Notes for teachers

Use this PR to leave feedback. Here are some tips:

  • Click the Files changed tab to see all of the changes pushed to main since the assignment started. To leave comments on specific lines of code, put your cursor over a line of code and click the blue + (plus sign). To learn more about comments, read “Commenting on a pull request”.
  • Click the Commits tab to see the commits pushed to main. Click a commit to see specific changes.
  • If you turned on autograding, then click the Checks tab to see the results.
  • This page is an overview. It shows commits, line comments, and general comments. You can leave a general comment below.
    For more information about this pull request, read “Leaving assignment feedback in GitHub”.

Subscribed: @p1onerka @sofyak0zyreva @shvorobsofia

github-classroom bot and others added 30 commits March 18, 2024 06:42
Implement BST and traverse method; Refactor abstractNode
Implement 1 balance method instead of 2
Implement red-black node class and red-black tree class
Apply fixes in AVLTree and abstractTree
shvorobsofia and others added 4 commits March 31, 2024 11:02
Implement tests for a reb-black tree
Implement pack of tests for BST and do refactoring (add exception for null parent of non-root node)
Copy link

@sgrishkova sgrishkova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Понравилось, как у вас хорошо все организовано (например, переиспользование кода для поворотов, продуманная архитектура, понятные названия методов) и выполнено в едином стиле.

@@ -0,0 +1,6 @@
package nodes

abstract class abstractNode<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>>(val key:K, var value: V) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Согласно котлиновскому Coding conventions названия для классов лучше начинать с заглавной буквы.

Suggested change
abstract class abstractNode<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>>(val key:K, var value: V) {
abstract class AbstractNode<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>>(val key:K, var value: V) {


import nodes.abstractNode

abstract class abstractTree<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>> {
Copy link

@sgrishkova sgrishkova Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тоже названия классов.

Suggested change
abstract class abstractTree<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>> {
abstract class AbstractTree<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>> {

Comment on lines 4 to 5
var leftChild: someNode? = null
var rightChild: someNode? = null
Copy link

@sgrishkova sgrishkova Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хорошо ли, когда ссылки на детей узла public?
Вижу, что ни один из public методов не возвращает пользователю узел, т. е. трюк в духе
node1.leftChild = node2 пользователю провернуть будет не так просто, однако можно подумать над использованием модификатора internal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Действительно, безопасность данных мы в первую очередь обеспечивали через устойчивость методов, так как именно за счет них происходит взаимодействие пользователя с библиотекой. Поэтому проблемы с таким решением маловероятны, но замечание на будущее хорошее, спасибо

package nodes

class AVLNode<K : Comparable<K>, V>(key: K, value: V): abstractNode<K, V, AVLNode<K, V>>(key, value) {
var height = 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Стоит ли оставлять высоту узла public? Если кто-то вдруг получит доступ к узлу, то можно будет сделать что-нибудь в духе node.height = 42, а тогда и балансировка AVL поломается.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно, тут тот же случай, что и выше. Хотя если пользователь добился доступа к ноде, ему ничего не помешает обходными путями сделать то же самое и здесь :)


import nodes.abstractNode

abstract class balancedTree<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>>: abstractTree<K, V, someNode>() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Та же история про название класса.

Suggested change
abstract class balancedTree<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>>: abstractTree<K, V, someNode>() {
abstract class BalancedTree<K: Comparable<K>, V, someNode: abstractNode<K, V, someNode>>: abstractTree<K, V, someNode>() {

}

protected fun traverse(curNode: someNode?, listOfNodes: MutableList<someNode>) {
if(curNode != null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пробел.

Suggested change
if(curNode != null) {
if (curNode != null) {

import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.test.Test


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему не используете @BeforeEach и @Nested? Кажется будто некоторые тесты для удобства можно сгруппировать и автоматически создавать для них одно и то же дерево.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

С этим согласна, учтем на будущее.

val actualValue = tree.find(2)
assertEquals(expectedValue, actualValue)
}

Copy link

@sgrishkova sgrishkova Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Еще можно отдельными тестами проверить:

  • insert на пустом дереве
  • при вставке нод с меньшим ключём уходит влево
  • при вставке нод с большим ключём уходит вправо

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У нас есть тесты, проверяющие insert и любой случай (вправо/влево/перезапись). Иначе бы и соответствующего покрытия не было. Выносить еще какие-то тесты было бы излишним.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я вижу, что у вас это проверяется, но если судить тесты только по названию, то insert node and perform left-right rotation будто проверяет только то, что узел куда-то вставляется, а потом совершается поворот.

То есть, если что-то пойдет не так, и тест завершится с ошибкой, первая мысль будет проверять повороты, хотя проблема может крыться в другом.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если вы еще раз посмотрите на реализацию, то увидите, что повороты вызываются исключительно внутри balance(). Так что я не вижу проблему.

val actualKeysAndHeights = tree.preorderTraverse()
assertEquals(expectedKeysAndHeights, actualKeysAndHeights)
}
} No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Много тестов на повороты, но кажется будто стоит отдельно проверить, получилось ли дерево в итоге (после вставки / удаления) сбалансированным. Правильно выполненный поворот еще не гарантирует сбалансированность (вдруг что-то было пропущено и дерево осталось несбалансированным).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод preorderTraverse() организован таким образом, что по возвращенному из него списку можно однозначно восстановить расположение узлов в дереве, а следовательно и его полную структуру. В наших тестах мы в качестве expectedKeysAndHeights подаем список вершин и их высот, который вернулся бы из preorderTraverse() в случае корректной работы - сбалансированного дерева как результат (то есть, когда все дети в правильных местах, а в случае АВЛ и РБ еще и соблюдены правила балансировки). Поэтому сравнить фактический список, пришедший из метода, с составленным вручную, достаточно, чтобы утверждать корректность не только узлов дерева, но и его структуры. Тестов на повороты не много, они покрывают каждый из 4 кейсов в методе balance.

Copy link

@sgrishkova sgrishkova Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Много" имела в виду в хорошем смысле, что рассмотрены не отдельные кейсы, а все.

Насчет сбланасированности согласна, придирка скорее к описанию тестов.
insert node and perform left-right rotation будто проверяет только поворот, а не баланс в целом. Да, по полученному списку можно проверить и сбалансированность, но будто это что-то косвенное. То есть если тест зафейлится, не будет сразу понятно, что пошло не так: поворот не сработал, или балансируется дерево не полностью.

Кажется будто это может отнять лишнее время при дебаге (особенно если человек потеинцально не знаком с кодом).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Опять-таки, это не что-то косвенное, так как повороты нужны только в балансировке. А если тест упадет, то найти ошибку поможет как раз actualKeysAndHeights. Конечно, мы напрямую не узнаем, в каком именно она методе, так как мы их не можем вызвать напрямую (они же не public). Но наш вывод служит хорошей подсказкой.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Справедливо.

Comment on lines 134 to 136
fun find(key: K): V? {
return findNodeByKey(key)?.value
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется будто название этого метода можно уточнить. По принимаемым аргументам и по ридми понятно, что осуществляется поиск по ключу, но в теории искать можно и по value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вся функциональность метода описана в ридми, и мы предполагаем, что пользователь прочитает его перед использованием библиотеки. Вообще, поиск узла по значению -- немного странное действие, учитывая, что в нашем дереве мы работаем исключительно по ключам, а значения там появляются только в контексте ввода/вывода. Реализация поиска по значению напоминала бы простой обход, что в принципе ломает концепцию дерева как эффективной структуры хранения/взаимодействия с упорядоченными данными. Как дополнительную фичу такой поиск, конечно, можно было бы реализовать, но точно не как основной метод

Copy link

@D31IRIUM D31IRIUM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Круто (итератор не оч)

Comment on lines +7 to +30
private fun getGrandparent(node: RBNode<K, V>): RBNode<K, V>? {
val parent = findParent(node) ?: return null
val grandparent = findParent(parent)
return grandparent
}

private fun getSibling(node: RBNode<K, V>): RBNode<K, V>? {
val parent = findParent(node) ?: return null
return if (node == parent.leftChild) {
parent.rightChild
} else {
parent.leftChild
}
}

private fun getUncle(node: RBNode<K, V>): RBNode<K, V>? {
val parent = findParent(node) ?: return null
val grandparent = findParent(parent)
return if (parent == grandparent?.leftChild) {
grandparent.rightChild
} else {
grandparent?.leftChild
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Каждый раз идти от корня дерева в поиске родителя нода?
В балансировке после ставки это повышает асимптотику до O(log n * log n), в теории и в балансировке после удаления тоже.

Зачем по два раза искать родителя в getGrandparent и getUncle? Переиспользование методов это прикольно, но тут ноды совсем уж рядом расположены.

Copy link
Contributor

@shvorobsofia shvorobsofia Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использовать или не использовать ссылку на родителя было опциональным решением. В нашем случае, выбранная стратегия позволила повысить переиспользуемость кода (например, поворотов) и уменьшить вероятность ошибок, связанных с кривой привязкой родителя.

Comment on lines +283 to +290
fun preorderTraverse(): List<Pair<K, Color>> {
val listOfNodes = mutableListOf<RBNode<K, V>>()
traverse(root, listOfNodes)
val listOfKeysAndColors = mutableListOf<Pair<K, Color>>()
listOfNodes.forEach { listOfKeysAndColors.add(Pair(it.key, it.color)) }
return listOfKeysAndColors
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кому нужен такой итератор, тестерам? А ой, действительно нужен...(´・ᴗ・`)
Если серьёзно, то возникает куча вопросов: почему возвращает не значение нодов, но цвет(что пользователю делать с ним?), почему итератор сделан так, а не с помощью интерфейса Iterator, например, почему preorder, а не сортированный по возрастанию ключей inorder, или вообще 3 разных метода под все order'ы?
Это даже итератором можно назвать с натяжкой, потому что массив пар - не итератор(впрочем, уже по этому массиву действительно можно итерироваться).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Начнем с того, что слова "итератор" ни в коде, ни в презентации нашей архитектуры, ни в требованиях к реализации библиотеки не было. Цитата из условия задания: "Также необходимо поддержать возможность итерирования по ключам и значениям, хранящимся в дереве, например, в порядке обхода в ширину или глубину (или как-то ещё иначе)". Мы были вольны выбрать любой вариант, писать свой метод или реализовывать итератор. Наш вариант реализует (упомянутый в условии) обход в глубину и описан в README довольно понятно, да и примеры приведены. Идея такого вывода пришла мне, поэтому поясню: так как библиотекой пользуются программисты, показалось, что им могут быть полезны такие сведения о структуре дерева, в том числе и цвет (в АВЛ с той же целью присутствует высота, для простого и понятного представления дерева); мы не преследовали цель как-то упростить себе тестирование, но впоследствии оказалось и правда удобно использовать обход. Выбор именно preorder и отсутствие вывода значений я уже прокомментировала вашей сокоманднице.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я правильно понимаю, что чтобы пройти все узлы дерева(и достать значения), нужно доставать каждый ключ из preorderTraversal, затем с помощью find находить соответствующий value?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-04-04 at 11 07 32 PM

Если вопрос в том, как доставать соответствующие ключам значения, то мы уже выяснили, что наша реализация не поддерживает этого в методе preorderTraverse, просто из-за излишней нагроможденности. Пользователь может вывести конкретное значение с помощью find, и мы посчитали это достаточным. Прикрепляю скрин, почему нам не очень импонирует идея выводить значения.

Comment on lines 161 to 165
private fun deleteCase1(node: RBNode<K, V>) {
val parent = findParent(node)
if(parent != null) deleteCase2(node)
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, я тоже респектую автору страницы про rb на вики, но ссылочку можно было и оставить в readme, раз код скопирован чуть менее чем полностью.
Это же относиться к балансировке после вставки.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Насколько я знаю, технология не является запатентованной или лицензированной, и указания авторства не требует. Более того, RB-дерево -- довольно популярная и много раз написанная-переписанная идея. А страница на вики есть и, например, у сортировки пузырьком :))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вообще, случаи, когда-куда-что вставлять, поворачивать и перекрашивать -- это вопрос не конкретной реализации, а устройства красно-черного дерева. Вопрос, как конкретно -- это уже реализация. Код все равно получается разный, и если вы руководствовались другой статьей, поясняющей работу КЧ, то это ваш выбор. delete() разделен на кейсы, возможно, из-за сильной громоздкости, хотя у меня есть свои причины против этого подхода.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

При чём тут технологии? Речь про код.
delete() разделён на кейсы не из-за сильной громоздкости, а потому что код в википедии разделён на кейсы.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

все еще не понимаю суть проблемы. красно-черное дерево написано задолго до нас, изобретать велосипед не было смысла изначально (и вряд ли бы получилось. учитывая количество реализаций, не знаю, как надо было извернуться, чтобы не быть ни на кого похожим. а главное, зачем). по поводу ссылки все было сказано выше. BTW чекните ссылки в about the project ридми, которые были там изначально ;))
жду появления в репо второй домашки первого сема ссылки на используемую сортировку, кстати :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ладно, закрываю тред с позором. ╥﹏╥
Просто мне казалось(и, если честно, до сих пор кажется), что если можно копировать реализацию методов с сайтов, то это ничем не отличается от "списывания" у других людей. Whatever
А вот касательно ссылок - тут я знатно опростоволосился и не заметил их(хотя и похоже на то, что вдохновение принесла как раз русская страничка в википедии, а не англицкая). Всё, пойду проветривать помещение

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

на лекции как-то раз объясняли, что, на наше же благо, патентованием алгоритмов занимаются только в очень сложных случаях, когда речь идет о, зачастую, огромной коммерческой выгоде и/или сложности патентуемого алгоритма. когда речь идет о предмете вроде красно-черного дерева, это все равно, что попытаться присвоить себе слово "окно" и заявлять, что никто больше не имеет права им пользоваться. пойду, кстати, тоже открою

Comment on lines +45 to +49
/* case 1: insertedNode is root */
if (root == insertedNode) {
insertedNode.color = Color.BLACK
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему для удаления выделены отдельные методы(removeCase1,2,3,...), а в балансировке после добавления тут всё в одном методе?

Copy link
Contributor

@shvorobsofia shvorobsofia Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В удалении кейсы решили вынести в отдельные методы просто для читаемости, потому что там код намного массивнее, чем во вставке. С заботой о ваших глазках :3

Comment on lines 58 to 59
val uncle = getUncle(insertedNode)
val grandparent = getGrandparent(insertedNode) ?: throw IllegalArgumentException("Every node by that point should have grandparent")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grandparent же буквально указывает на uncle, его найти - вопрос одного if else(´。_。`)
Но выглядит доходчиво и понятно, этого не отнять.

import nodes.Color
import nodes.RBNode

class RBTree<K : Comparable<K>, V>: balancedTree<K, V, RBNode<K, V>>() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Форматирование хромает, какие-то методы отделены друг от друга, какие-то - нет, также внутри методов тоже есть мелочи аля разное написание if else.

Comment on lines 112 to 114
private fun deleteLeaf(node: RBNode<K, V>) {
if (node.color != Color.RED) deleteCase1(node)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Используется один раз + название deleteLeaf... стоит ли говорить, что листовые узлы здесь, тащемта, null.
Да, докопался, а кому сейчас легко?

import kotlin.test.Test


class RBTreeTest {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jacoco показывает покрытие 72% веток.
Это следствие распыления балансировки после удаления на много разных методов, но всё равно неприятно. Впрочем, если отмести все ветки с exceptions, нарушающие инварианты rb, то покрытие уже неплохое.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У меня 76%, так что мы в эпсилон-окрестности крутого покрытия

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Неполное покрытие некоторых веток в основном связано с большим количеством исключений в реализации КЧ-дерева, их тестами не получится покрыть, а также с проверкой вершин на null (проверяем, является ли вершина null, и если нет, то она по умолчанию имеет чёрный цвет), но ведь в КЧ-дереве null вершины (leaf) и так являютя чёрными... упс! Этого можно было бы избежать, сделав отдельный класс для leaf-вершин, чтобы он всегда был чёрный, но мы решили этого не делать. ну типа KISS и все такое, покрытие в любом случае вышло приемлемое. Спасибо за вклад, Дамир!

Comment on lines +89 to +96
/* case 4: uncle is black, parent is red; grandparent, parent and node form a "triangle"
* G G
* / \
* P - left triangle P - right triangle
* \ /
* N N
*
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Схемы прикольные, я когда их увидел здесь, быстренько себе парочку подобных нарисовал :)

@homka122
Copy link

homka122 commented Apr 4, 2024

не увидел нигде никакой духоты

отсутствие хоть какого-либо интереса к решению и беззубый ответ был бы намного оскорбительнее и удручающее, чем то, что я увидел

я увидел интерес человека узнать больше про ваше решение, задать интересующие вопросы и высказать мысли о том, что ему показалось чем-то необычным

не без заблуждений или ошибок — но мы все учимся

я всегда был бы только рад увидеть подобный интерес к своей работе и получение такого фидбека (миша, твой фидбек мне очень нравится)

@DronShock кто у нас душит кого?

@n03d3n
Copy link

n03d3n commented Apr 4, 2024

Тут была паста, которую попросили убрать

@homka122
Copy link

homka122 commented Apr 4, 2024

@DronShock андрей...

@D31IRIUM
Copy link

D31IRIUM commented Apr 5, 2024

В интернете опять кто-то неправ :O
Есть такая особенность, что моё дерево фидбека вышло очень несбалансированным - критики много, а из хорошего почти ничего освещено не было. Это действительно оч неприятно, когда стараешься над какой-то мелочью, а её принимают как нечто само собой разумеющееся, замечая только негативные аспекты, тем самым принижая твои заслуги.
Ну так вотб, мне действительно очень понравились тесты! Я примерно за день до ревью мельком посмотрел на ваши тесты и ужаснулся - насколько же громозко, непонятно и куце выглядели(а некоторые отдельные и до сих пор выглядят) мои. Мало того, что взамен моих миллионов ассёртов на каждый тест вы очень элегантно обходите дерево с помощью preorderTraverse, так ещё и проверяете все инварианты - ключ и, в случае RB, цвет узла. Поэтому-то про тесты я ничего плохого не написал, больно уж классные они :)
Соня вообще молодец, что взялась за красно-чёрные деревья(я вот больше их трогать не хочу), поэтому надеюсь, что моё ревью ни она, ни пятая команда в целом не восприняли слишком близко к сердцу. PEACE(๑ت๑)ノ

sofyak0zyreva and others added 20 commits April 7, 2024 10:38
Beautify RBTree's and RBTest's code
Perform a post-review refactoring that contains optimization of BSTree tests by using BeforeEach and refactoring of abstractions (abstract tree, balanced tree, abstract node) to fit them into Kotlin coding conventions
Delete unnecessary for user files
Some of comments and exceptions messages were changed same as one variable name (rightSubtree). Unnecessary "when" constructions were replaced by "if" conditions
Remove table of contents, add general info about installing lib
…on of test code achieved through designing the test part of the lib similar to the main one
Full post-review change of tests architecture. Optimization of test code achieved through designing the test part of the lib similar to the main one
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants