Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An alternative implementation to speed up iterating successive DcmList elements using seek_to #114

Closed
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
4 changes: 4 additions & 0 deletions dcmdata/include/dcmtk/dcmdata/dclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ class DCMTK_DCMDATA_EXPORT DcmList
/// pointer to current node in list
DcmListNode *currentNode;

/// the current absolute position that is maintained in order to avoid O(n) lookup
/// when essentially iterating the elements using `seek_to`
unsigned long currentAbsolutePosition;

/// number of elements in list
unsigned long cardinality;

Expand Down
81 changes: 77 additions & 4 deletions dcmdata/libsrc/dclist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "dcmtk/ofstd/ofstream.h"
#include "dcmtk/dcmdata/dclist.h"

#include <limits>


// *****************************************
// *** DcmListNode *************************
Expand Down Expand Up @@ -51,10 +53,20 @@ DcmListNode::~DcmListNode()
// *****************************************


namespace
{
static const unsigned long invalidAbsolutePosition = std::numeric_limits<unsigned long>::max();
}


// ********************************


DcmList::DcmList()
: firstNode(NULL),
lastNode(NULL),
currentNode(NULL),
currentAbsolutePosition(invalidAbsolutePosition),
cardinality(0)
{
}
Expand All @@ -75,6 +87,7 @@ DcmList::~DcmList()
delete temp;
} while ( firstNode != NULL );
currentNode = firstNode = lastNode = NULL;
currentAbsolutePosition = invalidAbsolutePosition;
}
}

Expand All @@ -95,6 +108,7 @@ DcmObject *DcmList::append( DcmObject *obj )
node->prevNode = lastNode;
currentNode = lastNode = node;
}
currentAbsolutePosition = cardinality;
cardinality++;
} // obj == NULL
return obj;
Expand All @@ -117,6 +131,7 @@ DcmObject *DcmList::prepend( DcmObject *obj )
firstNode->prevNode = node;
currentNode = firstNode = node;
}
currentAbsolutePosition = 0;
cardinality++;
} // obj == NULL
return obj;
Expand All @@ -133,6 +148,7 @@ DcmObject *DcmList::insert( DcmObject *obj, E_ListPos pos )
if ( DcmList::empty() ) // list is empty !
{
currentNode = firstNode = lastNode = new DcmListNode(obj);
currentAbsolutePosition = 0;
cardinality++;
}
else {
Expand All @@ -155,6 +171,7 @@ DcmObject *DcmList::insert( DcmObject *obj, E_ListPos pos )
node->nextNode = currentNode;
currentNode->prevNode = node;
currentNode = node;
// NB: no need to update currentAbsolutePosition
cardinality++;
}
else //( pos==ELP_next || pos==ELP_atpos )
Expand All @@ -169,6 +186,7 @@ DcmObject *DcmList::insert( DcmObject *obj, E_ListPos pos )
node->prevNode = currentNode;
currentNode->nextNode = node;
currentNode = node;
currentAbsolutePosition++;
cardinality++;
}
}
Expand Down Expand Up @@ -206,6 +224,7 @@ DcmObject *DcmList::remove()
currentNode = currentNode->nextNode;
tempobj = tempnode->value();
delete tempnode;
// NB: no need to update currentAbsolutePosition
cardinality--;
return tempobj;
}
Expand All @@ -230,17 +249,37 @@ DcmObject *DcmList::seek( E_ListPos pos )
{
case ELP_first :
currentNode = firstNode;
if ( DcmList::valid() )
currentAbsolutePosition = 0;
else
currentAbsolutePosition = invalidAbsolutePosition;
break;
case ELP_last :
currentNode = lastNode;
if ( DcmList::valid() )
currentAbsolutePosition = cardinality - 1;
else
currentAbsolutePosition = invalidAbsolutePosition;
break;
case ELP_prev :
if ( DcmList::valid() )
{
currentNode = currentNode->prevNode;
if ( DcmList::valid() )
currentAbsolutePosition--;
else
currentAbsolutePosition = invalidAbsolutePosition;
}
break;
case ELP_next :
if ( DcmList::valid() )
{
currentNode = currentNode->nextNode;
if ( DcmList::valid() )
currentAbsolutePosition++;
else
currentAbsolutePosition = invalidAbsolutePosition;
}
break;
default:
break;
Expand All @@ -254,22 +293,55 @@ DcmObject *DcmList::seek( E_ListPos pos )

DcmObject *DcmList::seek_to(unsigned long absolute_position)
{
if (absolute_position >= cardinality)
{
/* invalid position */
currentNode = NULL;
currentAbsolutePosition = invalidAbsolutePosition;
return get( ELP_atpos );
}

if (currentAbsolutePosition != invalidAbsolutePosition)
{
const unsigned long distance = absolute_position >= currentAbsolutePosition
? absolute_position - currentAbsolutePosition
: currentAbsolutePosition - absolute_position;

// Are we seeking to a position that is closer to the current position, than to
// the start or end of the sequence? This is often the case, if we are using
// `seek_to` to essentially iterate over the sequence, for example. If so, then
// let's start iterating from the current position. Often the position we want
// is simply the next position (or maybe the previous one). Let's make those
// use cases be O(1), and not O(n).
Copy link
Author

Choose a reason for hiding this comment

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

This is the beef of the PR. Basically the complexity (of the updated seek_to method) should be O(min(m,n))), where m is the distance of the desired position from the current position (and n is the number of elements in the list). Note that in the relevant actual use case (DcmPixelSequence), it seems that typically m = 1.

if (distance <= absolute_position && distance < cardinality - absolute_position)
{
if (currentAbsolutePosition <= absolute_position)
while (currentAbsolutePosition < absolute_position)
seek( ELP_next );
else
while (currentAbsolutePosition > absolute_position)
seek( ELP_prev );

return get( ELP_atpos );
}
}

// Iterate from the start or the end of the list.

if (absolute_position < cardinality / 2)
{
/* iterate over first half of the list */
seek( ELP_first );
for (unsigned long i = 0; i < absolute_position; i++)
seek( ELP_next );
}
else if (absolute_position < cardinality)
else
{
assert(absolute_position < cardinality);
/* iterate over second half of the list (starting from the end) */
seek( ELP_last );
for (unsigned long i = absolute_position + 1; i < cardinality; i++)
seek( ELP_prev );
} else {
/* invalid position */
currentNode = NULL;
}
return get( ELP_atpos );
}
Expand Down Expand Up @@ -304,5 +376,6 @@ void DcmList::deleteAllElements()
firstNode = NULL;
lastNode = NULL;
currentNode = NULL;
currentAbsolutePosition = invalidAbsolutePosition;
cardinality = 0;
}
22 changes: 17 additions & 5 deletions dcmdata/tests/tsequen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,31 @@ OFTEST(dcmdata_sequenceInsert)
DcmSequenceOfItems sequence(DCM_OtherPatientIDsSequence);
/* add a large number of items to the sequence */
unsigned long counter = 0;
DcmItem* initiallyInsertedItems[NUMBER_OF_ITEMS];
for (unsigned long i = 0; i < NUMBER_OF_ITEMS; ++i)
{
if (sequence.insert(new DcmItem()).good())
DcmItem* item = new DcmItem();
initiallyInsertedItems[i] = item;
if (sequence.insert(item).good())
++counter;
}
/* check whether that worked (for-loop shouldn't take too long) */
OFCHECK_EQUAL(counter, NUMBER_OF_ITEMS);
/* access specific items (performance should be no issue) */
OFCHECK(sequence.getItem(0) != NULL);
OFCHECK(sequence.getItem(2) != NULL);
OFCHECK(sequence.getItem(0) == initiallyInsertedItems[0]);
OFCHECK(sequence.getItem(2) == initiallyInsertedItems[2]);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS) == NULL);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS - 1) != NULL);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS - 2) != NULL);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS - 1) == initiallyInsertedItems[NUMBER_OF_ITEMS - 1]);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS - 2) == initiallyInsertedItems[NUMBER_OF_ITEMS - 2]);
/* insert a single item before the current item */
DcmItem* item = new DcmItem();
OFBool before = true;
OFCHECK(sequence.insertAtCurrentPos(item, before).good());
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS - 2) == item);
/* the items after the new item should have been shifted by one */
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS - 1) == initiallyInsertedItems[NUMBER_OF_ITEMS - 2]);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS) == initiallyInsertedItems[NUMBER_OF_ITEMS - 1]);
OFCHECK(sequence.getItem(NUMBER_OF_ITEMS + 1) == NULL);
}


Expand Down