lordvlad
8/14/2014 - 12:12 AM

index.html

function LList(data) {
    this.head = null;
    this.tail = null;    
  if (data)
    (Array.isArray(data) ? data : [].slice.apply(arguments))
    .reverse().forEach(this.unshift, this);
}

LList.makeQuery = function(obj){
  if (typeof obj === "undefined")
    return function(el){
      return typeof el === "undefined";
    };
    
  if (obj === null)
    return function(el){
      return el === null;
    };
    
  if (obj.constructor === Object) {
    var fns = {};
    Object.keys(obj).forEach(function(k){fns[k] = LList.makeQuery(obj[k]);});
    return function(el){
      return Object.keys(obj).every(function(k){return fns[k].call(el[k], el[k]);});
    };
  }
    
  if (obj.constructor === Function)
    return obj;
  
  if (obj.constructor === RegExp)
    return function(el){
      return obj.test(el);
    };
    
  if (obj.constructor === String && /^[<>=!]/.test(obj))
    obj = "return this " + obj;

  if (obj.constructor === String && /^return/.test(obj))
    return new Function(obj);
  
  return function(el){
    return el === obj;
  };
};

LList.FIND_NODE = 1;
LList.FIND_HEAD = 2;
LList.FIND_PREV = 4;  
  
LList.prototype.find = function(query, flags){
  if (!flags) flags = LList.FIND_HEAD;
  query = LList.makeQuery(query);
  var x = this, y = null;
  while (y = x, (x = x.tail) !== null) {
    if (query.call(x.head, x.head)){
      if (flags & LList.FIND_PREV) x = y;
      if (flags & LList.FIND_HEAD) x = x.head;
      break;
    }
  }
  return x;
};

LList.prototype.filter = function(query){
  query = LList.makeQuery(query);
  var x = this, a = new LList();
  while ((x = x.tail) !== null){
    if (query.call(x.head, x.head))
      a.unshift(x.head);
  }
  return a;
};

LList.prototype.map = function(fn){
  fn = LList.makeQuery(fn);
  return this.toArray().map(function(x){return fn.call(x, x);});
};

LList.prototype.unshift = function(x){
  this.tail = {head: x, tail: this.tail};
  return this;
};

LList.prototype.insert = function(a){
  return {
    after: function(b){
      b = this.find(b, LList.FIND_NODE);
      b.tail = {head: a, tail: b.tail};
      return this;
    }.bind(this),
    before: function(b){
      b = this.find(b, LList.FIND_NODE | LList.FIND_PREV);
      b.tail = {head: a, tail: b.tail};
      return this;
    }.bind(this)
  };
};

LList.prototype.remove = function(a){
  a = this.find(a, LList.FIND_NODE | LList.FIND_PREV);
  a.tail = a.tail.tail;
  return this;
};

LList.prototype.toArray = function(){
  var a = [], x = this;
  while ((x = x.tail) !== null){
    a.push(x.head);
  }
  return a;
};

LList.prototype.forEach = function(fn, scope){
  var x = this;
  while ((x = x.tail) !== null)
    fn.call(scope || x.head, x.head);
  return this;
};

LList.prototype.some = function(fn, scope){
  var x = this;
  while ((x = x.tail) !== null)
    if (fn.call(scope || x.head, x.head)) return true;
  return false;
};

LList.prototype.contains = function(a){
  return this.some(function(b){return a === b;});
};

LList.intersect = function(a, b){
  var c = new LList();
  a.forEach(function(x){if (b.contains(x)) c.unshift(x);});
  return c;
};

LList.union = function(a, b){
  var c = new LList();
  a.forEach(function(x){c.unshift(x);});
  b.forEach(function(x){if (!b.contains(x)) c.unshift(x);});
  return c;
};

LList.prototype.toString = function(){
  return this.toArray().join(", ");
};
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>

<script id="jsbin-javascript">
function LList(data) {
    this.head = null;
    this.tail = null;    
  if (data)
    (Array.isArray(data) ? data : [].slice.apply(arguments))
    .reverse().forEach(this.unshift, this);
}

LList.makeQuery = function(obj){
  if (typeof obj === "undefined")
    return function(el){
      return typeof el === "undefined";
    };
    
  if (obj === null)
    return function(el){
      return el === null;
    };
    
  if (obj.constructor === Object) {
    var fns = {};
    Object.keys(obj).forEach(function(k){fns[k] = LList.makeQuery(obj[k]);});
    return function(el){
      return Object.keys(obj).every(function(k){return fns[k].call(el[k], el[k]);});
    };
  }
    
  if (obj.constructor === Function)
    return obj;
  
  if (obj.constructor === RegExp)
    return function(el){
      return obj.test(el);
    };
    
  if (obj.constructor === String && /^[<>=!]/.test(obj))
    obj = "return this " + obj;

  if (obj.constructor === String && /^return/.test(obj))
    return new Function(obj);
  
  return function(el){
    return el === obj;
  };
};

LList.FIND_NODE = 1;
LList.FIND_HEAD = 2;
LList.FIND_PREV = 4;  
  
LList.prototype.find = function(query, flags){
  if (!flags) flags = LList.FIND_HEAD;
  query = LList.makeQuery(query);
  var x = this, y = null;
  while (y = x, (x = x.tail) !== null) {
    if (query.call(x.head, x.head)){
      if (flags & LList.FIND_PREV) x = y;
      if (flags & LList.FIND_HEAD) x = x.head;
      break;
    }
  }
  return x;
};

LList.prototype.filter = function(query){
  query = LList.makeQuery(query);
  var x = this, a = new LList();
  while ((x = x.tail) !== null){
    if (query.call(x.head, x.head))
      a.unshift(x.head);
  }
  return a;
};

LList.prototype.map = function(fn){
  fn = LList.makeQuery(fn);
  return this.toArray().map(function(x){return fn.call(x, x);});
};

LList.prototype.unshift = function(x){
  this.tail = {head: x, tail: this.tail};
  return this;
};

LList.prototype.insert = function(a){
  return {
    after: function(b){
      b = this.find(b, LList.FIND_NODE);
      b.tail = {head: a, tail: b.tail};
      return this;
    }.bind(this),
    before: function(b){
      b = this.find(b, LList.FIND_NODE | LList.FIND_PREV);
      b.tail = {head: a, tail: b.tail};
      return this;
    }.bind(this)
  };
};

LList.prototype.remove = function(a){
  a = this.find(a, LList.FIND_NODE | LList.FIND_PREV);
  a.tail = a.tail.tail;
  return this;
};

LList.prototype.toArray = function(){
  var a = [], x = this;
  while ((x = x.tail) !== null){
    a.push(x.head);
  }
  return a;
};

LList.prototype.forEach = function(fn, scope){
  var x = this;
  while ((x = x.tail) !== null)
    fn.call(scope || x.head, x.head);
  return this;
};

LList.prototype.some = function(fn, scope){
  var x = this;
  while ((x = x.tail) !== null)
    if (fn.call(scope || x.head, x.head)) return true;
  return false;
};

LList.prototype.contains = function(a){
  return this.some(function(b){return a === b;});
};

LList.intersect = function(a, b){
  var c = new LList();
  a.forEach(function(x){if (b.contains(x)) c.unshift(x);});
  return c;
};

LList.union = function(a, b){
  var c = new LList();
  a.forEach(function(x){c.unshift(x);});
  b.forEach(function(x){if (!b.contains(x)) c.unshift(x);});
  return c;
};

LList.prototype.toString = function(){
  return this.toArray().join(", ");
};
</script>


</body>
</html>