Home > Mobile >  Python __repr__ method: writing a JS equivalent?
Python __repr__ method: writing a JS equivalent?

Time:03-21

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.

CodePudding user response:

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