0

I am writing this code below but when I am trying to call the function size() it is throwing error

class Node:
    def __init__(self,data):
        self.data=data
        self.left=None
        self.right=None

    def insert(self,data):
            if self.data:
                if data<self.data:
                    if self.left is None:
                        self.left=Node(data)
                    else:
                        self.left.insert(data)
                else:
                    if self.right is None:
                        self.right=Node(data)
                    else:
                        self.right.insert(data)
            else:
                self.data=data

    def size(node):
        if node is None:
            return 0 
        else:
            return (size(node.left)+ 1 + size(node.right)) 

root=Node(4)

root.insert(5)
root.insert(3)
root.insert(8)

print(size(root))

The error below is getting thrown:

NameError                                 Traceback (most recent call last)
<ipython-input-7-8c72ba7719dc> in <module>
     41 root.insert(8)
     42 
---> 43 print(size(root))
     44 
     45 #root.print()

NameError: name 'size' is not defined
martineau
  • 119,623
  • 25
  • 170
  • 301
Micky
  • 55
  • 6
  • 1
    Exactly, `size` is not defined in this context. It's defined in the class `Node`, though – ForceBru Dec 16 '19 at 16:50
  • Does this answer your question? [Why do you need explicitly have the "self" argument in a Python method?](https://stackoverflow.com/questions/68282/why-do-you-need-explicitly-have-the-self-argument-in-a-python-method) – Roope Dec 16 '19 at 16:50
  • 1
    Quick warning: If your trees may grow arbitrarily deep (approaching or exceeding a thousand nodes deep), you're going to run into [`RecursionError`s](https://docs.python.org/3/library/exceptions.html#RecursionError). If that'll never happen, ignore me, but if it might, you'll need to rewrite this without using recursion. – ShadowRanger Dec 16 '19 at 17:06
  • @Roope: Don't think that really covers the OP's issue. As written, the argument to a method that's normally called `self` is instead named `node`, but that's harmless. The problem is trying to do `size(SOMENODE)`, when they've defined the method in such a way that only `SOMENODE.size()` is valid; either `size` shouldn't be indented inside the class body, or it needs to be invoked as a method, not a top level function. – ShadowRanger Dec 16 '19 at 17:07

3 Answers3

2

Either define size after the class statement:

class Node:
    def __init__(self,data):
        self.data=data
        self.left=None
        self.right=None

    def insert(self,data):
        ...


def size(node):
    if node is None:
        return 0
    else:
        return (size(node.left)+ 1 + size(node.right)) 

or make it a proper method:

class Node:
    def __init__(self,data):
        self.data=data
        self.left=None
        self.right=None

    def insert(self,data):
        ...

    def size(self):
        rv = 1
        if self.left is not None:
            rv += self.left.size()
        if self.right is not None:
            rv += self.right.size()
        return rv
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 2
    Note: Making it a top-level function is... unusual, to say the least. Especially in a case like `size`, where it seems like it should be something handled by `len` (by defining the special method `__len__`). Unless you've got a whole host of classes where `size` both makes sense, and is logically distinct from `len` (in which case, the OP should be looking at [`functools.singledispatch`](https://docs.python.org/3/library/functools.html#functools.singledispatch) to make this manageable), `size` should be method (possibly `__len__`), not a top level function. – ShadowRanger Dec 16 '19 at 17:02
  • @ShadowRanger Whether it should use `len` is an interesting case, eh. Do you know if there are any resources which standardize/define what `len` should do? – AMC Dec 16 '19 at 17:22
  • @AlexanderCécile: [The docs themselves](https://docs.python.org/3/library/functions.html#len) just say: "Return the length (the number of items) of an object." For this case, it's a little strange given there is no top-level collection involved, and it might be something to avoid given the cost is `O(n)`, not `O(1)` like a typically `__len__` implementation, but that doesn't mean you want to use a different, highly generically named top-level function, just that you should use an appropriately named method. – ShadowRanger Dec 16 '19 at 17:27
  • 1
    Another argument: The ABC that requires `__len__` is actually named [`Sized`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sized), so there would clearly be some conflict in meaning if you kept the name `size` but had it use a different behavior, since "length" and "size" are largely synonymous (in Python, and many other languages). – ShadowRanger Dec 16 '19 at 17:29
  • @ShadowRanger Yeah that conflict would be bad. I guess `__len__` is the way to go, then?! – AMC Dec 16 '19 at 17:35
  • 1
    @AlexanderCécile: Eh. Personally, I'd use a top-level class which stored the root `Node` and kept a pre-computed length stored internally (for efficiency), as well as providing the rest of the `Collection` (or better, `Sequence` or `Set`) ABC. Having `Node` report a "size" when it's not really related to the size of the `Node` itself is always going to be strange. If that's not possible, a more precise method name like `num_child_nodes` (that would report a value one less than `size` does, since it wouldn't count itself) would be my preference. – ShadowRanger Dec 16 '19 at 17:43
  • @ShadowRanger Top-level class is a good idea, actually. You can just call it `Tree` or something. – AMC Dec 16 '19 at 17:44
  • @chepner: i had doubt on how recursion works in your code ```def size(self): rv = 1 if self.left is not None: rv += self.left.size() if self.right is not None: rv += self.right.size() return rv ``` I am not good at recursion... – Micky Dec 16 '19 at 22:38
  • @Micky Which part don't you understand? Have you tried breaking it down on paper? – AMC Dec 16 '19 at 23:25
  • @AlexanderCécile i was getting confused in recursion with the iterative method. Like whenever the size method is getting called recursively then rv=1 is assigned,so how does it sum up the rv...is it that till self.left is not none,it assigns 1 to each node...during backtracking it sums up all the values....as recursion normally works...I face issue while solving recursion problems – Micky Dec 16 '19 at 23:48
  • Recursion is easiest if you don't think too hard about it. If `self.left` is not `None`, it's a tree. How do you find the size of a tree? Use its `size` method. The call stack (which you don't see) takes care keeping the various calls separate from each other. – chepner Dec 17 '19 at 00:25
  • @Micky Yeah, you got it!? – AMC Dec 17 '19 at 22:41
1

In your Code, size is a method bound to a Node object, so you need to call root.size() (because root is a Node instance)

Brendon
  • 33
  • 5
  • `size` would also have to be defined appropriately. Right now, this looks more like an indentation issue; `size` should have been defined *after*, not *in*, the `class` statement. – chepner Dec 16 '19 at 16:54
1

After some discussion with @ShadowRanger, here is what I came up with.

from __future__ import annotations

from dataclasses import dataclass
from typing import Any


@dataclass
class Node:
    data: Any
    left: Node = None
    right: Node = None

    def insert(self, data: Any) -> None:
        if self.data:
            if data < self.data:
                if self.left is None:
                    self.left = Node(data)
                else:
                    self.left.insert(data)
            else:
                if self.right is None:
                    self.right = Node(data)
                else:
                    self.right.insert(data)
        else:
            self.data = data

    def num_child_nodes(self) -> int:
        num_nodes = 0
        if self.left:
            num_nodes += 1 + self.left.num_child_nodes()
        if self.right:
            num_nodes += 1 + self.right.num_child_nodes()
        return num_nodes
AMC
  • 2,642
  • 7
  • 13
  • 35