Showing posts with label data-structures. Show all posts
Showing posts with label data-structures. Show all posts

Tuesday, December 19, 2017

Interview quеstion: data structure to set all values in O(1)

Leave a Comment

Looking through interview questions on the Internet I encountered the following one.

Describe a data structure for which getValue(int index), setValue(int index, int value), and setAllValues(int value) are all O(1).

Though array is good enough for the first and second operations to be performed in O(1), what can be proposed for the third (setAllValues)?

10 Answers

Answers 1

How about an array of tuples {timestamp, value}, with an additional {timestamp, value} called all. Since you only care about the relative time of insertions, you can use a monotonically increasing id for the values of timestamp:

type Entry {   int timestamp,    int value }  type structure {     int     id     Entry   all     Entry[] array } 

Initialise all fields to 0. Then the following should work for you:

  • setValue(index i, value v):

    array[i] = {id++, value} 
  • value getValue(index i)

    if(all.timestamp > array[i].timestamp) return all.value else return array[i].value 
  • setAll(value v)

    all = {id++, value} 

A problem with this approach is that eventually you'll run out of ids for timestamp, and might wrap around. If you chose a 64 bit value to store timestamps, then this gives you 18,446,744,073,709,551,616 insertions or setAlls before this happens. Depending on the expected use of the datastructure, an O(n) cleanup phase might be appropriate, or you could just throw an exception.

Another issue that might need to be considered is multi-threading. Three obvious problems:

  • if id++ isn't atomic and two threads obtained a new id at the same time then you could get two entries with the same id.
  • if the incrementation of id and the assignment of the created tuple aren't atomic together (they're probably not) and there were two simultaneous inserts, then you could get a race condition where the older id wins.
  • if the assignment of the tuple isn't atomic, and there's an insert() at the same time as a get() then you might end up in a position where you've got say {new_id, old_value} in the array, causing the wrong value to be returned.

If any of these are problems, the absolute easiest solution to this is to put "not thread safe" in the documentation (cheating). Alternatively, if you can't implement the methods atomically in your language of choice, you'd need to put some sort of synchronisation locks around them.

Answers 2

How about an array of pointers to a single common value? Set the value, and all references will point to the single changed value in O(1)..

Answers 3

We can have a variable V which stores an int and an array of containing Tuple as {Value, id}..

And a global int variable G (which will act like identity in SQL and whenever any set or setAll operation is done its value get increment by 1)

initial all Ids and V value will be default say null..

so V = null All Tuple = {null, null} set(index i, val v) -> G = G+1, arr[i].Val = v, arr[i].Id = G  get(index i) -> if V.Id > arr[i].Id return V.Val else return arr[i]    set-all(val v) -> G= G+1, V.Id= G, V.Val = v 

Answers 4

I got the same question in one of the technical interviews.
Here is my complete ready-to-use Java-implementation including the test cases.

The key idea is to keep the value of setAll() in special variable (e.g. joker) and then trace the change of this value in a proper way.

In order to keep the code clean, some access modifiers have been abolished.

Node class:

class Node {      int value;     Integer joker;      Node(int val, Integer jok) {         value = val;         joker = jok;     } } 

DS class:

class DS {      Node[] arr;      DS(int len) {         arr = new Node[len];     } } 

DataStructure class:

class DataStructure {      private boolean isJokerChanged;     private Integer joker;     private DS myDS;      DataStructure(int len) {         myDS = new DS(len);     }      Integer get(int i) {          Integer result = null;          if (myDS.arr.length < i) {             return null;         }          // setAll() has been just called         if (isJokerChanged) {             return joker;         }          if (myDS.arr[i] == null) {              // setAll() has been previously called             if (joker != null) {                 result = joker;             } else {                 result = null;              }          } else if (myDS.arr[i].joker == joker) {             // cell value has been overridden after setAll() call             result = myDS.arr[i].value;         } else {             result = joker;         }          return result;     }      void setAll(int val) {         isJokerChanged = true;         joker = val;     }      void set(int i, int val) {         isJokerChanged = false;         myDS.arr[i] = new Node(val, joker);     } } 

Main class:

class Main {      public static void main(String[] args) {          DataStructure ds = new DataStructure(100);          Integer res;          res = ds.get(3);          ds.set(3, 10);          res = ds.get(3);          ds.setAll(6);          res = ds.get(3);          res = ds.get(15);          ds.set(4, 7);          res = ds.get(4);          res = ds.get(3);          ds.setAll(45);          ds.set(8, 2);          res = ds.get(3);     } } 

Answers 5

I was just asked this question very recently in an Interview. I came up with a hash table implementation. It would solve the problem of running out of the timestamp values but the thread safety feature needs to be implemented (probably using lazy initialization techniques)

Let's say in our class we have a private variable _defaultValue to hold a default value and we also have a private hashtable or dictionary _hashtable. SetAllValues could just set _defaultValue equal to the value passed and _hashtable initialized/set to a new hashtable and discard any reference to the old hash table. SetValue should just add a new value to _hashtable or update the value if the key ( or index) is already present in the _hashtable. GetValue should check whether the key (or index) is present in _hashtable, then return it, else return the value stored in _defaultValue.

This is my first answer on StackOverflow. I am a little lazy in writing up the code. Will probably edit the answer soon.

The interviewer nodded yes to this solution but insisted to implement it without using a hashtable. I guess, he was asking me to give a similar approach as Timothy's answer. And I wasn't able to get it at that moment :(. Anyways, Cheers!

EDIT: Posting the code (in C#)

class MyDataStruct {     private int _defaultValue;     private Dictionary<int,int> _hashtable;      public MyDataStruct()     {         _defaultValue = 0; // initialize default with some value         _hashtable = new Dictionary<int, int>();     }      public void SetAllValues(int val)     {         _defaultValue = val;         _hashtable = new Dictionary<int, int>();     }      public void SetValue(int index, int val)     {         if (_hashtable.ContainsKey(index))         {             _hashtable.Add(index, val);         }         else         {             _hashtable[index] = val;         }     }      public int GetValue(int index)     {         if (_hashtable.ContainsKey(index))         {             return _hashtable[index];         }         else         {             return _defaultValue;         }     } } 

Answers 6

All existing answers use a timestamp that is incremented on every setVal operation. This is not necessary. In fact, it is necessary only to increment the timestamp on setAll. Another issue some raised was arithmetic overflow. This can be handled without breaking the constant time bounds by updating a single cell on each setAll and performing the time comparison carefully.

How it works

The basic concept is essentially similar to that of the other answers, but with a twist.

What they do: Store the value used for the last setAll operation separately, and keep track of the time that operation was performed. Each time they perform a setVal, they store the current time along with the given value in the array. Each time they perform a getVal, they compare the time in the given location to the time the last setAll occurred, and then choose either the value at the location or the setAll value depending on which is greater.

Why this can be a problem: Suppose the current time overflows and a setAll operation occurs soon afterward. It will appear as though most of the stored array values are newer than the setAll value, when they are actually older.

The solution: Stop imagining that we're keeping track of the total amount of time that has passed since data structure initialization. Imagine a giant clock with a "second hand" that ticks not 60 times around the circle but rather 2^n times around the circle. The position of the second hand represents the time of the most recent setAll operation. Each setVal operation stores this time along with a value. So if we perform a setAll when the "clock" is at 45, and then perform six setVal operations on different elements, the setAll time and the times at all six locations will be the same. We wish to maintain the following invariant:

The time at a given element location equals the setAll time if and only if that element was set with setVal more recently than the last setAll operation.

Clearly, the procedure described above automatically ensures that if the element was set recently, then its time will equal the setAll time. The challenge is to make the reverse implication hold as well.

To be continued ....

The code

I've written this in Haskell because that is the language I know best, but it is not exactly the most natural language for the job.

{-# LANGUAGE BangPatterns #-} module RepArr where  import Control.Monad.Primitive import Data.Primitive.MutVar import qualified Data.Vector.Mutable as V import Data.Vector.Mutable (MVector) import Control.Applicative import Prelude hiding (replicate) import Control.Monad.ST import Data.Word  -- The Int in the MutVar is the refresh pointer data RepArr s a = RepArr (MutVar s (Word, a, Int)) (MVector s (Word,a))  -- Create a fancy array of a given length, initially filled with a given value replicate :: (PrimMonad m, Applicative m) => Int -> a -> m (RepArr (PrimState m) a) replicate n a = RepArr <$> newMutVar (0,a,0) <*> V.replicate n (0,a)  getVal :: PrimMonad m => RepArr (PrimState m) a -> Int -> m a getVal (RepArr allv vec) n = do   (vectime, vecval) <- V.read vec n   (alltime, allval, _) <- readMutVar allv   if (fromIntegral (alltime - vectime) :: Int) > 0     then return allval     else return vecval  setVal :: PrimMonad m => RepArr (PrimState m) a -> Int -> a -> m () setVal (RepArr allv vec) i v = do   (!alltime, _, _) <- readMutVar allv   V.write vec i (alltime, v)  setAll :: PrimMonad m => RepArr (PrimState m) a -> a -> m () setAll r@(RepArr allv vec) v = do   (oldt, _, oldc) <- readMutVar allv   getVal r oldc >>= setVal r oldc   let !newc = case oldc+1 of         op1 | op1 == V.length vec -> 0             | otherwise -> op1   let !newt = oldt+1   writeMutVar allv (newt, v, newc) 

To avoid potential (rare) garbage collection pauses, it's actually necessary to unbox the Int and Word values, as well as using unboxed vectors instead of polymorphic ones, but I'm not really in the mood and this is a fairly mechanical task.

Here's a version in C (completely untested):

#include <malloc.h>  struct Pair {   unsigned long time;   void* val; };  struct RepArr {   unsigned long allT;   void* allV;   long count;   long length;   struct Pair vec[]; };  struct RepArr *replicate (long n, void* val) {   struct RepArr *q = malloc (sizeof (struct RepArr)+n*sizeof (struct Pair));   q->allT = 1;   q->allV = val;   q->count = 0;   q->length = n;    int i;   for (i=0; i<n; i++) {     struct Pair foo = {0,val};     q->vec[i] = foo;   }   return q; }   void* getVal (struct RepArr *q, long i) {   if ((long)(q->vec[i].time - q->allT) < 0)     return q->allV;   else     return q->vec[i].val; }  void setVal (struct RepArr *q, long i, void* val) {   q->vec[i].time = q->allT;   q->vec[i].val = val; }  void setAll (struct RepArr *q, void* val) {   setVal (q, q->count, getVal (q, q->count));   q->allV = val;   q->allT++;   q->count++;   if (q->count == q->length)     q->count = 0; } 

Answers 7

/* 

At the time I am writing this, all of the solutions on this page will double (or more) the amount of space required to store the array. The following solution reduces the amount of wasted space from Ω(n) to θ(n/w), where w is the number of bits in a computer "word". On my machine, that's 64.

This prose in this answer is in C comments, so you can copy-and-paste this answer verbatim and compile it with your C compiler.

*/  #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <stdlib.h>  /* 

The problem is to support reading and writing values in an array in O(1) time along with bulk writes in which all values in the array are written at once in O(1) time. This is possible using a technique invented by Aho, Hopcroft and Ullman, as far as I know. I will present a version due to Gonzalo Navarro, "Constant-Time Array Initialization in Little Space".

The idea is to keep three metadata arrays along with the data array. We also keep two integers: unset, which is the last value used in a bulk write operation, and size, an approximation for the number of values that have been set since the last bulk write. At all times, the number of distinct values written since the last bulk write is between size and w * size.

The three metadata arrays describe information about blocks of w values in the data array. They are:

  • nth: nth[i] is the ith unique block to be written to since the last bulk write

  • inverse_nth: inverse_nth[i] is the order in in which the ith block of the array was written, counting from 0 at the last bulk write.

  • bitset: The jth bit of bitset[i] is 1 when the array cell numbered 64*i + j has been written to since the last bulk write.

bitset[i] and inverse_nth[i] are allowed to be invalid if i is not a member of the set {nth[0], nth[1], ... , nth[size-1]}. In other words, inverse_nth[i] and bitset[i] are valid if and only if inverse_nth[i] < size and nth[inverse_nth[i]] == i.

Rather than store three separate arrays of the same length, I chose to store one array, is_set, with three fields.

*/  typedef struct {   int nth_;   int inverse_nth_;   uint64_t bitset_; } IsSetCell;  typedef struct {   int unset_;   int size_;   IsSetCell is_set_[]; } IsSetArray;  typedef struct {   IsSetArray * metadata_;   int payload_[]; } ResettableArray;  /* 

To construct an array, we need a default value to return when the reading a value that has never been written to.

*/  ResettableArray * ConstructResettableArray(int n, int unset) {   ResettableArray* result =       malloc(offsetof(ResettableArray, payload_) + n * sizeof(int));   if (!result) return NULL;   n = (n + 63) / 64;   result->metadata_ =       malloc(offsetof(IsSetArray, is_set_) + n * sizeof(IsSetCell));   if (!result->metadata_) {     free(result);     return NULL;   }   result->metadata_->size_ = 0;   result->metadata_->unset_ = unset;   return result; }  void DestructResettableArray(ResettableArray * a) {   if (a) free(a->metadata_);   free(a); }  /* 

The bulk of the algorithm is in writing and reading the metadata. After IsSet() and Set() are defined (below), reading and writing the arrays is straightforward.

*/  bool IsSet(const IsSetArray * a, int i); void Set(IsSetArray * a, int i);  int GetValue(const ResettableArray * a, int i) {   if (!IsSet(a->metadata_, i)) return a->metadata_->unset_;   return a->payload_[i]; }  void SetValue(ResettableArray * a, int i, int v) {   a->payload_[i] = v;   Set(a->metadata_, i); }  void SetAllValues(ResettableArray * a, int v) {   a->metadata_->unset_ = v; }  /* 

The complex part of reading and writing is the bidirectional relationship between inverse_nth and nth. If they point to each other at location i (is_set[is_set[i].inverse_nth].nth == i) then location i contains valid data that was written after the last bulk write, as long as is_set[i].inverse_nth < size.

*/  uint64_t OneBit(int i) {   return UINT64_C(1) << i; }  bool IsSet(const IsSetArray * a, int i) {   const int cell = i/64, offset = i & 63;   const int inverse_nth = a->is_set_[cell].inverse_nth_;   return inverse_nth < a->size_ && a->is_set_[inverse_nth].nth_ == cell &&          a->is_set_[cell].bitset_ & OneBit(offset); }  void Set(IsSetArray * a, int i) {   const int cell = i/64, offset = i & 63;   const int inverse_nth = a->is_set_[cell].inverse_nth_;   if (inverse_nth >= a->size_ || a->is_set_[inverse_nth].nth_ != cell) {     a->is_set_[cell].inverse_nth_ = a->size_;     a->is_set_[cell].bitset_ = 0;     a->is_set_[a->size_].nth_ = cell;     ++a->size_;   }   a->is_set_[cell].bitset_ |= OneBit(offset); } 

Answers 8

Python example

class d:     def __init__(self, l):         self.len = l         self.g_p = [i for i in range(self.len)]         self.g_v = [0 for i in range(self.len)]         self.g_s = self.len - 1         self.g_c = 0        def getVal(self, i):         if (i < 0 or i >= self.len):             return          if (self.g_p[i] <= self.g_s):             return self.g_v[self.g_p[i]]          return self.g_c      def setVal(self, i, v):         if (i < 0 or i >= self.len):             return          if (self.g_p[i] > self.g_s):             self.g_s += 1              self.g_p[self.g_s], self.g_p[i] = self.g_p[i], self.g_p[self.g_s]          self.g_v[self.g_p[i]] = v      def setAll(self, v):         self.g_c = v         self.g_s = -1 

Answers 9

Regarding Timothy Jone's answer:

A problem with this approach is that eventually you'll run out of ids for timestamp, and might wrap around. If you chose a 64 bit value to store timestamps, then this gives you 18,446,744,073,709,551,616 insertions or setAlls before this happens. Depending on the expected use of the datastructure, an O(n) cleanup phase might be appropriate, or you could just throw an exception.

This is exactly the worst case scenario which make this solution O(n) too, and not O(1). This stucture, although saves a lot of potential O(n) insert operation, is still in O(n) effeciency.

Answers 10

The correct solution in C#:

public sealed class SpecialDictionary<T, V> {     private Dictionary<T, Tuple<DateTime, V>> innerData;     private Tuple<DateTime, V> setAllValue;     private DateTime prevTime;      public SpecialDictionary()     {         innerData = new Dictionary<T, Tuple<DateTime, V>>();     }     public void Set(T key, V value) => innerData[key] = new Tuple<DateTime, V>(GetTime(), value);     public void SetAll(V value) => setAllValue = new Tuple<DateTime, V>(GetTime(), value);     public V Get(T key)     {         Tuple<DateTime, V> tmpValue = innerData[key];         if (setAllValue?.Item1 > tmpValue.Item1)         {             return setAllValue.Item2;         }         else         {             return tmpValue.Item2;         }     }     private DateTime GetTime()     {         if (prevTime == null)         {             prevTime = DateTime.Now;          }         else         {             if (prevTime == DateTime.Now)             {                 Thread.Sleep(1);             }             prevTime = DateTime.Now;         }         return prevTime;     } } 

And the test:

static void Main(string[] args) {     SpecialDictionary<string, int> dict = new SpecialDictionary<string, int>();     dict.Set("testA", 1);     dict.Set("testB", 2);     dict.Set("testC", 3);     Console.WriteLine(dict.Get("testC"));     dict.SetAll(4);     dict.Set("testE", 5);     Console.WriteLine(dict.Get("testC"));     Console.WriteLine(dict.Get("testE"));     Console.ReadKey(); } 
Read More

Saturday, November 11, 2017

JavaScript / Node.js best practice to save JSON objects AND most efficient way to retrieve certain values

Leave a Comment

I'm building a Node.js app that will store a huge amount of data, so I want to plan ahead and think of how I should structure the data.

Let's say I want to save 500,000 student accounts information:

       ID:  unique string,   // SID0001  username:  string,          // moe-kanan  password:  string,          // 123123      Name:  string,          // Moe kanan       Age:  int,             // 1 to 100     grade:  string,          // A, B, C or D 

Now, what is the best, fastest and most efficient way to structure the data to get a specific student's account information? Example, If a student wants to login, we have to check their credentials.

Therefore, If we save the information as an array of students, that means we will have to loop through the array. Will this slow the app if we have a huge amount of people tries to login at the same time?

I came up with two different ways to do it, but I don't know which one is faster and more efficient. Please explain that in your answers.


1. First method

Store them as JSON objects, and the object key will be the unique id - in this case would be the student ID. Example:

var database = {}; //NOTICE this is an object  database["SID0001"] = {       "ID":       "SID0001",        "username": "moe-kanan",        "password": "123123",        "name":     "Moe Kanan",        "age":      99,        "grade":    "A"  } 

in this method, I don't have to loop. I can get the credentials just by doing this:

var username = database["SID0001"].username;  //moe-kanan var password = database["SID0001"].password;  //123123 

2. Second method

var database = []; //NOTICE this is an array  database.push({       "ID":       "SID0001",        "username": "moe-kanan",        "password": "123123",        "name":     "Moe Kanan",        "age":      99,        "grade":    "A"  });  var getStudentInfo = (id) => {     let obj = database.filter(studen =>  studen.ID == id)[0];   //NOTICE the [0]         return {"username": obj.username, "password": obj.password} }  getStudentInfo("SID0001"); //{username: "moe-kanan", password: "123123"} 

Please feel free to add better solutions :) I really appreciate it!

NOTE: Keep in mind, I don't want to use a database for now, but I will use MongoDB in the future.

2 Answers

Answers 1

It pretty obvious that the first method using an object much faster/efficient that using an array.

Time complexity is O(1) using a hash map as opposed to O(n) using an array.

As pointed out as others, the only real answer is that you should use a database.

Answers 2

Assuming you want to store your data in the filesystem, I assume those are flat JSON files inside a directory. We are after the O(1) cost of retrieving the data to be as efficient as possible.

Personally I would go after a file per row solution as it would be easy to maintain and implemement.

Given each row has a unique ID we could store all the files inside a 3 levels deep directory tree, where the first directory would map to the first character of the ID, second directory would map to the second character and so on:

Given ID 0001, the path to the file would be:

/storage-directory/0/0/0/0001.json 

That way we could retrieve the data in one single step given the ID. However there are half a milion files so in each directory so in each directory there would be lots of files since each ID as far as I can tell is padded with zeros. That would degrade performance somewhat because most of the filesystems does not like having too many files inside one directory.

We could use a deterministic hashing function (like SHA1 for instance) to hash the ID so a larger number of characters would be available.

SHA1(0000001) produces 82c27eaf3472b30a873d39f4342f5e54de9532b9

so the row could be stored as:

/storage-directory/8/2/c/0000001.json 

Naive implementation of the getStudentInfo method could be:

this.getStudentInfo = (id) => {     let index = this.sha1Index(id);     let key = index[0]+"/"+index[1]+"/"+index[2]+"/"+id+".json";      return fs.parseJson(this.storageDirectory+"/"+key); } 

You would have to ensure to always normalize the ID before calculating the index because SHA1 would produce different hash for let say 1 and 001 while this is the same row (strip leftmost 0 for instance).

Congratulations, you have just invented your first key-value store.

Read More

Wednesday, April 5, 2017

Dealing with disassociated records in a poorly designed database

Leave a Comment

Overview

I have inherited a website that allows users to order customised products. The customisations were saved in a way that disassociates them from their record. I would like to modify the db so these records can be associated.

Example

Users can get Product #1 "stock", or customise it, changing as many as 10 different properties. Let's say color, fabric, width, height etc.

Orders can, and regularly do, contain multiple products, each of which may be customised.

Additionally, users can save their orders, so they can re-order later.

When the database was designed, the details of the order was neatly organised into individual columns. Customer name, address, payment type etc. But the list of products and more notably their customisations were saved as a JSON string in a single column. For ease, let's call this column the "cart".

Basically, the order table has a column cart and the cart column contains a JSON-formatted list of products and customisations.

Unfortunately, the JSON object has reference ids to the product table, but lacks references to the customisation table. Instead it uses a bunch of strings meant for a human to read. Fortunately those strings exist in the customisation table, but they were written as the cart was created.

The problem we face is that the list of customisations can be changed by a CMS. So far, they haven't been changed. 🙏 But they will need to be soon and that's going to cause problems:

Problems

  1. If a customisation option is removed (say, a fabric option) and a customer re-orders from an old saved order, we need to be able to parse the cart, detect this and warn them of the change.

  2. Customisations are currently immutable. Once a product is added to the cart, it cannot be changed. Users need to delete and re-add to make a single change. Poor UX.

  3. If anyone changes the human-readable text on a customisation we're dead. ☠️

Questions

  • How would you design this if you were staring from scratch?

  • How might we go about converting the current implementation and legacy data to this new schema?

I don't know if stack is notable, but we're on Postgres and Django-Python.

2 Answers

Answers 1

I would implement this with the following tables:

Products {   productId                   // primary key   name   price }  Customization_Types {   customizationTypeId         // primary key   name                        // e.g. COLOR, FABRIC, LENGTH }  Customizations {   customizationId             // primary key   customizationTypeId         // foreign key   value                       // e.g. BEIGE, VELVET, 8 }  Product_Customizations {   productCustomizationId      // primary key   productId                   // foreign key   customizationId             // foreign key   priceModifier               // price markup for applying the customization   isValid                     // false if this record is invalid/obsolete }  Orders {   orderId                     // primary key   customerId                  // foreign key }  Product_Orders {   productOrderId              // primary key   orderId                     // foreign key   productId                   // foreign key   quantity }  Customization_Orders {   customizationOrderId        // primary key   productOrderId              // foreign key   productCustomizationId      // foreign key } 

The Products table contains the data for your base products - name, price, etc

The Customization_Types table contains the type names for your different customizations - COLOR, FABRIC, LENGTH, etc

The Customizations table contains a link to a customizationTypeId as well as a legal value - I'm assuming that users can't enter arbitrary numerical values (for e.g. LENGTH or WIDTH) i.e. they're given a drop-down box instead of a text box, however if they can enter arbitrary numerical data then you'll need MIN/MAX fields that are null for named constraints (so e.g. you could have Type:COLOR/Value:BEIGE/Min:NULL/Max:NULL or Type:LENGTH/Value:NULL/Min:4/Max:8)

The Product_Customizations table links a Customization to a Product, so for example if ProductX can come in BEIGE then you would create a Product_Customization record that links ProductX to BEIGE.

The Orders table just contains an orderId and anything else relevant to the order (e.g. a link to the customerId and shippingAddressId)

Product_Orders links a product to an order

Customization_Orders links a Product_Customization to a Product_Order


Let's say a customer orders ProductX in BEIGE and LENGTH=8, then you would create an Order record, a Product_Order record with a link to ProductX, and two Customization_Order records - one linked to COLOR=BEIGE and one linked to LENGTH=8.

This should make it easy to modify a product's customizations without having to reload the entire product - the user can modify color to COLOR=RED without touching the length customization (delete the old Customization_Order:COLOR=BEIGE record and create a new COLOR=RED record), or the user can remove the length customization without touching the color customization (delete the old Customization_Order:LENGTH=8 record).

When reloading an old order/product you can quickly verify that the same productCustomizationIds still apply to the product in question, else flag the user. Additionally, you can flag the user if the customization still applies but the customization's price modifier has changed.


As far as converting the legacy data, I'm not familiar with Python but I do have experience with reading JSON via Java and I'm assuming that Python offers similar if not better libraries for this. The trick is going to be matching the existing data to pre-loaded Product_Customization data - if the data fails to match then create a new Product_Customization row corresponding to it with isValid=FALSE (this is assuming that the customization in question is no longer offered), and when you get a chance manually iterate through the invalid Product_Customization rows to ensure that these really are unmatched customizations and not just parsing errors.

Answers 2

Little improvement to Zim-Zam's answer.

Even better approach is to store not plain values (BEIGE, VELVET, 8) as customization parameters, but kind of schema from which code can build up correct view of a customization.

It could be just JSON/XML formatted text. And the entity that is responsible for building view and applying logic should be able to work with JSON data of different versions.

For example, if properties of a customization have changed and something new has been added, in that case you only need to change code and adjusted JSON will be saved. No need to change existing data. Also there should be possibility to read old JSON versions with old properties and work with it.

Two possible ways of what to do if you read an old entity from DB:

  1. View builder will ignore all old properties of a customization, add new properties and set their values to default. I would go with that personally.
  2. Old view is presented to user, but when user clicks, for example, Ok button or Finish, additional logic will check that there are old properties and notifies user that they should be removed manually or just removes them automatically.

More flexible approach that requires only code changes without touching db and allows to show user old customization properties if it is necessary.

Update: Customizations could have two kind of properties: one that administrator can define, such as title or price, which are not frequently changed and common for all customizations and another one such as size and color which could be changed frequently, could have user defined values and are not common for all customizations.

The first kind should be stored in Customization table as separate columns. That will allow to changed such properties in administrative panel and have all previously stored data consistent.

The second kind of properties could be 1) frequently changed 2) not all customization types could have such properties. It is a bad idea to store them as separate columns because if there are huge amount of data, changing column type or adding new column could cause performance degradation and sometimes could not be possible due to incompatible types of properties.
Actually, if they are stored as separate columns, you are probably will have to change code to support new properties anyway.

My idea is that you still allow administrator to change type of such properties and add new one or remove old one through some interface. The key thing here is that you are storing JSON data like this

{     "properties": {             {                 "propertyName": "height",                 "propertyType": "int",                 "min" : 10,                 "max" : 25,             },             {                 "propertyName": "color",                 "propertyType": "color",             },             {                 "propertyName": "anotherCustomField",                 "propertyType": "text",             },         } } 

What's left to do is to implement view builders or renderers for all kinds of property type. And add a separate table with only values. You fetched a customization record from db, you found out which customization properties there are, checked which one are still valid and rendered only valid ones. If administrator changed type of customization's property or just removed one, you marked that customization's property as not valid in db and that's all the work. No code changes, no database schema changes.

Read More

Tuesday, March 29, 2016

What is a Memory-Efficient Doubly Linked List in C?

Leave a Comment

I had come across the term "Memory-Efficient Doubly Linked List" while reading a book on C Data structures. It just had one line saying that a memory-efficient doubly linked list uses less memory than a normal doubly linked list, but does the same job. Nothing more was explained, and no example was given. Just it was given that this has been taken from a journal, and "Sinha" in Brackets.

After searching on Google, the closest I came to was this. But, I couldn't understand anything.

Can someone explain me what is a Memory-Efficient Doubly Linked List in C? How is it different from a normal Doubly Linked List?

EDIT: Okay, I made a serious mistake. See the link I had posted above, was the second page of the article. I didn't see that there was a first page, and thought the link given was the first page. The first page of the article actually gives an explanation, but I don't think it is perfect. It only talks of the basics concepts of Memory-Efficient Linked List or XOR Linked List.

5 Answers

Answers 1

I know that this is my second answer, but I think the explanation I provide here might be better, than the last answer.



A Memory-Efficient Linked List is more often called a XOR Linked List as this is totally dependent on the XOR Logic Gate and its properties.


Is it different from a Doubly Linked List?

Yes, it is. It is actually doing nearly the same job as a Doubly Linked List, but it is different.

A Doubly-Linked List is storing two pointers, which point at the next and the previous node. Basically, if you want to go back, you go to the address pointed by the back pointer. If you want to go forward, you go to the address pointed by the next pointer. It's like:

enter image description here

A Memory-Efficient Linked List, or namely the XOR Linked List is having only one pointer instead of two. This stores the previous address (addr (prev)) XOR (^) the next address (addr (next)). When you want to move to the next node, you do certain calculations, and find the address of the next node. This is the same for going to the previous node.It is like:

enter image description here


How does it work?

The XOR Linked List, as you can make out from its name, is highly dependent on the logic gate XOR (^) and it's properties.

It's properties are:

|-------------|------------|------------| |    Name     |   Formula  |    Result  | |-------------|------------|------------| | Commutative |    A ^ B   |    B ^ A   | |-------------|------------|------------| | Associative | A ^ (B ^ C)| (A ^ B) ^ C| |-------------|------------|------------| | None (1)    |    A ^ 0   |     A      | |-------------|------------|------------| | None (2)    |    A ^ A   |     0      | |-------------|------------|------------| | None (3)    | (A ^ B) ^ A|     B      | |-------------|------------|------------| 

Now let's leave this aside, and see what each node stores:

The first node, or the head, stores 0 ^ addr (next) as there is no previous node or address. It looks like:

enter image description here

Then the second node stores addr (prev) ^ addr (next). It looks like:

enter image description here

The picture above shows node B, or the second node. A and C are address's of the third and first node. All the node, except the head and the tail, are like the above one.

The tail of the list, does not have any next node, so it stores addr (prev) ^ 0. It looks like:

enter image description here

Before seeing how we move, let's see the representation of a XOR Linked List again:

enter image description here

When you see

enter image description here

it clearly means there is one link field, using which you move back and front.

Also, to note that when using a XOR Linked List, you need to have a temporary variable (not in the node), which stores the address of the node you were in before. When you move to the next node, you discard the old value, and store the address of the node you were in earlier.

Moving from Head to the next node

Let's say you are now at the first node, or at node A. Now you want to move at node B. This is the formula for doing so:

Address of Next Node = Address of Previous Node ^ pointer in the current Node 

So this would be:

addr (next) = addr (prev) ^ (0 ^ addr (next)) 

As this is the head, the previous address would simply be 0, so:

addr (next) = 0 ^ (0 ^ addr (next)) 

We can remove the parentheses:

addr (next) = 0 ^ 0 addr (next) 

Using the none (2) property, we can say that 0 ^ 0 will always be 0:

addr (next) = 0 ^ addr (next) 

Using the none (1) property, we can simplify it to:

addr (next) = addr (next) 

You got the address of the next node!

Moving from a node to the next node

Now let's say we are in a middle node, which has a previous and next node.

Let's apply the formula:

Address of Next Node = Address of Previous Node ^ pointer in the current Node 

Now substitute the values:

addr (next) = addr (prev) ^ (addr (prev) ^ addr (next)) 

Remove Parentheses:

addr (next) = addr (prev) ^ addr (prev) ^ addr (next) 

Using the none (2) property, we can simplify:

addr (next) = 0 ^ addr (next) 

Using the none (1) property, we can simplify:

addr (next) = addr (next) 

And you get it!

Moving from a node to the node you were in earlier

If you aren't understanding the title, it basically means that if you were at node X, and have now moved to node Y, you want to go back to the node visited earlier, or basically node X.

This isn't a cumbersome task. Remember that I had mentioned above, that you store the address you were at in a temporary variable. So the address of the node you want to visit, is lying in a variable:

addr (prev) = temp_addr 

Moving from a node to the previous node

This isn't the same as mentioned above. I mean to say that, you were at node Z, now you are at node Y, and want to go to node X.

This is nearly same as moving from a node to the next node. Just that this is it's vice-versa. When you write a program, you will use the same steps as I had mentioned in the moving from one node to the next node, just that you are finding the earlier element in the list than finding the next element.

I don't think I need to explain this.


Advantages of XOR Linked List

  • This uses less memory than a Doubly Linked List. Approximately 33% less.

  • It uses only one pointer. This simplifies the structure of the node.

  • As doynax said, a sub-section of a XOR can be reversed in constant time.


Disadvantages of XOR Linked List

  • This is a bit tricky to implement. It has higher chances of a failure, and debugging it is quite difficult.

  • All conversions (in case of an int) have to take place to / from uintptr_t

  • You cannot just acquire the address of a node, and start traversing (or whatever) from there. You have to always start from the head or tail.

  • You cannot go jumping, or skipping nodes. You have to go one by one.

  • Moving requires more operations.

  • It is difficult to debug a program which is using a XOR Linked List. It is much easier to debug a Doubly Linked List.


References

Answers 2

I would recommend seeing my second answer to this question, as it is much clearer. But I am not saying this answer is wrong. This is also correct.



A Memory-Efficient Linked List is also called a XOR Linked LIST.

How Does It Work

A XOR (^) Linked List is a Link List in which instead of storing the next and back pointers, we just use one pointer to do the job of both the next and back pointers. Let's first see the XOR logic gates properties:

|-------------|------------|------------| |    Name     |   Formula  |    Result  | |-------------|------------|------------| | Commutative |    A ^ B   |    B ^ A   | |-------------|------------|------------| | Associative | A ^ (B ^ C)| (A ^ B) ^ C| |-------------|------------|------------| | None (1)    |    A ^ 0   |     A      | |-------------|------------|------------| | None (2)    |    A ^ A   |     0      | |-------------|------------|------------| | None (3)    | (A ^ B) ^ A|     B      | |-------------|------------|------------| 

Lets now take an example. We have a doubly linked list with four nodes: A, B, C, D. Here's how it looks:

enter image description here

If you see, each node has two pointers, and 1 variable for storing data. So we're using three variables.

Now if you're have a Doubly Linked List with lot's of nodes, the memory it will be using will be too much. Too make it more efficient, we use a Memory-Efficient Doubly Linked List.

A Memory-Efficient Doubly Linked List is a Linked List in which we use just one pointer to move back and forth using XOR and it's properties.

Here's a pictorial representation:

enter image description here

How do you move back and forth?

You have a temporary variable (only one, not in the node). Let's say you're traversing the node from left to right. So you start at node A. In the pointer of node A, you store node B's address. You then move to node B. While moving to node B, in the temporary variable, you store node A's address.

The link (pointer) variable of node B has the address of A ^ C. You would take the previous node’s address (which is A) and XOR it with the current link field , giving you the address of C. Logically, this would look like:

A ^ (A ^ C) 

Let's now simplify the equation. We can remove the parentheses and rewrite it because of the Associative property like:

A ^ A ^ C 

We can further simplify this to

0 ^ C 

because of the second ("None (2)" as stated in the table) property.

Because of the first ("None (1)" as stated in the table) property, this is basically

C 

If you can't understand all this, just simply see the third property ("None (3)" as stated in the table).

Now, you got the address of node C. This will be the same process for going back.

Let's say that you were going from node C to node B. You would store the address of node C in the temporary variable, do the process given above again.

NOTE: Everything like A, B, C are addresses. Thanks to Bathsheba for telling me to make it clear.

Disadvantages of XOR Linked List

  • As Lundin mentioned, all the conversions have to be done from/to uintptr_t.

  • As Sami Kuhmonen mentioned, you have to start from a well-defined start point, not just a random node.

  • You cannot just jump a node. You have to go in order.

Also note that a XOR Linked List is not better than a doubly-linked list in most cases.

References

Answers 3

This is an old programming trick that allows you to save memory. I don't think it's used much anymore, since memory is no longer as tight a resource as it was in the old days.

The basic idea is this: In a conventional doubly-linked list, you have two pointers to adjacent list elements, a "next" pointer which points to the next element, and a "prev" pointer which points to the previous element. You can therefore traverse the list either forwards or backwards by using the appropriate pointers.

In the reduced-memory implementation, you replace "next" and "prev" with a single value, which is the bitwise exclusive-OR (bitwise-XOR) of "next" and "prev". You therefore reduce the storage for the adjacent element pointers by one half.

Using this technique, it is still possible to traverse the list in either direction, but you need to know the address of the previous (or next) element to do so. For instance, if you are traversing the list in the forward direction, and you have the address of "prev", then you can get "next" by taking the bitwise-XOR of "prev" with the current combined pointer value, which is "prev" XOR "next". The result is "prev" XOR "prev" XOR "next", which is just "next". The same can be done in the opposite direction.

The downside is that you can't do things like delete an element, given a pointer to that element, without knowing the address of either the "prev" or "next" element, since you have no context with which to decode the combined pointer value.

The other downside is that this sort of pointer trick circumvents the normal data type checking mechanism that a compiler may expect.

It's a clever trick, but in all honesty I see very little reason to use it these days.

Answers 4

OK, so you've seen the XOR linked list, which saves you one pointer per item... but that is an ugly, ugly data structure, and far from the best you can do.

If you're worried about memory, it's almost always better to use a doubly-linked list with more than 1 element per node, like a linked list of arrays.

For example, while an XOR linked list costs 1 pointer per item, plus the item itself, A doubly-linked list with 16 items per node costs 3 pointers for each 16 items, or 3/16 pointers per item. (the extra pointer is the cost of the integer that records how many items are in the node) That is less than 1.

In addition to the memory savings, you get advantages in locality because all 16 items in the node are next to each other in memory. Algorithms that iterate through the list will be faster.

Note that an XOR-linked list also requires you to allocate or free memory each time you add or delete a node, and that is an expensive operation. With the array-linked list, you can do better than this by allowing nodes to be less than completely full. If you allow 5 empty item slots, for example, then you only have allocate or free memory on every 3rd insert or delete at worst.

There are many possible strategies for determining how and when to allocate or free nodes.

Answers 5

You already got a pretty elaborate explanation of XOR linked list, I'll share a few more ideas on memory optimization.

  1. Pointers normally take 8 bytes on 64 bit machines. It's necessary to address any point in RAM beyond 4GB addressable with 32 bit pointers.

  2. Memory managers typically deal with fixed size blocks, not bytes. E.g. C malloc usually allocates within 16 byte granularity.

These two things imply that if your data is 1 byte, corresponding doubly linked list element will take 32 bytes (8+8+1, rounded up to the nearest 16 multiple). With XOR trick you can get it down to 16.

However, to optimize even further, you can consider using your own memory manager, that: (a) deals with blocks at lower granuarity, such as 1 byte or maybe even go into bits, (b) has stricter limit on overall size. For example, if you know your list will always fit inside a continuous block of 100 MB, you only need 27 bits to address any byte within that block. Not 32 or 64 bits.

If you aren't developing a generic list class, but you know specific usage patterns for your application, in many cases implementing such memory manager may be an easy job. For example, if you know you will never allocate more than 1000 elements and each element takes 5 bytes, you can implement memory manager as 5000-byte array with a variable that holds index of the first free byte, and as you allocate extra element, you just take that index and move it forward by the allocated size. In this case your pointers will not be real pointers (like int*) but will be just indexes inside that 5000-byte array.

Read More

Saturday, March 19, 2016

What is a Memory-Efficient Doubly Linked List in C?

Leave a Comment

I had come across the term "Memory-Efficient Doubly Linked List" while reading a book on C Data structures. It just had one line saying that a memory-efficient doubly linked list uses less memory than a normal doubly linked list, but does the same job. Nothing more was explained, and no example was given. Just it was given that this has been taken from a journal, and "Sinha" in Brackets.

After searching on Google, the closest I came to was this. But, I couldn't understand anything.

Can someone explain me what is a Memory-Efficient Doubly Linked List in C? How is it different from a normal Doubly Linked List?

EDIT: Okay, I made a serious mistake. See the link I had posted above, was the second page of the article. I didn't see that there was a first page, and thought the link given was the first page. The first page of the article actually gives an explanation, but I don't think it is perfect. It only talks of the basics concepts of the XOR Linked List.

5 Answers

Answers 1

I know that this is my second answer, but I think the explanation I provide here might be better, than the last answer.



A Memory-Efficient Linked List is more often called a XOR Linked List as this is totally dependent on the XOR Logic Gate and its properties.


Is it different from a Doubly Linked List?

Yes, it is. It is actually doing nearly the same job as a Doubly Linked List, but it is different.

A Doubly-Linked List is storing two pointers, which point at the next and the previous node. Basically, if you want to go back, you go to the address pointed by the back pointer. If you want to go forward, you go to the address pointed by the next pointer. It's like:

enter image description here

A Memory-Efficient Linked List, or namely the XOR Linked List is having only one pointer instead of two. This stores the previous address (addr (prev)) XOR (^) the next address (addr (next)). When you want to move to the next node, you do certain calculations, and find the address of the next node. This is the same for going to the previous node.It is like:

enter image description here


How does it work?

The XOR Linked List, as you can make out from its name, is highly dependent on the logic gate XOR (^) and it's properties.

It's properties are:

|-------------|------------|------------| |    Name     |   Formula  |    Result  | |-------------|------------|------------| | Commutative |    A ^ B   |    B ^ A   | |-------------|------------|------------| | Associative | A ^ (B ^ C)| (A ^ B) ^ C| |-------------|------------|------------| | None (1)    |    A ^ 0   |     A      | |-------------|------------|------------| | None (2)    |    A ^ A   |     0      | |-------------|------------|------------| | None (3)    | (A ^ B) ^ A|     B      | |-------------|------------|------------| 

Now let's leave this aside, and see what each node stores:

The first node, or the head, stores 0 ^ addr (next) as there is no previous node or address. It looks like:

enter image description here

Then the second node stores addr (prev) ^ addr (next). It looks like:

enter image description here

The picture above shows node B, or the second node. A and C are address's of the third and first node. All the node, except the head and the tail, are like the above one.

The tail of the list, does not have any next node, so it stores addr (prev) ^ 0. It looks like:

enter image description here

Before seeing how we move, let's see the representation of a XOR Linked List again:

enter image description here

When you see

enter image description here

it clearly means there is one link field, using which you move back and front.

Also, to note that when using a XOR Linked List, you need to have a temporary variable (not in the node), which stores the address of the node you were in before. When you move to the next node, you discard the old value, and store the address of the node you were in earlier.

Moving from Head to the next node

Let's say you are now at the first node, or at node A. Now you want to move at node B. This is the formula for doing so:

Address of Next Node = Address of Previous Node ^ pointer in the current Node 

So this would be:

addr (next) = addr (prev) ^ (0 ^ addr (next)) 

As this is the head, the previous address would simply be 0, so:

addr (next) = 0 ^ (0 ^ addr (next)) 

We can remove the parentheses:

addr (next) = 0 ^ 0 addr (next) 

Using the none (2) property, we can say that 0 ^ 0 will always be 0:

addr (next) = 0 ^ addr (next) 

Using the none (1) property, we can simplify it to:

addr (next) = addr (next) 

You got the address of the next node!

Moving from a node to the next node

Now let's say we are in a middle node, which has a previous and next node.

Let's apply the formula:

Address of Next Node = Address of Previous Node ^ pointer in the current Node 

Now substitute the values:

addr (next) = addr (prev) ^ (addr (prev) ^ addr (next)) 

Remove Parentheses:

addr (next) = addr (prev) ^ addr (prev) ^ addr (next) 

Using the none (2) property, we can simplify:

addr (next) = 0 ^ addr (next) 

Using the none (1) property, we can simplify:

addr (next) = addr (next) 

And you get it!

Moving from a node to the node you were in earlier

If you aren't understanding the title, it basically means that if you were at node X, and have now moved to node Y, you want to go back to the node visited earlier, or basically node X.

This isn't a cumbersome task. Remember that I had mentioned above, that you store the address you were at in a temporary variable. So the address of the node you want to visit, is lying in a variable:

addr (prev) = temp_addr 

Moving from a node to the previous node

This isn't the same as mentioned above. I mean to say that, you were at node Z, now you are at node Y, and want to go to node X.

This is nearly same as moving from a node to the next node. Just that this is it's vice-versa. When you write a program, you will use the same steps as I had mentioned in the moving from one node to the next node, just that you are finding the earlier element in the list than finding the next element.

I don't think I need to explain this.


Advantages of XOR Linked List

  • This uses less memory than a Doubly Linked List. Approximately 33% less.

  • It uses only one pointer. This simplifies the structure of the node.

  • As doynax said, a sub-section of a XOR can be reversed in constant time.


Disadvantages of XOR Linked List

  • This is a bit tricky to implement. It has higher chances of a failure, and debugging it is quite difficult.

  • All conversions (in case of an int) have to take place to / from uintptr_t

  • You cannot just acquire the address of a node, and start traversing (or whatever) from there. You have to always start from the head or tail.

  • You cannot go jumping, or skipping nodes. You have to go one by one.

  • Moving requires more operations.

  • It is difficult to debug a program which is using a XOR Linked List. It is much easier to debug a Doubly Linked List.


References

Answers 2

I would recommend seeing my second answer to this question, as it is much clearer. But I am not saying this answer is wrong. This is also correct.



A Memory-Efficient Linked List is also called a XOR Linked LIST.

How Does It Work

A XOR (^) Linked List is a Link List in which instead of storing the next and back pointers, we just use one pointer to do the job of both the next and back pointers. Let's first see the XOR logic gates properties:

|-------------|------------|------------| |    Name     |   Formula  |    Result  | |-------------|------------|------------| | Commutative |    A ^ B   |    B ^ A   | |-------------|------------|------------| | Associative | A ^ (B ^ C)| (A ^ B) ^ C| |-------------|------------|------------| | None (1)    |    A ^ 0   |     A      | |-------------|------------|------------| | None (2)    |    A ^ A   |     0      | |-------------|------------|------------| | None (3)    | (A ^ B) ^ A|     B      | |-------------|------------|------------| 

Lets now take an example. We have a doubly linked list with four nodes: A, B, C, D. Here's how it looks:

enter image description here

If you see, each node has two pointers, and 1 variable for storing data. So we're using three variables.

Now if you're have a Doubly Linked List with lot's of nodes, the memory it will be using will be too much. Too make it more efficient, we use a Memory-Efficient Doubly Linked List.

A Memory-Efficient Doubly Linked List is a Linked List in which we use just one pointer to move back and forth using XOR and it's properties.

Here's a pictorial representation:

enter image description here

How do you move back and forth?

You have a temporary variable (only one, not in the node). Let's say you're traversing the node from left to right. So you start at node A. In the pointer of node A, you store node B's address. You then move to node B. While moving to node B, in the temporary variable, you store node A's address.

The link (pointer) variable of node B has the address of A ^ C. You would take the previous node’s address (which is A) and XOR it with the current link field , giving you the address of C. Logically, this would look like:

A ^ (A ^ C) 

Let's now simplify the equation. We can remove the parentheses and rewrite it because of the Associative property like:

A ^ A ^ C 

We can further simplify this to

0 ^ C 

because of the second ("None (2)" as stated in the table) property.

Because of the first ("None (1)" as stated in the table) property, this is basically

C 

If you can't understand all this, just simply see the third property ("None (3)" as stated in the table).

Now, you got the address of node C. This will be the same process for going back.

Let's say that you were going from node C to node B. You would store the address of node C in the temporary variable, do the process given above again.

NOTE: Everything like A, B, C are addresses. Thanks to Bathsheba for telling me to make it clear.

Disadvantages of XOR Linked List

  • As Lundin mentioned, all the conversions have to be done from/to uintptr_t.

  • As Sami Kuhmonen mentioned, you have to start from a well-defined start point, not just a random node.

  • You cannot just jump a node. You have to go in order.

Also note that a XOR Linked List is not better than a doubly-linked list in most cases.

References

Answers 3

This is an old programming trick that allows you to save memory. I don't think it's used much anymore, since memory is no longer as tight a resource as it was in the old days.

The basic idea is this: In a conventional doubly-linked list, you have two pointers to adjacent list elements, a "next" pointer which points to the next element, and a "prev" pointer which points to the previous element. You can therefore traverse the list either forwards or backwards by using the appropriate pointers.

In the reduced-memory implementation, you replace "next" and "prev" with a single value, which is the bitwise exclusive-OR (bitwise-XOR) of "next" and "prev". You therefore reduce the storage for the adjacent element pointers by one half.

Using this technique, it is still possible to traverse the list in either direction, but you need to know the address of the previous (or next) element to do so. For instance, if you are traversing the list in the forward direction, and you have the address of "prev", then you can get "next" by taking the bitwise-XOR of "prev" with the current combined pointer value, which is "prev" XOR "next". The result is "prev" XOR "prev" XOR "next", which is just "next". The same can be done in the opposite direction.

The downside is that you can't do things like delete an element, given a pointer to that element, without knowing the address of either the "prev" or "next" element, since you have no context with which to decode the combined pointer value.

The other downside is that this sort of pointer trick circumvents the normal data type checking mechanism that a compiler may expect.

It's a clever trick, but in all honesty I see very little reason to use it these days.

Answers 4

OK, so you've seen the XOR linked list, which saves you one pointer per item... but that is an ugly, ugly data structure, and far from the best you can do.

If you're worried about memory, it's almost always better to use a doubly-linked list with more than 1 element per node, like a linked list of arrays.

For example, while an XOR linked list costs 1 pointer per item, plus the item itself, A doubly-linked list with 16 items per node costs 3 pointers for each 16 items, or 3/16 pointers per item. (the extra pointer is the cost of the integer that records how many items are in the node) That is less than 1.

In addition to the memory savings, you get advantages in locality because all 16 items in the node are next to each other in memory. Algorithms that iterate through the list will be faster.

Note that an XOR-linked list also requires you to allocate or free memory each time you add or delete a node, and that is an expensive operation. With the array-linked list, you can do better than this by allowing nodes to be less than completely full. If you allow 5 empty item slots, for example, then you only have allocate or free memory on every 3rd insert or delete at worst.

There are many possible strategies for determining how and when to allocate or free nodes.

Answers 5

You already got a pretty elaborate explanation of XOR linked list, I'll share a few more ideas on memory optimization.

  1. Pointers normally take 8 bytes on 64 bit machines. It's necessary to address any point in RAM beyond 4GB addressable with 32 bit pointers.

  2. Memory managers typically deal with fixed size blocks, not bytes. E.g. C malloc usually allocates within 16 byte granularity.

These two things imply that if your data is 1 byte, corresponding doubly linked list element will take 32 bytes (8+8+1, rounded up to the nearest 16 multiple). With XOR trick you can get it down to 16.

However, to optimize even further, you can consider using your own memory manager, that: (a) deals with blocks at lower granuarity, such as 1 byte or maybe even go into bits, (b) has stricter limit on overall size. For example, if you know your list will always fit inside a continuous block of 100 MB, you only need 27 bits to address any byte within that block. Not 32 or 64 bits.

If you aren't developing a generic list class, but you know specific usage patterns for your application, in many cases implementing such memory manager may be an easy job. For example, if you know you will never allocate more than 1000 elements and each element takes 5 bytes, you can implement memory manager as 5000-byte array with a variable that holds index of the first free byte, and as you allocate extra element, you just take that index and move it forward by the allocated size. In this case your pointers will not be real pointers (like int*) but will be just indexes inside that 5000-byte array.

Read More