Python __repr__ method: writing a JS equivalent? - javascript

I am working through a short beginner's course on Algorithms and Data Structures. The instructor's language is Python; I am converting the code examples to JavasScript. So far, so good.
I am dealing with Linked Lists. The instructor tests the code using Python's __repr__() method. After days of trial and error, I have a working JS solution, but it is not exactly the same as the Python code. I would like to know if there is a better way of implementing the JS code, which I provide, along with the Python code.
Python
# class LinkedList and its methods are presumed to exist
def __repr__(self):
nodes = []
current = self.head
while current:
if current is self.head:
nodes.append("[Head: %s]" % current.data)
elif current.next_node is None:
nodes.append("[Tail: %s]" % current.data)
else
nodes.append("[%s]" % current.data)
current = current.next_node
return '-> '.join(nodes)
# running script
>>> l = LinkedList()
>>> l.add(1)
>>> l.add(2)
>>> l.add(3)
>>> l
[Head: 3]-> [2]-> [Tail: 1] # output
>>>
JS
// class LinkedList and its methods are presumed to exist
repr () {
let nodes = "";
let current = this.head;
while (current) {
if (current === this.head) {
nodes = `Head: ${current.data}-> `;
} else if (current.nextNode === null) {
nodes += `Tail: ${current.data}`;
} else {
nodes += `${current.data}-> `;
}
current = current.nextNode;
}
return nodes;
// running the script
let l = LinkedList();
l.add(1);
l.add(2);
l.add(3);
let result = l.repr();
console.log(result); // Head: 3-> 2-> Tail: 1
Again, the two fragments will only run in a full implementation of the Linked List algorithm, but they do work.
Attempts I have made: I tried to use JS toString(), append() and appendChild(), but they were too difficult for me to understand how best to use them, particularly as the last two modify the DOM. I'm sure there is a better way of implementing a JS equivalent of the Python __repr__(); I would like to know how it might be done.

A closer implementation would use a toString method. This method is called implicitly when a conversion to string is needed. Python has actually two methods for this, which have a slightly different purpose: __repr__ and __str__. There is no such distinction in JavaScript.
Furthermore, we should realise that Python's print will implicitly call __repr__, which is not how console.log works. So with console.log you'd have to enforce that conversion to string.
Here is how the given Python code would be translated most literally (I add the classes needed to run the script):
class Node {
constructor(data, next=null) {
this.data = data;
this.next_node = next;
}
}
class LinkedList {
constructor() {
this.head = null;
}
add(data) {
this.head = new Node(data, this.head);
}
toString() {
let nodes = [];
let current = this.head;
while (current) {
if (current === this.head) {
nodes.push(`[Head: ${current.data}]`);
} else if (current.next_node === null) {
nodes.push(`[Tail: ${current.data}]`);
} else {
nodes.push(`[${current.data}]`);
}
current = current.next_node;
}
return nodes.join('-> ');
}
}
// running script
let l = new LinkedList();
l.add(1);
l.add(2);
l.add(3);
// Force conversion to string
console.log(`${l}`); // [Head: 3]-> [2]-> [Tail: 1]
Personally, I would make the following changes (not reflected in the Python version):
Produce output without the words "Head" and "Tail" and other "decoration". This is too verbose to my liking. Just output the separated values.
Make list instances iterable, implementing the Symbol.iterator method (In Python: __iter__). Then use this for implementing the toString method.
Allow the list constructor to take any number of values with which the list should be populated.
This leads to the following version:
class Node {
constructor(data, next=null) {
this.data = data;
this.next = next;
}
}
class LinkedList {
constructor(...values) { // Accept any number of values
this.head = null;
// Populate in reverse order
for (let data of values.reverse()) this.add(data);
}
add(data) {
this.head = new Node(data, this.head);
}
// Make lists iterable
*[Symbol.iterator]() {
let current = this.head;
while (current) {
yield current.data;
current = current.next;
}
}
toString() {
// Array.from triggers the above method
return Array.from(this).join("→");
}
}
// Provide the desired values immediately:
let l = new LinkedList(3, 2, 1);
console.log(`${l}`); // 3→2→1

Related

Binary search tree - iterative insert method leaves tree empty

I tried to create the insert method for a binary search tree, but 'this.root' remains null.
My logic is:
As long as current (which at the beginning is this.root) is not null, continue to update the current variable, by comparing it with the value we want to insert (if it's greater or less).
When current is null, we assign it the new Node:
class Node {
constructor(value){
this.value = value
this.left = null
this.right = null
}
}
class BST {
constructor(){
this.root = null
this.count = 0
}
insert(value){
this.count++
let current = this.root;
while(current){
if(value<current){
current=current.left
}
if(value>current){
current=current.right
}
}
current = new Node(value);
}
}
let Binst = new BST(10);
Binst.insert(22)
Binst.insert(12)
Binst.insert(4)
console.log(Binst)
There are these issues:
Comparing value with current is not right: that is comparing a number with an object. You need to compare with current.value
In the main program you call the BST constructor with an argument, but the constructor does not expect an argument. Although you could adapt the constructor to take that argument, it is better to not pass an argument to the constructor and have an extra call of insert in your main program.
current = new Node(value) will not change the tree. It only assigns a new node to a local variable. In order to extend the tree, you need to assign the new node to a left or right property of an existing node (or to the root property of the BST instance). Assigning to a variable never mutates your object structure.
this.root is never assigned anything else after its initialisation with null. Again, assigning to current, is never going to change this.root.
Because of the previous points, you need to stop your loop one iteration earlier -- when current points to the node that is about to get a new child. And you also need to deal separately with the case where the new node must become the root of the tree.
The following are just suggestions, not problems:
It is better practice to separate your statements with semicolons. There is the automatic semicolon insertion algorithm, but you wouldn't be the first to fall in one if its traps. Better take control over it yourself.
It is common practice to name variables with an initial capital when they are classes (constructors), but for instances a lower case initial letter is commonly used. So binst or bIntst instead of Binst. I would even suggest a more readable name, like tree.
Here is the corrected code:
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
this.count = 0;
}
insert(value) {
this.count++;
let current = this.root;
if (!current) {
this.root = new Node(value);
return;
}
while (true) {
if (value < current.value){
if (current.left) {
current = current.left;
} else {
current.left = new Node(value);
return;
}
}
if (value > current.value) {
if (current.right) {
current = current.right;
} else {
current.right = new Node(value);
return;
}
}
}
}
}
let tree = new BST();
tree.insert(10);
tree.insert(22);
tree.insert(12);
tree.insert(4);
console.log(tree);

Javascript observer or proxy without all changes going through proxy

I'm writing a subclass of arrays in Javascript to have better support for matrix operations (I know others exist, this is partially for me to re-teach myself linear algebra), and what I want is to have some properties that are reset whenever any values in the matrix are adjusted. Some calculations like the determinant are computationally intensive, and I'd like to be able to store them to avoid re-calculation, but then they need to be reset to null whenever any matrix elements are changed.
Essentially, it seems like what i want is the deprecated Array.observe(). And the replacement, proxies, seem like a lot of overhead for this one thing. As alluded to in some of the comments on Detecting Changes in a Javascript Array using the proxy object that were not directly addressed, I don't want to have to access my matrices only ever through proxies. I use a lot of handy [i][j] indexing and [mat[i], mat[j]] = [mat[j], mat[i]] in the code I've written so far.
class Matrix extends Array {
constructor() {
var args = [];
for (var i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
args.push(new Matrix(...arguments[i]));
} else {
args.push(arguments[i]);
}
}
super(...args);
this._determinant = null;
}
determ(forceRecalculate = false) {
if (this._determinant === null || forceRecalculate) {
this.upperEchelon();
}
return this._determinant;
}
upperEchelon(reduced = false) {
//There's a lot of code here but in the process of doing this other thing
//you get 99% of the way to calculating the determinant so it does this
this._determinant = factor;
}
}
Basically, I want anything like mat[0][0] = 10 or mat.push([2,4,5]) that updates the values in the matrix to set mat._determinant = null. Or any equivalent method of flagging that it needs to be re-calculated next time it's asked for. I'm not opposed to using proxies necessarily if someone can help me figure out the implementation, I would just rather have this set-to-null-on-update property be inherent to my class functionality.
What I really want is a way to overload base methods like [] a la C# so the functions that do the updating would trigger this without changing syntax, but I've resigned myself to not having that in JS.
While a Proxy would work, it would also be pretty slow. A different approach would be for every method that needs to use the value of _determinant go through a different function first to check to see if the _determinant needs to be updated (and if so, updates it). This way, the expensive recalculation is not done every time the array changes, but only just in time for the result to be used. For example:
class Matrix extends Array {
constructor() {
var args = [];
for (var i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
args.push(new Matrix(...arguments[i]));
} else {
args.push(arguments[i]);
}
}
super(...args);
this._determinant = null;
}
// next method is effectively a recursive deep join
// could also use toString if it doesn't interfere with anything else
getString() {
const itemsStr = this.map((item) => (
item instanceof Matrix
? item.getString()
: item
))
.join(',');
const result = '[' + itemsStr + ']';
return result;
}
getDeterm() {
const newString = this.getString();
if (newString !== this._lastString) {
this._lastString = newString;
this.upperEchelon();
}
return this._determinant;
}
upperEchelon() {
console.log('running upperEchelon');
this._determinant = Math.random();
}
}
const m = new Matrix([2, 3, 4], 5);
console.log(m.getDeterm());
// Not calculated again:
console.log(m.getDeterm());
// Mutation, next call of getDeterm will run upperEchelon:
m[0][0] = 1;
console.log(m.getDeterm());

Performance optimizations for add, removeFirst, removeFirstN array operations

For my use case I've found that the shift/slice methods are stressing my CPU way too much, as the array grows in size. In theory the array could be as big as 86400 items, although usually it would much lower - around 10000 array elements.
I've tried to illustrate it with a simple example. Imagine this at a very large scale. It'll run decently up until a point, but generally it seems highly ineffective to remove the first (or first n) item(s) like this.
Hopefully somebody with more knowledge in "why that is", can fill out one or more of the 3 functions in the snippet below:
add()
removeFirst()
removeFirstN(n)
Immutability won't work here - or rather, since we're after the optimal performance, copying a growing and quite large datastructure (array in this case) definitely won't work.
Any good suggestions? :-)
let objArray = []
let maxCount = 10;
let i = 0;
function add(){
objArray.push({x: + new Date(), y: Math.floor(Math.random() * 10000) + 1});
console.log("add")
}
function removeFirst(){
objArray.shift();
console.log("removeFirst")
}
function removeFirstN(n){
objArray.splice(0,n)
console.log(`removeFirstN(${n})`)
}
// Every second and obj is added to the array
setInterval(function(){
if(objArray.length === maxCount){
removeFirst();
} else if(objArray.length > maxCount) { // this is possible since we're allowed to change maxCount
const diff = objArray.length+1 - maxCount;
removeFirstN(diff);
}
// Always add
add();
i++;
if(i === 15) {
maxCount--;
i = 0;
}
console.log(`length: ${[...objArray].length}`)
console.log([...objArray])
}, 1000)
Judging by the listed operations, you’re looking for a queue with constant-time enqueue and dequeue. When you use an array as a queue by moving all the elements for operations on one side, that operation instead takes time proportional to the number of elements in the array. An implementation based on a circular buffer or linked list (both satisfy the constant-time requirement) will be faster as the number of elements becomes larger.
Linked lists are simple enough to demonstrate in a post:
class LinkedQueue {
constructor() {
this.head = null;
this.tail = null;
}
enqueue(value) {
const node = {value, next: null};
if (this.tail === null) {
// Empty queue; make this the only node
this.tail = this.head = node;
} else {
// Make this the successor of the current last node,
// then make it the new last node
this.tail = this.tail.next = node;
}
}
dequeue() {
const result = this.head.value;
if (this.head === this.tail) {
// Last element remaining
this.head = this.tail = null;
} else {
// Remove the first element
this.head = this.head.next;
}
return result;
}
}
but for the best performance in practice, you’ll want to use a queue based on a circular buffer. double-ended-queue is one such npm package.

Breadth first search binary search tree javascript implementation

I have the following code that implements a BST tree in JavaScript.
function Node(value) {
this.left = null;
this.right = null;
this.value = value;
}
function BinarySearchTree() {
this.root = null;
return;
}
BinarySearchTree.prototype.push = function(value) {
if (!this.root) {
this.root = new Node(value);
return;
}
var currentRoot = this.root;
var newNode = new Node(value);
while (currentRoot) {
if (value < currentRoot.value) {
if (!currentRoot.left) {
currentRoot.left = newNode;
break;
} else {
currentRoot = currentRoot.left;
}
} else {
if (!currentRoot.right) {
currentRoot.right = newNode;
break;
} else {
currentRoot = currentRoot.right;
}
}
}
}
var a = new BinarySearchTree();
a.push(27);
a.push(14);
a.push(35);
a.push(10);
a.push(19);
a.push(31);
a.push(42);
I am trying to implement a function which can do a breadth first traversal of the tree. This is what I have tried so far.
console.log(a.root.value);
traverse(a.root);
//function to traverse
function traverse(node) {
currentNode = node;
while (currentNode.left) {
displayNodes(currentNode);
parent = currentNode;
currentNode = currentNode.left;
displayNodes(currentNode);
if(parent.right!=null){
displayNodes(parent.right);
}
}
}
//function that displays the left and right node of a node
function displayNodes(node) {
if (node.left != null) {
console.log(node.left.value);
}
if (node.right != null) {
console.log(node.right.value);
}
}
I am unable to implement a function that could scale with a large number of data. I am not sure if a recursive method to traverse would be better or using a while loop. How can I implement the function? I know that the function gives unexpected behavior? What correction should I make?
You currently traverse the path from the root node to the left-most leaf.
A simple non-recursive breadth-first traversal function invoking a callback on each traversed node could look as follows:
// Breadth-first traversal:
function traverse(node, cb) {
var current = [node];
while (current.length > 0) {
var next = [];
for (var node of current) {
cb(node);
if (node.left) next.push(node.left);
if (node.right) next.push(node.right);
}
current = next;
}
}
// Example:
traverse(root, function(node) {
console.log(node.value);
});
It works by keeping an array of already discovered or traversed nodes current which initially contains just your root node. Now, you iteratively replace each node in that list with its children. In above function, the children are stored in a next array. At the end of each iteration, all nodes of the current level in current are replaced with all their children of the next deeper level in next. See also the first suggestion given by #DavidKnipe's answer.
A non-recursive approach has the advantage of not being subject to the call stack size limit. This theoretically allows you to handle larger data structures when the call stack size is limited.
If you're looking for a way to BFS using O(1) memory, I don't think there's a nice way to do it. (DFS is another matter though. Are you sure it has to be BFS?)
There are two ways I can see to do this. You could start with the array [this.root], and write a function that iterates over an array of nodes and then returns an array of children of those nodes. Then call that function on the array of children, and keep going down the tree until you get an empty array.
If memory is an issue, there's another way to do it. Instead of remembering the array of nodes at a given level, you could just remember the depth, then redo the iteration each time. So you'd have a function which takes a natural number n and iterates over the tree, but without going deeper than n, and does whatever it is you're trying to do at the nth level only; then call this function for all values of n until there are no more nodes left.
That last one might sound very wasteful, but it might not be too bad if the last few levels of the tree contain most of the nodes. It depends on your dataset and computational capabilities.

When would you use .concat() in javascript

Why/when would one use .concat() in place of assignment operators?
i.e. if I am trying to combine the following:
var p1 = "My name is ";
var p2 = "Joe";
var sen = p1+p2;
//Or you could use concat to do the same
var sen2 = p1.concat(p2);
//My question is, why would you ever use the latter?
Sometimes its best to consult the documentation: Array.concat, and String.concat.
Simply, Array.concat() is used to create a new array equivalent to the flat merging of all passed in objects (arrays or otherwise). String.concat() is used to create a new string, which is equivalent to the merging of all passed in strings.
However, as MDN hints at, String.concat() should not be used as the assignment +, += operators are much faster. Why then would you use String.concat()? You wouldn't. Why have it then? It's part of the spec: See Page 111 - 112 (Section: 15.5.4.6).
So on to the question of Why is String.Concat so slow?. I did some digging through Chrome's V8 Engine. To start with, behind the scenes, this is what a call to String.prototype.concat is doing:
// ECMA-262, section 15.5.4.6
// https://github.com/v8/v8/blob/master/src/string.js#L64
function StringConcat(other /* and more */) { // length == 1
CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
var len = %_ArgumentsLength();
var this_as_string = TO_STRING_INLINE(this);
if (len === 1) {
return this_as_string + other;
}
var parts = new InternalArray(len + 1);
parts[0] = this_as_string;
for (var i = 0; i < len; i++) {
var part = %_Arguments(i);
parts[i + 1] = TO_STRING_INLINE(part);
}
return %StringBuilderConcat(parts, len + 1, "");
}
As you can see all of the real work happens in StringBuilderConcat, which then calls a StringBuilderConcatHelper which then finally calls String::WriteToFlat to build a string. These are each extremely long functions and I've cut most of it out for brevity. But if you'd like to look for your self have a look in github:
StringBuilderConcat
// https://github.com/v8/v8/blob/master/src/runtime.cc#L7163
RUNTIME_FUNCTION(Runtime_StringBuilderConcat) {
// ...
StringBuilderConcatHelper(*special,
answer->GetChars(),
FixedArray::cast(array->elements()),
array_length);
// ...
}
StringBuilderConcatHelper
template <typename sinkchar>
static inline void StringBuilderConcatHelper(String* special,
sinkchar* sink,
FixedArray* fixed_array,
int array_length) {
// ...
String::WriteToFlat(string, sink + position, 0, element_length);
// ...
}
String::WriteToFlat
// https://github.com/v8/v8/blob/master/src/objects.cc#L8373
template <typename sinkchar>
void String::WriteToFlat(String* src,
sinkchar* sink,
int f,
int t) {
String* source = src;
int from = f;
int to = t;
while (true) {
// ...
// Do a whole bunch of work to flatten the string
// ...
}
}
}
Now what's different about the assignment pathway? Lets start with the JavaScript addition function:
// ECMA-262, section 11.6.1, page 50.
// https://github.com/v8/v8/blob/master/src/runtime.js#L146
function ADD(x) {
// Fast case: Check for number operands and do the addition.
if (IS_NUMBER(this) && IS_NUMBER(x)) return %NumberAdd(this, x);
if (IS_STRING(this) && IS_STRING(x)) return %_StringAdd(this, x);
// Default implementation.
var a = %ToPrimitive(this, NO_HINT);
var b = %ToPrimitive(x, NO_HINT);
if (IS_STRING(a)) {
return %_StringAdd(a, %ToString(b));
} else if (IS_STRING(b)) {
return %_StringAdd(%NonStringToString(a), b);
} else {
return %NumberAdd(%ToNumber(a), %ToNumber(b));
}
}
First thing to note, there's no loops and its quite a bit shorter compared to StringConcat up above. But most of the work we're interested in happens in the %_StringAdd function:
// https://github.com/v8/v8/blob/master/src/runtime.cc#L7056
RUNTIME_FUNCTION(Runtime_StringAdd) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
CONVERT_ARG_HANDLE_CHECKED(String, str1, 0);
CONVERT_ARG_HANDLE_CHECKED(String, str2, 1);
isolate->counters()->string_add_runtime()->Increment();
Handle<String> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, result, isolate->factory()->NewConsString(str1, str2));
return *result;
}
This is pretty simple actually, some counters and a call to something called NewConsString with the left and right operands. NewConsString is also pretty simple:
// https://github.com/v8/v8/blob/master/src/ast-value-factory.cc#L260
const AstConsString* AstValueFactory::NewConsString(
const AstString* left, const AstString* right) {
// This Vector will be valid as long as the Collector is alive (meaning that
// the AstRawString will not be moved).
AstConsString* new_string = new (zone_) AstConsString(left, right);
strings_.Add(new_string);
if (isolate_) {
new_string->Internalize(isolate_);
}
return new_string;
}
So this just returns a new AstConsString, what's that:
// https://github.com/v8/v8/blob/master/src/ast-value-factory.h#L117
class AstConsString : public AstString {
public:
AstConsString(const AstString* left, const AstString* right)
: left_(left),
right_(right) {}
virtual int length() const OVERRIDE {
return left_->length() + right_->length();
}
virtual void Internalize(Isolate* isolate) OVERRIDE;
private:
friend class AstValueFactory;
const AstString* left_;
const AstString* right_;
};
Well this doesn't look like a string at all. Its actually an 'Abstract Syntax Tree', this structure forms a 'Rope' which is efficient for modifying strings. It turns out most of the other browsers now use this type or rope structure when doing string addition.
The take away from this, is that the addition pathway uses a more efficient data structure, where as StringConcat does significantly more work with a different data structure.
According to Javascript: The Good Parts by Douglas Crockford:
The concat method makes a new string by concatenating other strings
together. It is rarely used because the + operator is more convenient
Concat is not only less convenient, it is also slower: Benchmark
On the documentation page from MDN:
It is strongly recommended that assignment operators (+, +=) are used
instead of the concat method.
Javascript has some less than ideal parts. Every language has at least some bad parts. Don't think you have to use every part of any language.

Categories