Python custom class indexing - python

Is it possible to make a class in python that can be indexed with square brackets but not derived from a different indexed type?
I'm interested in making a class with optional indexes, that would behave like this:
class indexed_array():
def __init__(self, values):
self.values = values
def __sqb__(self, indices): #This is a made up thing that would convert square brackets to a function
if len(indices) == 2:
return self.values[indices[0]][indices[1]]
elif len(indices) == 1:
return self.values[indices[0]][0]
myarray = indexed_array([[1,2,3], [4,5,6], [7,8,9]])
print myarray[1, 1] # returns 5
print myarray[1] # returns 4
Is there a real method like my __sqb__? Alternatively, can you index a custom class another way?

You are need to implement __getitem__. Be aware that a single index will be passed as itself, while multiple indices will be passed as a tuple.
Typically you might choose to deal with this in the following way:
class indexed_array:
def __getitem__(self, indices):
# convert a simple index x[y] to a tuple for consistency
if not isinstance(indices, tuple):
indices = tuple(indices)
# now handle the different dimensional cases
...

Related

Is a way to implement a double index into a special method Python? [duplicate]

I'm trying to make a 2D array class, and ran into a problem. The best way I could figure out to do it was to pass get/setitem a tuple of the indices, and have it unpacked in the function. Unfortunately though, the implementation looks really messy:
class DDArray:
data = [9,8,7,6,5,4,3,2,1,0]
def __getitem__ (self, index):
return (self.data [index [0]], self.data [index [1]])
def __setitem__ (self, index, value):
self.data [index [0]] = value
self.data [index [1]] = value
test = DDArray ()
print (test [(1,2)])
test [(1, 2)] = 120
print (test [1, 2])
I tried just having it accept more parameters:
class DDArray:
data = [9,8,7,6,5,4,3,2,1,0]
def __getitem__ (self, index1, index2):
return (self.data [index1], self.data [index2])
def __setitem__ (self, index1, index2, value):
self.data [index1] = value
self.data [index2] = value
test = DDArray ()
print (test [1, 2])
test [1, 2] = 120
print (test [1, 2])
but that results in a weird type error telling me that I'm not passing enough arguments (I guess anything inside of the subscript operator is considered 1 argument, even if there's a comma).
(Yes, I know, the above class isn't actually a 2D array. I wanted to have the operators figured out before I moved on to actually making it 2D.)
Is there a standard way of doing it that looks a little cleaner?
Thanks
There are a couple ways you can do this. If you want syntax like test[1][2], then you can have __getitem__ returns a column (or row), which can be indexed again with __getitem__ (or even just return a list).
However, if you want the syntax test[1,2], you are on the right track, test[1,2] actually passes the tuple (1,2) to the __getitem__ function, so you don't need to include the parantheses when calling it.
You can make the __getitem__ and __setitem__ implementations a little less messy like so:
def __getitem__(self, indices):
i, j = indices
return (self.data[i], self.data[j])
with your actual implementation of __getitem__ of course. The point being that you have split the indices tuple into appropriately named variables.

How to modify the value of index while trying to fecth it?

For example there is a list called Demo_list.
Demo_list = [4,5,6,7]
If i give
Demo_list[0]
we will get value as 4.
But if i gave only Demo_list[0] i want to get square of that value and the list should not be modified.
Is it possible?
Yes, it is possible.
variable = Demo_list[0]**2
The code above won't modify the list.
demo_list = [4, 6, 7, 8]
for i in range (len(demo_list)):
j = demo_list[i] * demo_list[i]
print j
May be you are looking something like that..
#For complete list
SqrtList = [x**2 for x in Demo_list]
#For single element
Sqrtvariable = Demo_list**2
You can use the < math > function
import math
print ( math.pow(demo[0],2)
where, 2 is the power that you want to raise the value in demo[0].
Edit (Inheriting from the collections, and overriding the abstract list methods , in your case (getitem),that you wish to modify).
import collections
class MyList(collections.MutableSequence):
def __init__(self, *args):
self.list=list()
self.extend(list(args))
def __len__(self):
return len(self.list)
def __getitem__(self,i):
return (self.list[i]**2)
def __delitem__(self,i):
del self.list[i]
def __setitem__(self,i,v):
self.list[i]=v
def insert(self,i,v):
self.list.insert(i,v)
def __str__(self):
return str(self.list)
Note: When you override these abstract methods, you need to define your list, with the type, you declared in this class. i.e.,
demo_list=MyList(1,2,3,4)
demo_list[1]
Output : 4

How to remove `duplicates' in list of instances

I have a list of instances of a certain class. This list contains `duplicates', in the sense that duplicates share the exact same attributes. I want to remove the duplicates from this list.
I can check whether two instances share the same attributes by using
class MyClass:
def __eq__(self, other) :
return self.__dict__ == other.__dict__
I could of course iterate through the whole list of instances and compare them element by element to remove duplicates, but I was wondering if there is a more pythonic way to do this, preferably using the in operator + list comprehension.
sets (no order)
A set cannot contain duplicate elements. list(set(content)) will deduplicate a list. This is not too inefficient and is probably one of the better ways to do it :P You will need to define a __hash__ function for your class though, which must be the same for equal elements and different for unequal elements for this to work. Note that the hash value must obey the aforementioned rule but otherwise it may change between runs without causing issues.
index function (stable order)
You could do lambda l: [l[index] for index in range(len(l)) if index == l.index(l[index])]. This only keeps elements that are the first in the list.
in operator (stable order)
def uniquify(content):
result = []
for element in content:
if element not in result:
result.append(element)
return result
This will keep appending elements to the output list unless they are already in the output list.
A little more on the set approach. You can safely implement a hash by delegating to a tuple's hash - just hash a tuple of all the attributes you want to look at. You will also need to define an __eq__ that behaves properly.
class MyClass:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __eq__(self, other):
return (self.a, self.b, self.c) == (other.a, other.b, other.c)
def __hash__(self):
return hash((self.a, self.b, self.c))
def __repr__(self):
return "MyClass({!r}, {!r}, {!r})".format(self.a, self.b, self.c)
As you're doing so much tuple construction, you could just make your class iterable:
def __iter__(self):
return iter((self.a, self.b, self.c))
This enables you to call tuple on self instead of laboriously doing .a, .b, .c etc.
You can then do something like this:
def unordered_elim(l):
return list(set(l))
If you want to preserve ordering, you can use an OrderedDict instead:
from collections import OrderedDict
def ordered_elim(l):
return list(OrderedDict.fromkeys(l).keys())
This should be faster than using in or index, while still preserving ordering. You can test it something like this:
data = [MyClass("this", "is a", "duplicate"),
MyClass("first", "unique", "datum"),
MyClass("this", "is a", "duplicate"),
MyClass("second", "unique", "datum")]
print(unordered_elim(data))
print(ordered_elim(data))
With this output:
[MyClass('first', 'unique', 'datum'), MyClass('second', 'unique', 'datum'), MyClass('this', 'is a', 'duplicate')]
[MyClass('this', 'is a', 'duplicate'), MyClass('first', 'unique', 'datum'), MyClass('second', 'unique', 'datum')]
NB if any of your attributes aren't hashable, this won't work, and you'll either need to work around it (change a list to a tuple) or use a slow, n ^ 2 approach like in.

How would I be able to return which instance belongs to the random number in my list. Without using a million if statements?

what I am trying to do, is returnthe instance, which range has the value from a random.randint() in a list.... Example...
class Testing:
def __init__(self, name, value):
self.name = name
self.value = value
randomtest = Testing('First', range(1, 50))
randomtest_2 = Testing('Second', range(50, 100))
selections = []
counter = 0
while counter < 2:
counter =+ 1
selector = random.randint(1, 100)
selections.append(selector)
But I don't want to use a million if statements to determine which index in the selections list it belongs to.. Like this:
if selections[0] in list(randomtest.value):
return True
elif selections[0] in list(randomtest_2.value):
return True
Your help is much appreciated, I am fairly new to programming and my head has just come to a stand still at the moment.
You can use a set for your selections object then check the intersection with set.intersection() method:
ex:
In [84]: a = {1, 2}
In [85]: a.intersection(range(4))
Out[85]: {1, 2}
and in your code:
if selections.intersection(randomtest.value):
return True
You can also define a hase_intersect method for your Testing class, in order to cehck if an iterable object has intersection with your obejct:
class Testing:
def __init__(self, name, value):
self.name = name
self.value = value
def hase_intersect(self, iterable):
iterable = set(iterable)
return any(i in iterable for i in self.value)
And check like this:
if randomtest.hase_intersect(selections):
return True
based on your comment, if you want to check the intersection of a spesific list against a set of objects you have to iterate over the
set of objects and check the intersection using aforementioned methods. But if you want to refuse iterating over the list of objects you should probably use a base claas
with an special method that returns your desire output but still you need to use an iteration to fild the name of all intended instances. Thus, if you certainly want to
create different objects you neend to at least use 1 iteration for this task.

Python set with the ability to pop a random element

I am in need of a Python (2.7) object that functions like a set (fast insertion, deletion, and membership checking) but has the ability to return a random value. Previous questions asked on stackoverflow have answers that are things like:
import random
random.sample(mySet, 1)
But this is quite slow for large sets (it runs in O(n) time).
Other solutions aren't random enough (they depend on the internal representation of python sets, which produces some results which are very non-random):
for e in mySet:
break
# e is now an element from mySet
I coded my own rudimentary class which has constant time lookup, deletion, and random values.
class randomSet:
def __init__(self):
self.dict = {}
self.list = []
def add(self, item):
if item not in self.dict:
self.dict[item] = len(self.list)
self.list.append(item)
def addIterable(self, item):
for a in item:
self.add(a)
def delete(self, item):
if item in self.dict:
index = self.dict[item]
if index == len(self.list)-1:
del self.dict[self.list[index]]
del self.list[index]
else:
self.list[index] = self.list.pop()
self.dict[self.list[index]] = index
del self.dict[item]
def getRandom(self):
if self.list:
return self.list[random.randomint(0,len(self.list)-1)]
def popRandom(self):
if self.list:
index = random.randint(0,len(self.list)-1)
if index == len(self.list)-1:
del self.dict[self.list[index]]
return self.list.pop()
returnValue = self.list[index]
self.list[index] = self.list.pop()
self.dict[self.list[index]] = index
del self.dict[returnValue]
return returnValue
Are there any better implementations for this, or any big improvements to be made to this code?
I think the best way to do this would be to use the MutableSet abstract base class in collections. Inherit from MutableSet, and then define add, discard, __len__, __iter__, and __contains__; also rewrite __init__ to optionally accept a sequence, just like the set constructor does. MutableSet provides built-in definitions of all other set methods based on those methods. That way you get the full set interface cheaply. (And if you do this, addIterable is defined for you, under the name extend.)
discard in the standard set interface appears to be what you have called delete here. So rename delete to discard. Also, instead of having a separate popRandom method, you could just define popRandom like so:
def popRandom(self):
item = self.getRandom()
self.discard(item)
return item
That way you don't have to maintain two separate item removal methods.
Finally, in your item removal method (delete now, discard according to the standard set interface), you don't need an if statement. Instead of testing whether index == len(self.list) - 1, simply swap the final item in the list with the item at the index of the list to be popped, and make the necessary change to the reverse-indexing dictionary. Then pop the last item from the list and remove it from the dictionary. This works whether index == len(self.list) - 1 or not:
def discard(self, item):
if item in self.dict:
index = self.dict[item]
self.list[index], self.list[-1] = self.list[-1], self.list[index]
self.dict[self.list[index]] = index
del self.list[-1] # or in one line:
del self.dict[item] # del self.dict[self.list.pop()]
One approach you could take is to derive a new class from set which salts itself with random objects of a type derived from int.
You can then use pop to select a random element, and if it is not of the salt type, reinsert and return it, but if it is of the salt type, insert a new, randomly-generated salt object (and pop to select a new object).
This will tend to alter the order in which objects are selected. On average, the number of attempts will depend on the proportion of salting elements, i.e. amortised O(k) performance.
Can't we implement a new class inheriting from set with some (hackish) modifications that enable us to retrieve a random element from the list with O(1) lookup time? Btw, on Python 2.x you should inherit from object, i.e. use class randomSet(object). Also PEP8 is something to consider for you :-)
Edit:
For getting some ideas of what hackish solutions might be capable of, this thread is worth reading:
http://python.6.n6.nabble.com/Get-item-from-set-td1530758.html
Here's a solution from scratch, which adds and pops in constant time. I also included some extra set functions for demonstrative purposes.
from random import randint
class RandomSet(object):
"""
Implements a set in which elements can be
added and drawn uniformly and randomly in
constant time.
"""
def __init__(self, seq=None):
self.dict = {}
self.list = []
if seq is not None:
for x in seq:
self.add(x)
def add(self, x):
if x not in self.dict:
self.dict[x] = len(self.list)
self.list.append(x)
def pop(self, x=None):
if x is None:
i = randint(0,len(self.list)-1)
x = self.list[i]
else:
i = self.dict[x]
self.list[i] = self.list[-1]
self.dict[self.list[-1]] = i
self.list.pop()
self.dict.pop(x)
return x
def __contains__(self, x):
return x in self.dict
def __iter__(self):
return iter(self.list)
def __repr__(self):
return "{" + ", ".join(str(x) for x in self.list) + "}"
def __len__(self):
return len(self.list)
Yes, I'd implement an "ordered set" in much the same way you did - and use a list as an internal data structure.
However, I'd inherit straight from "set" and just keep track of the added items in an
internal list (as you did) - and leave the methods I don't use alone.
Maybe add a "sync" method to update the internal list whenever the set is updated
by set-specific operations, like the *_update methods.
That if using an "ordered dict" does not cover your use cases. (I just found that trying to cast ordered_dict keys to a regular set is not optmized, so if you need set operations on your data that is not an option)
If you don't mind only supporting comparable elements, then you could use blist.sortedset.

Categories

Resources