Removing element from container class - python-3.x

I'm having trouble to define the __delitem__ or similar method for the class Container in the example below. How can I rectify this? Thx.
import numpy as np
import pandas as pd
class XLData(object):
def __init__(self, name):
self.name = name
self.data = pd.DataFrame({self.name: list("ASDF" * 2),
'x': np.random.randint(1, 100, 8) })
def __repr__(self):
return repr(self.data.head(2))
class Container(object):
def __init__(self):
self.counter = 0
self.items = []
def append(self, item):
self.counter += 1
self.items = self.items + [item]
def __delitem__(self, name):
for c in self.items:
print("element name:{}, to delete:{}".format(c.name, name))
if c.name == name:
pass #!
#del c
def __iter__(self):
for c in self.items:
yield c
a = XLData('a')
b = XLData('b')
c = XLData('c')
dl = Container()
dl.append(a)
dl.append(b)
dl.append(c)
del dl['b']
for c in dl:
print(c)
# 'b' is still in ..

It is a good idea not to modify the array we are looping over in the loop itself. So just pick the index of the item and delete it outside the loop.
class Container(object):
def __init__(self):
self.counter = 0
self.items = {} # create a dict!
def append(self, item):
self.counter += 1
self.items[item.name] = item # add items to it, keyed under their names
def __delitem__(self, name):
del self.items[name] # this becomes *really* simple, and efficient
def __iter__(self):
for c in self.items.values: # loop over the dict's values to the items
yield c #

As you seem to be aware, given your code's comments, you can't usefully do del c in your loop, because that only removes the c variable from the function's local namespace temporarily, it doesn't change the list structure at all.
There are a few different ways you could make it work.
One idea would be to use enumerate while looping over the values in the list, so that you'll have the index at hand when you need to delete an item from the list:
for i, item in enumerate(self.items):
if item.name == name:
del self.items[i]
return
Note that I return from the function immediately after deleting the item. If multiple items with the same name could exist in the list at once, this may not be what you want, but this code can't properly handle that case because once you delete an item from the list, the iteration won't work properly (it will let you keep iterating, but it will have skipped one value).
A better option might be to rebuild the list so that it only includes the values you want to keep, using a list comprehension.
self.items = [item for item in self.items if item.name != name]
That's nice and concise, and it will work no matter how many items have the name you want to remove!
One flaw of both of the approaches above share is that they'll be fairly slow for large lists. They need to iterate over all the items, they can't tell ahead of time where the item to remove is stored. An alternative might be to use a dictionary, rather than a list, to store the items. If you use the item names as keys, you'll be able to look them up very efficiently.
Here's an implementation that does that, though it only allows one item to have any given name (adding another one will replace the first):
class Container(object):
def __init__(self):
self.counter = 0
self.items = {} # create a dict!
def append(self, item):
self.counter += 1
self.items[item.name] = item # add items to it, keyed under their names
def __delitem__(self, name):
del self.items[name] # this becomes *really* simple, and efficient
def __iter__(self):
for c in self.items.values(): # loop over the dict's values to the items
yield c

It is a good idea not to modify the array we are looping over in the loop itself. So just pick the index of the item and delete it outside the loop.
def __delitem__(self, name):
idx = -1
found = False
for c in self.items:
idx += 1
print("element name:{}, to delete:{}".format(c.name, name))
if c.name == name:
found = True
break
if found:
del self.items[idx]

The way you have it implemented, it's going to be slow for operations like del. And if you wanted to add other methods that return your objects by name like __getitem__(), looking them up by iterating through a list will be slow. You probably want a dictionary to hold your XLData objects in inside Container. And you won't be needing to keep a count of them since the data objects of Python all have a length property.
class Container(object): # Python 3 doesn't require 'object' in class decls.
def __init__(self):
self._items = {}
def add(self, item):
# self._items.append(item) # Why create a new list each time.
# Just append.
self._items[item.name] = item
def __len__(self):
return len(self._items)
def __getitem__(self, name):
return self._items[name]
def __delitem__(self, name):
del self._items[name] # Simple.
def __iter__(self):
for c in self._items.values():
yield c
With a dict you get the benefits of both a list and a dictionary: fast access by name, and iteration over items, etc. The dict keeps track of the order in which the keys and items are added. It is possible to have more than one data type holding information on your contained objects if you really needed a separate list to sort and iterate over. You just have to keep the dict and list in sync.
Come to think of it, you could even sort the dictionary without a list if you wanted your class to support a sort() operation, just requires a little creativity.
def sort(self, key=None):
self._items = {k: v for k, v in sorted(self._items.items(), key=key)}
I think I'm taking it a bit too far now =)

alternative method, using list filter option with object attr conditions in def delitem method
import numpy as np
import pandas as pd
class XLData(object):
def __init__(self, name):
self.name = name
self.data = pd.DataFrame({self.name: list("ASDF" * 2),
'x': np.random.randint(1, 100, 8)})
def __repr__(self):
return repr(self.data.head(2))
class Container(object):
def __init__(self):
self.counter = 0
self.items = []
def append(self, item):
self.counter += 1
self.items = self.items + [item]
def __delitem__(self, name):
self.items = [x for x in self.items if x.name != name]
def __iter__(self):
for c in self.items:
yield c
a = XLData('a')
b = XLData('b')
c = XLData('c')
dl = Container()
dl.append(a)
dl.append(b)
dl.append(c)
del dl['b']
for c in dl:
print(c)
output
a x
0 A 13
1 S 97
c x
0 A 91
1 S 17

Related

How to overload python sets to accept duplicates?

I had taken a python coding test, which asked to create a class that overloads the builtin set(all the methods of sets must work). The only change between the set I was asked to create and the builtin sets is that my custom sets SHOULD store duplicates, and 2 more custom methods.
Here is what I could come up with:
import builtins
class Multiset(builtins.set):
def __init__(self):
super().__init__()
self.my_set = builtins.set()
def add(self, val):
self.my_set.add(val)
def remove(self, val):
# removes one occurrence of val from the multiset, if any
self.my_set.discard(val)
def __contains__(self, val):
# returns True when val is in the multiset, else returns False
return val in self.my_set
def __len__(self):
# returns the number of elements in the multiset
return len(self.my_set)
I have tried overriding multiple methods, but to no avail. I also couldn't find a method that defined this non-duplicate criteria for sets. So, how do I do this?
EDIT 1:
Here is the problem description, if you want to see it.
You can use a dictionary which maps objects to a list of all objects that are equal to themselves. The advantage is that dict keys are already set-like.
from collections import defaultdict
class MultiSet:
def __init__(self):
self._items = defaultdict(list)
def add(self, item):
self._items[item].append(item)
def remove(self, item):
try:
self._items[item].remove(item)
except ValueError:
pass
def __contains__(self, item):
return item in self._items
def __len__(self):
return sum(len(v) for v in self._items.values())

Is there one variable way to create single linked list in python

So I have the below single linked list class:
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
and now I create a single linked list from an array [-10,-3,0,5,9]
input = [-10,-3,0,5,9]
head = ListNode(input[0])
for idx, x in enumerate(input[1:]):
if idx == 0:
head.next = ListNode(x)
temp = head.next
else:
temp.next = ListNode(x)
temp = temp.next
Let's check my implementation:
while (head):
print(head.val)
head = head.next
the output is
-10
-3
0
5
9
now the output is correct. But as you saw, I used some ugly way of creating a single linked list instance, i.e., I used two variables: temp and head. Is there any way I can use only head or temp?
Here is a decent implementation of a singly linked list, with a separate class for ListNode and SinglyLinkedList, in which the ListNode is a node in a singly-linked list whereas SinglyLinkedList is the singly-linked list which also supports the operations like prepend and append.
class ListNode:
"""
A node in a singly-linked list.
"""
def __init__(self, data=None, next=None):
self.data = data
self.next = next
def __repr__(self):
return repr(self.data)
class SinglyLinkedList:
def __init__(self):
"""
Create a new singly-linked list.
Takes O(1) time.
"""
self.head = None
def __repr__(self):
"""
Return a string representation of the list.
Takes O(n) time.
"""
nodes = []
curr = self.head
while curr:
nodes.append(repr(curr))
curr = curr.next
return '[' + ', '.join(nodes) + ']'
def prepend(self, data):
"""
Insert a new element at the beginning of the list.
Takes O(1) time.
"""
self.head = ListNode(data=data, next=self.head)
def append(self, data):
"""
Insert a new element at the end of the list.
Takes O(n) time.
"""
if not self.head:
self.head = ListNode(data=data)
return
curr = self.head
while curr.next:
curr = curr.next
curr.next = ListNode(data=data)
if __name__ == '__main__':
singly_linked_list = SinglyLinkedList()
print(singly_linked_list)
input_array = [-10, -3, 0, 5, 9]
for x in input_array:
print(x)
singly_linked_list.append(x)
print(singly_linked_list)
Other implementations for the singly-linked list can be seen here.
Hope it helps.

Create a list that can be accessed from multiple classes

I'm trying to create a list that is populated in one class and read in another class. I have a number of things, right now I'm getting the error shown below. I show only one class where the list is read, but there will be others.
How do I do this?
ReadDatabase.py
class ReadDatabase(object):
f_type_list = ReadDatabase.getFTypes() ## 'ReadDatabase' not defined
#staticmethod
def getFTypes():
<reads from database>
return my_list
MyTreeView.py
from ReadDatabase import *
class MyTreeView (ttk.Treeview):
def __init__(self, frame=None, list=[], column_headers=[]):
for f_type in ReadDatabase.f_type_list:
<do stuff>
You can separate it into two different classes in two different ways.
Example 1: Using two classes, one class (A) creates & updates the list and the other class (B) creates an instance of A and controls it:
class A:
"""
Create and updates the list
"""
def __init__(self):
self.my_list = []
self._read_from_database()
def _read_from_database(self):
# some database update logic
self.my_list.append(3)
class B:
"""
Creates an instance of A and can read from it.
"""
def __init__(self):
self.a = A()
def print_list(self):
for index, element in enumerate(self.a.my_list):
print(f"Index: {index} Element: {element}")
b_object = B()
b_object.print_list() # Prints: Index: 0 Element: 3
or
Example 2: You can just create a method in class B and just pass it the lst from class A:
class A:
"""
Create and updates the list
"""
def __init__(self):
self.my_list = []
self._read_from_database()
def _read_from_database(self):
# some database update logic
self.my_list.append(3)
class B:
def __init__(self):
pass
def print_list(self, lst):
for index, element in enumerate(lst):
print(f"Index: {index} Element: {element}")
a_object = A()
b_object = B()
b_object.print_list(a_object.my_list)
You can also pass the entire instance of A to B for it to use if you wanted to do it that way.

Linked list implementation in python

I am learning how to use linked lists, and would like to add a value, remove a value, and test if a value is in the linked list. I am struggling to work out how test for a value and remove a value.
class Node(object):
def __init__(self, v, n):
self.value = v
self.next = n
class LinkedList(object):
def __init__(self):
self.firstLink = None
def add (self, newElement):
self.firstLink = Node(newElement, self.firstLink)
def test(self, testValue):
def remove(self, testValue):
To test if a value is in a LinkedList you have to go through the list and check every item
def contains(self, testValue):
ptr = self.firstLink
while ptr != None:
if ptr.value == testValue:
return True
ptr = ptr.next
return False
When using remove() method you usually don't pick an item to be removed. Remove method should only remove the last item added to LinkedList. Last in, First out.
def remove(self):
if self.firstLink == None():
return None
else:
item = self.firstLink.value
self.firstLink = self.firstLink.next
return item
To learn more about Linked Lists or see how can 'remove element' from LinkedList be implemented in python go to this site. It is well explained in there LinkedList

QTreeView only edits in first column?

I am trying to make a simple property editor, where the property list is a nested dict and the data is displayed and edited in a QTreeView. (Before I get to my question -- if anyone already has a working implementation of this in Python 3 I'd love to be pointed at it).
Anyway, after much work I have my QAbstractItemModel and I can open a QTreeView with this model and it shows the data. If I click on a label in the first column (the key) then it opens up an editor, either a text editor or a spinbox etc depending on the datatype. When I finish editing it calls my "model.setData" where I reject it because I don't want to allow editable keys. I can disable the editing of this by using flags and that works fine. I just wanted to check that everything works the way that I'd expect it to.
Here is what doesn't happen: if I click on a cell in the second column (the value that I actually want to edit) then it bypasses the loading of an editor and simply calls model.setData with the current value. I am baffled. I've tried changing the tree selectionBehavior and selectionMode but no dice. I'm returning Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable in flags. It seems to display fine. It just won't open up an editor.
Any thoughts about what stupid mistake I must be making? I'll include the code below, with some print statements that I'm using to try to debug the thing.
Thanks
PS One thing that hung me up for a long time was that my QModelIndex members would just disappear, so the indices that I got back were garbage. I found that by keeping a reference to them (throwing them in a list) that they worked. This seems to be a problem that springs up a lot in Qt work (I had the same problem with menus disappearing -- I guess that means that I should think about it sooner). Is there a "best practices" way of dealing with this?
# -*- coding: utf-8 -*-
from collections import OrderedDict
from PyQt4.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt4.QtGui import QAbstractItemView
class PropertyList(OrderedDict):
def __init__(self, *args, **kwargs):
OrderedDict.__init__(self, *args, **kwargs)
self.myModel = PropertyListModel(self)
def __getitem__(self,index):
if issubclass(type(index), list):
item = self
for key in index:
item = item[key]
return item
else:
return OrderedDict.__getitem__(self, index)
class PropertyListModel(QAbstractItemModel):
def __init__(self, propList, *args, **kwargs):
QAbstractItemModel.__init__(self, *args, **kwargs)
self.propertyList = propList
self.myIndexes = [] # Needed to stop garbage collection
def index(self, row, column, parent):
"""Returns QModelIndex to row, column in parent (QModelIndex)"""
if not self.hasIndex(row, column, parent):
return QModelIndex()
if parent.isValid():
indexPtr = parent.internalPointer()
parentDict = self.propertyList[indexPtr]
else:
parentDict = self.propertyList
indexPtr = []
rowKey = list(parentDict.keys())[row]
childPtr = indexPtr+[rowKey]
newIndex = self.createIndex(row, column, childPtr)
self.myIndexes.append(childPtr)
return newIndex
def get_row(self, key):
"""Returns the row of the given key (list of keys) in its parent"""
if key:
parent = key[:-1]
return list(self.propertyList[parent].keys()).index(key[-1])
else:
return 0
def parent(self, index):
"""
Returns the parent (QModelIndex) of the given item (QModelIndex)
Top level returns QModelIndex()
"""
if not index.isValid():
return QModelIndex()
childKeylist = index.internalPointer()
if childKeylist:
parentKeylist = childKeylist[:-1]
self.myIndexes.append(parentKeylist)
return self.createIndex(self.get_row(parentKeylist), 0,
parentKeylist)
else:
return QModelIndex()
def rowCount(self, parent):
"""Returns number of rows in parent (QModelIndex)"""
if parent.column() > 0:
return 0 # only keys have children, not values
if parent.isValid():
indexPtr = parent.internalPointer()
try:
parentValue = self.propertyList[indexPtr]
except:
return 0
if issubclass(type(parentValue), dict):
return len(self.propertyList[indexPtr])
else:
return 0
else:
return len(self.propertyList)
def columnCount(self, parent):
return 2 # Key & value
def data(self, index, role):
"""Returns data for given role for given index (QModelIndex)"""
# print('Looking for data in role {}'.format(role))
if not index.isValid():
return None
if role in (Qt.DisplayRole, Qt.EditRole):
indexPtr = index.internalPointer()
if index.column() == 1: # Column 1, send the value
return self.propertyList[indexPtr]
else: # Column 0, send the key
if indexPtr:
return indexPtr[-1]
else:
return ""
else: # Not display or Edit
return None
def setData(self, index, value, role):
"""Sets the value of index in a given role"""
print('In SetData')
if not index.isValid():
return False
print('Trying to set {} to {}'.format(index,value))
print('That is column {}'.format(index.column()))
if not index.column(): # Only change column 1
return False
try:
ptr = index.internalPointer()
self.propertyList[ptr[:-1]][ptr[-1]] = value
self.emit(self.dataChanged(index, index))
return True
except:
return False
def flags(self, index):
"""Indicates what can be done with the data"""
if not index.isValid():
return Qt.NoItemFlags
if index.column(): # only enable editing of values, not keys
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable #Qt.NoItemFlags
if __name__ == '__main__':
p = PropertyList({'k1':'v1','k2':{'k3':'v3','k4':4}})
import sys
from PyQt4 import QtGui
qApp = QtGui.QApplication(sys.argv)
treeView = QtGui.QTreeView()
# I've played with all the settings on these to no avail
treeView.setHeaderHidden(False)
treeView.setAllColumnsShowFocus(True)
treeView.setUniformRowHeights(True)
treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
treeView.setSelectionMode(QAbstractItemView.SingleSelection)
treeView.setAlternatingRowColors(True)
treeView.setEditTriggers(QAbstractItemView.DoubleClicked |
QAbstractItemView.SelectedClicked |
QAbstractItemView.EditKeyPressed |
QAbstractItemView.AnyKeyPressed)
treeView.setTabKeyNavigation(True)
treeView.setModel(p.myModel)
treeView.show()
sys.exit(qApp.exec_())
#strubbly was real close but forgot to unpack the tuple in his index method.
Here's the working code for Qt5. There are probably a couple of imports and stuff that would need to be fixed. Only cost me a couple weeks of my life :)
import sys
from collections import OrderedDict
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
class TupleKeyedOrderedDict(OrderedDict):
def __init__(self, *args, **kwargs):
super().__init__(sorted(kwargs.items()))
def __getitem__(self, key):
if isinstance(key, tuple):
item = self
for k in key:
if item != ():
item = item[k]
return item
else:
return super().__getitem__(key)
def __setitem__(self, key, value):
if isinstance(key, tuple):
item = self
previous_item = None
for k in key:
if item != ():
previous_item = item
item = item[k]
previous_item[key[-1]] = value
else:
return super().__setitem__(key, value)
class SettingsModel(QtCore.QAbstractItemModel):
def __init__(self, data, parent=None):
super().__init__(parent)
self.root = data
self.my_index = {} # Needed to stop garbage collection
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if parent.isValid():
index_pointer = parent.internalPointer()
parent_dict = self.root[index_pointer]
else:
parent_dict = self.root
index_pointer = ()
row_key = list(parent_dict.keys())[row]
child_pointer = (*index_pointer, row_key)
try:
child_pointer = self.my_index[child_pointer]
except KeyError:
self.my_index[child_pointer] = child_pointer
index = self.createIndex(row, column, child_pointer)
return index
def get_row(self, key):
if key:
parent = key[:-1]
if not parent:
return 0
return list(self.root[parent].keys()).index(key[-1])
else:
return 0
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
child_key_list = index.internalPointer()
if child_key_list:
parent_key_list = child_key_list[:-1]
try:
parent_key_list = self.my_index[parent_key_list]
except KeyError:
self.my_index[parent_key_list] = parent_key_list
return self.createIndex(self.get_row(parent_key_list), 0,
parent_key_list)
else:
return QtCore.QModelIndex()
def rowCount(self, parent):
if parent.column() > 0:
return 0 # only keys have children, not values
if parent.isValid():
indexPtr = parent.internalPointer()
parentValue = self.root[indexPtr]
if isinstance(parentValue, OrderedDict):
return len(self.root[indexPtr])
else:
return 0
else:
return len(self.root)
def columnCount(self, parent):
return 2 # Key & value
def data(self, index, role):
if not index.isValid():
return None
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
indexPtr = index.internalPointer()
if index.column() == 1: # Column 1, send the value
return self.root[indexPtr]
else: # Column 0, send the key
if indexPtr:
return indexPtr[-1]
else:
return None
else: # Not display or Edit
return None
def setData(self, index, value, role):
pointer = self.my_index[index.internalPointer()]
self.root[pointer] = value
self.dataChanged.emit(index, index)
return True
def flags(self, index):
if not index.isValid():
return 0
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
data = TupleKeyedOrderedDict(**{'1': OrderedDict({'sub': 'b'}), '2': OrderedDict({'subsub': '3'})})
model = SettingsModel(data)
tree_view = QtWidgets.QTreeView()
tree_view.setModel(model)
tree_view.show()
sys.exit(app.exec_())
You keep a list of indexes to prevent them being garbage collected. This is needed because, as the documentation explains, the Python object referenced by the internalPointer of a QModelIndex is not protected from garbage collection by that reference. However, your list is added to every time your Model is asked for an index so a new internalPointer is created even for the same Item in the Model. Whereas Qt expects the index and therefore the internalPointer to be the same. This is also problematic since it means the indexes list just keeps growing (as you can see if you add a debug print printing out the contents of self.myIndexes).
This is not trivial to fix in your case. In most models, the internalPointer just stores a pointer to the parent item which is therefore never duplicated. But that won't work in your case because items in the PropertyList don't know their parent. The easiest solution might be to change that, but the PropertyList shouldn't really be affected by its use in the Qt model.
Instead, I have built a dict which is used to find an "original" key list for any key list you build. This looks a bit odd but it works and fixes your code with the fewest changes. I have mentioned some alternative approaches at the bottom.
So these are my changes (really just the lines changing self.myIndexes but also changing the key list to be a tuple rather than a list so it can be hashed):
def __init__(self, propList, *args, **kwargs):
QAbstractItemModel.__init__(self, *args, **kwargs)
self.propertyList = propList
self.myIndexes = {} # Needed to stop garbage collection
def index(self, row, column, parent):
"""Returns QModelIndex to row, column in parent (QModelIndex)"""
if not self.hasIndex(row, column, parent):
return QModelIndex()
if parent.isValid():
indexPtr = parent.internalPointer()
parentDict = self.propertyList[indexPtr]
else:
parentDict = self.propertyList
indexPtr = ()
rowKey = list(parentDict.keys())[row]
childPtr = indexPtr+(rowKey,)
try:
childPtr = self.myIndexes[childPtr]
except KeyError:
self.myIndexes[childPtr] = childPtr
newIndex = self.createIndex(row, column, childPtr)
return newIndex
def parent(self, index):
"""
Returns the parent (QModelIndex) of the given item (QModelIndex)
Top level returns QModelIndex()
"""
if not index.isValid():
return QModelIndex()
childKeylist = index.internalPointer()
if childKeylist:
parentKeylist = childKeylist[:-1]
try:
parentKeylist = self.myIndexes[parentKeylist]
except KeyError:
self.myIndexes[parentKeylist] = parentKeylist
return self.createIndex(self.get_row(parentKeylist), 0,
parentKeylist)
else:
return QModelIndex()
This seems to work, though I've not done too much testing.
Alternatively you could use the internalPointer to store the parent model item (dictionary) and keep a mapping from model item to key list. Or a mapping from model item to parent item. Both of these need a little fiddling (not least because dictionaries are not immediately hashable) but both are possible.

Resources