Specify a Default Offset for Python List - python

Is there a way in python that you can specify a default offset for list?
Like:
a = [0, 1, 2, 3, 4, 5, 6]
a.offset = 2
So that whenever use index for access/modify, the index will be added by the offset first:
a[0] == 2
a[4] == 6

There's no built-in way to achieve this. However you can create your custom class by extending list to get this behaviour. When you do my_list[n], internally __getitem__() function is triggered. You can override this function to return the value by adding offset to the index.
Similarly, list contains other magic functions which you can override to further modify the behaviour of your custom class. For example, __setitem__() is triggered when you assign any value to list, __delitem__() is trigger while deleting the item.
Here's a sample code to create OffsetList class which takes additional argument as offset while creating the list, and performs index based operations on index+offset value.
class OffsetList(list):
def __init__(self, offset, *args, **kwargs):
super(OffsetList, self).__init__(*args, **kwargs)
self.offset = offset
def _get_offset_index(self, key):
if isinstance(key, slice):
key = slice(
None if key.start is None else key.start + self.offset,
None if key.stop is None else key.stop + self.offset,
key.step
)
elif isinstance(key, int):
key += self.offset
return key
def __getitem__(self, key):
key = self._get_offset_index(key)
return super(OffsetList, self).__getitem__(key)
def __setitem__(self, key, value):
key = self._get_offset_index(key)
return super(OffsetList, self).__setitem__(key, value)
def __delitem__(self, key):
key = self._get_offset_index(key)
return super(OffsetList, self).__delitem__(key)
Sample Run:
# With offset as `0`, behaves as normal list
>>> offset_list = OffsetList(0, [10,20,30,40,50,60])
>>> offset_list[0]
10
# With offset as `1`, returns index+1
>>> offset_list = OffsetList(1, [10,20,30,40,50,60])
>>> offset_list[0]
20
# With offset as `2`, returns index+2
>>> offset_list = OffsetList(2, [10,20,30,40,50,60])
>>> offset_list[0]
30
# Slicing support, with `start` as start+offset and `end` as end+offset
>>> offset_list[1:]
[40, 50, 60]
# Assigning new value, based on index+offset
>>> offset_list[0] = 123
>>> offset_list
[10, 20, 123, 40, 50, 60]
# Deleting value based on index+offset
>>> del offset_list[0]
>>> offset_list
[10, 20, 40, 50, 60]
Similarly you can modify the behaviour of other magic functions like __len__(), __iter__(), __repr__(), __str__(), etc as per your need.

There is no such feature in Python -- or in any other language that I know of. Your suggested syntax is reasonable, assuming that you could get the feature approved. However, it has several drawbacks.
Until and unless this feature became common usage, you would confuse anyone trying to read such code. Zero-based and one-based indexing are the "rule"; arbitrary indexing is a violation of long-learned assumptions.
You would seriously crimp Python's right-end indexing: the semantics aren't clear. If someone writes a[-1] to access the last element, should they get that element (this is a language-defined idiom), the original a[1] element (per your definition), a "reflective" a[-3], or index out of bounds trying to move two elements to the right?
Note that Python does give you the capability to define your own functionality:
class
Any time you don't like the given data types, you get to make your own. You're not allowed to alter the built-in types, but you can do what you like by inheriting from list and writing your own get and other methods.

If you're just reading data from the list, you could probably work with a subscript copy of the original:
a = [0, 1, 2, 3, 4, 5, 6]
a = a[2:]
a[0] == 2 # True
a[4] == 6 # True
Keep in mind that this makes a copy of the list using the same variable name so you are losing the original content (indexes 0 and 1). You could keep it in a separate variable if you do need it though:
a = [0, 1, 2, 3, 4, 5, 6]
a0,a = a,a[2:]
a[0] == 2 # True
a[4] == 6 # True
a0[0] == 0 # True
a0[4] == 4 # True
If you really need a view on the original array with read and write capabilities, then I would suggest using a numpy array:
import numpy as np
a = np.array([0, 1, 2, 3, 4, 5, 6])
b = a[2:].view()
b[0] == 2 # True
b[4] == 4 # True
b[1] = 99
print(a) # [ 0 1 2 99 4 5 6]
a[3] == 99 # True
If you want to implement something similar to numpy yourself, you could create a class that represents a "view" on a list with an internal slice property (start, stop, step):
class ListView:
def __init__(self,aList,start=None,stop=None,step=1):
self.data = aList
self.slice = slice(start,stop,step)
#property
def indices(self): return range(len(self.data))[self.slice]
def offset(self,index=None):
if not isinstance(index,slice): return self.indices[index]
first = self.indices[index][0]
last = self.indices[index][-1]
step = (index.step or 1)*(self.slice.step or 1)
return slice(first,last+1-2*(step<0),step)
def __len__(self): return len(self.indices)
def __getitem__(self,index): return self.data[self.offset(index)]
def __repr__(self): return self[:].__repr__()
def __iter__(self): return self[:].__iter__()
def __setitem__(self,index,value): self.data[self.offset(index)] = value
def __delitem__(self,index): del self.data[self.offset(index)]
usage:
a = list(range(1,21))
v = ListView(a,3,-2,2)
len(v) # 8
print(a)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
print(v)
# [4, 6, 8, 10, 12, 14, 16, 18]
v[2] += 80
print(a)
# [1, 2, 3, 4, 5, 6, 7, 88, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
v.slice = slice(-4,None,-3)
print(v)
# [17, 14, 11, 88, 5, 2]

Related

How to use RECURSION to group items in a list using python

I'm trying to create a function that consumes a flat list and returns a list of lists/nested list that basically groups the values of the original list together based on their value.
so basically
L = [ 1, 19, 5, 2, 22, 12 28]
if I group them in groups of based on groups of 10 L will become
L = [[1, 2, 5], [12, 19], [22, 28]]
However I HAVE to use recursion to do this. I'm a little lost and don't know where to start. If I can have a bit of help I would appreciate it
def group_lists(L, pos):
if pos>=len(L):
return none
else:
if L[pos]<=10:
return ?
elif 20>=L[pos]>10:
return ?
else:
return ?
You should pass the intermediate result through the recursive calls, and then return it when you've run out of elements. If you must use exactly two parameters for whatever reason, make a wrapper function or use the global keyword. (I would not recommend using global in practice, however.)
def group_lists(L, pos, result):
if len(L) <= pos:
return result
else:
result[L[pos] // 10].append(L[pos])
return group_lists(L, pos + 1, result)
L = [ 1, 19, 5, 2, 22, 12, 28]
print(group_lists(L, 0, [[], [], []]))
Here's a version that adds one sublist per recursive call:
>>> def group_lists(arr):
... if isinstance(arr[-1], list):
... return arr
... i = sum(isinstance(sub, list) for sub in arr)
... return group_lists(
... arr[:i]
... + [[n for n in arr[i:] if n // 10 == i]]
... + [n for n in arr[i:] if n // 10 != i]
... )
...
>>> group_lists([1, 19, 5, 2, 22, 12, 28])
[[1, 5, 2], [19, 12], [22, 28]]
You can try the following:
lst = [1, 19, 5, 2, 22, 12, 28]
def group_lists(lst):
if not lst: # empty list
return []
output = group_lists(lst[1:]) # recursion
while len(output) <= lst[0] // 10: # if not enough slots
output.append([]) # make as many as needed
output[lst[0] // 10].append(lst[0]) # append the item to the right slot
return output
print(group_lists(lst)) # [[2, 5, 1], [12, 19], [28, 22]]
This would automatically add slots as needed, using a while loop. It will make empty slots if your input is something like [1, 21].
You can use defaultdict structure from collection module like
from collections import defaultdict
def group_list(L, res=defaultdict(list)):
if len(L) == 1:
res[L[0] // 10].append(L[0])
return
if len(L) == 0:
return
group_list(L[:len(L) // 2])
group_list(L[len(L) // 2:])
return res
print(group_list([ 1, 19, 5, 2, 22, 12, 28]))
#defaultdict(<class 'list'>, {0: [1, 5, 2], 1: [19, 12], 2: [22, 28]})

How to slice a circular list

Suppose I have list as follow:
lst = [0,10,20,30,40,50,60,70]
I want elements from lst from index = 5 to index = 2 in cyclic order.
lst[5:2] yields []
I want lst[5:2] = [50,60,70,0,10]. Is there any simple library function to do this?
Simply split the slicing in two if the second term is smaller than the first:
lst = [0,10,20,30,40,50,60,70]
def circslice(l, a, b):
if b>=a:
return l[a:b]
else:
return l[a:]+l[:b]
circslice(lst, 5, 2)
output: [50, 60, 70, 0, 10]
Using a deque as suggested in comments:
from collections import deque
d = deque(lst)
a,b = 5,2
d.rotate(-a)
list(d)[:len(lst)-a+b]
NB. I find it not very practical as it requires to make a copy of the list to create the deque, and another copy to slice
For something that allows you to still use the native slicing syntax and that maintains static typing compatibility, you can use a light wrapper class around your sequence:
from typing import Generic, Protocol, TypeVar
S = TypeVar('S', bound="ConcatSequence")
class CircularView(Generic[S]):
def __init__(self, seq: S) -> None:
self.seq = seq
def __getitem__(self, s: slice) -> S:
if s.start <= s.stop:
return self.seq[s]
else:
wrap = len(self.seq) % s.step if s.step else 0
return self.seq[s.start::s.step] + self.seq[wrap:s.stop:s.step]
lst = [0, 10, 20, 30, 40, 50, 60, 70]
print(CircularView(lst)[2:5]) # [20, 30, 40]
print(CircularView(lst)[5:2]) # [50, 60, 70, 0, 10]
print(CircularView(lst)[5:2:2]) # [50, 70, 0]
print(CircularView(lst)[5:3:2]) # [50, 70, 0, 20]
print(CircularView(lst)[4:3:3]) # [40, 70, 20]
with the optional protocol for static typing
class ConcatSequence(Protocol):
"""
A sequence that implements concatenation via '__add__'.
This protocol is required instead of using
'collections.abc.Sequence' since not all sequence types
implement '__add__' (for example, 'range').
"""
def __add__(self, other):
...
def __getitem__(self, item):
...
def __len__(self):
...
This method passes type checking with mypy.
You could use a function like this:
def circular_indexing(list_, start_index, end_index) -> list:
return [*list_[start_index:len(list_)], *list_[0:end_index]]
For example:
list1 = [0, 1, 2, 3]
def circular_indexing(list_, start_index, end_index) -> list:
return [*list_[start_index:len(list_)], *list_[0:end_index]]
print(circular_indexing(list1, 2, 1))
Output: [2, 3, 0]
There are two fast/easy solutions to this problem.
The first, and more complicated method, would be to overwrite the default python library implementation of the python list.__getitem__ method, which has been referenced in other places on StackOverflow.
This would allow you to reference the slicing as you would normally, i.e. list[5:3], and it would, in theory, behave as you define. This would be a "local expansion" of the default library.
Transversely, you could implement your own function that will iterate over your list in a circular manner, that meets your own criterion.
Some pseudo-code:
def foo(left_idx, right_idx):
if right_idx < left_idx:
wrap index when right bound has been reached
else:
iterate normally

Is there a way to append to an object in python? Priority Queues

At the moment I am currently getting an error when it comes to my enQueue function. When I try to append a number to my object list, it says "'set' object has no attribute 'append'". I assume the problem has to do with how I am passing in the list but this is currently my problem. I have one hard-coded list of size 10 to work with because I didn't want to make a bigger list until I know what's going on. Any help would be appreciated. Also the comments that I have in the code is what I want to do as the end result. If you have any input on doing that, that would be more than helpful. At the moment though, I would like to just figure out how to not get that error. Thank you.
class PQ_List(object):
def __init__(self, sampleList):
print ("creates an unsorted list from passed in list")
self.list = sampleList
print (self.list)
#
# Returns the list
def enQueue(self, item):
print ("adds an item to the PQ")
self.list.append(item)
print (self.list)
# Add an item to the PQ
def deQueue(self):
print ("removes the highest priority item from the PQ")
self.list = self.list[1:]
print (self.list)
# Remove the highest priority item from the PQ
def sneakAPeek(self):
print ("returns the highest priority in the PQ, but does not remove it")
return self.list[0]
#
# Return the highest priority item from the PQ, but don't remove it
def isEmpty(self):
print ("returns T if PQ is empty, F if PQ has entries")
if len(self.list) > 0:
return 'F'
else:
return 'T'
# Return a T if PQ is empty, F if PQ is not empty
#
def size(self):
print ("returns number of items in queue")
return len(self.list)
# Return the number of items in the queue
sampleList = {1, 2, 5, 8, 4, 15, 13, 12, 10, 6}
my_listPQ = PQ_List(sampleList) #print first 10 numbers, use size to prove the rest is there
my_listPQ.enQueue(1500)
my_listPQ.deQueue()
my_listPQ.sneakAPeek()
my_listPQ.isEmpty()
my_listPQ.size()
I expect the output to add 1500 to the list for the enQueue function. Then do the following functions.
Any help would be appreciated!
In python you use the square brackets [, ] for lists and the curly brackets {, } for sets.
Hence, change the line
sampleList = {1, 2, 5, 8, 4, 15, 13, 12, 10, 6}
to
sampleList = [1, 2, 5, 8, 4, 15, 13, 12, 10, 6]
and you are good to go.
change
sampleList = {1, 2, 5, 8, 4, 15, 13, 12, 10, 6} # this is set and don't have append
to this one:
sampleList = [1, 2, 5, 8, 4, 15, 13, 12, 10, 6] # this is list

How to access list of arbitrary depth?

I have a list known as data which can be any depth equal to or greater than 1.
e.g;
Sometimes data = [3,65,3]
or data = [[3,65,3],[88,44,9],[6,2,21]] ... and so on.
How would I generally access data, considering it's depth can fluctuate? It's assumed that the depth and indexes to access an element (a number) will always be known.
I.e.
Say I have a function f that takes arbitrary arguments. How would I generalize the following?
f(a , b, c...)
if depth = 1:
return data[a]
elif depth = 2:
return data[a][b]
elif depth = 3:
return data[a][b][c]
...
and so on
You can iteratively access sublists based on the number of indices you provide, which implicitly means that's the depth. You can further make checks for the legality of the arguments if you don't trust the user to always provide valid indices.
def f(*indices):
element = data
for index in indices:
element = element[index]
return element
You can also edit the value at some index, which will be done in place.
def f(indices, value):
if not len(indices):
return
element = data
for index in indices[:-1]:
element = element[index]
element[indices[-1]] = value
indices is supposed to be a loop, so you'd be expected to call it as f((1, 1), 5). If instead you would prefer to call it as f(1, 1, 5), where all the arguments are the indices and the ast one is the new value, then change the function to
def f(*args):
indices, value = args[:-1], args[-1]
# then the same as above
Considering your input would be like this:
data = [[3,65,3],[88,44,[9, 4, 56]],[6,2,21]]
you can flatten it like this:
def flatten(l):
r = []
for e in l:
if isinstance(e, list):
r += flatten(e)
else:
r.append(e)
return r
OUTPUT:
>>> flatten([[3,65,3],[88,44,[9, 4, 56]],[6,2,21]])
[3, 65, 3, 88, 44, 9, 4, 56, 6, 2, 21]
Another recursive solution using generators:
def flatten(s):
if not isinstance(s, list):
yield s
else:
for i in s:
for b in flatten(i):
yield b
print(list(flatten([[3,65,[3, [[5, 6, [2, 6, [4]]]]],[88,44,[9, 4, 56]],[6,2,21]]])))
Output:
[3, 65, 3, 5, 6, 2, 6, 4, 88, 44, 9, 4, 56, 6, 2, 21]

Need to create a list of sets, from a list of sets whose members may be connected

I'm dealing with polygonal data in realtime here, but the problems quite simple.
I have a huge list containing thousands of sets of polygon Indecies (Integers) and I need to simplify the list as "fast" as possible into a list of sets of "connected" Indecies.
i.e. Any sets containing integers that are also in another set become one set in the result. I've read several possible solutions involving sets & graphs etc. All i'm after are a final list of sets which had any degree of commonality.
I'm dealing with lots of data here, but for simplicities sake here's some sample data:
setA = set([0,1,2])
setB = set([6,7,8,9])
setC = set([4,5,6])
setD = set([3,4,5,0])
setE = set([10,11,12])
setF = set([11,13,14,15])
setG = set([16,17,18,19])
listOfSets = [setA,setB,setC,setD,setE,setF,setG]
In this case I'm after a list with a result like this, although ordering is irrelevant:
connectedFacesListOfSets = [ set([0,1,2,3,4,5,6,7,8,9]), set([10,11,12,13,14,15]), set([16,17,18,19])]
I've looked for similar solutions, but the one with the highest votes gave incorrect results on my large test data.
Merge lists that share common elements
It's hard to tell the performance without a sufficiently large set, but here is some basic code to start from:
while True:
merged_one = False
supersets = [listOfSets[0]]
for s in listOfSets[1:]:
in_super_set = False
for ss in supersets:
if s & ss:
ss |= s
merged_one = True
in_super_set = True
break
if not in_super_set:
supersets.append(s)
print supersets
if not merged_one:
break
listOfSets = supersets
This works in 3 iterations on the provided data. And the output is as follows:
[set([0, 1, 2, 3, 4, 5]), set([4, 5, 6, 7, 8, 9]), set([10, 11, 12, 13, 14, 15]), set([16, 17, 18, 19])]
[set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), set([10, 11, 12, 13, 14, 15]), set([16, 17, 18, 19])]
[set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), set([10, 11, 12, 13, 14, 15]), set([16, 17, 18, 19])]
This is a union find problem.
Though I haven't used it, this Python code looks good to me.
http://code.activestate.com/recipes/577225-union-find/
Forgive the messed up caps (autocorrect...):
# the results cotainer
Connected = set()
sets = # some list of sets
# convert the sets to frozensets (which are hashable and can be added to sets themselves)
Sets = map(frozenset, sets)
for s1 in sets:
Res = copy.copy(s1)
For s2 in sets:
If s1 & s2:
Res = res | s2
Connected.add(res)
So.. I think I got it. It's a mess but I got it. Here's what I did:
def connected_valid(li):
for i, l in enumerate(li):
for j, k in enumerate(li):
if i != j and contains(l,k):
return False
return True
def contains(set1, set2):
for s in set1:
if s in set2:
return True
return False
def combine(set1, set2):
set2 |= set1
return set2
def connect_sets(li):
while not connected_valid(li):
s1 = li.pop(0)
s2 = li[0]
if contains(s1, s2):
li[0] = combine(s1,s2)
else:
li.append(s1)
return li
Then in the main function you'd do something like this:
setA = set([0,1,2])
setB = set([6,7,8,9])
setC = set([4,5,6])
setD = set([3,4,5,0])
setE = set([10,11,12])
setF = set([11,13,14,15])
setG = set([16,17,18,19])
connected_sets = connect_sets([setA,setB,setC,setD,setE,setF,setG,])
After running it, I got the following output
print connected_sets
[set([0,1,2,3,4,5,6,7,8,9]), set([10,11,12,13,14,15]), set([16,17,18,19])]
Hope that's what you're looking for.
EDIT: Added code to randomly generate sets:
# Creates a list of 4000 sets with a random number of values ranging from 0 to 20000
sets = []
ma = 0
mi = 21000
for x in range(4000):
rand_num = sample(range(20),1)[0]
tmp_set_li = sample(range(20000), rand_num)
sets.append(set(tmp_set_li))
The last 3 lines can be condensed into one if you really wanted to.
I tried to do something different: this algorithm loops once for each set and once for each element:
# Our test sets
setA = set([0,1,2])
setB = set([6,7,8,9])
setC = set([4,5,6])
setD = set([3,4,5,0])
setE = set([10,11,12])
setF = set([11,13,14,15])
setG = set([16,17,18,19])
list_of_sets = [setA,setB,setC,setD,setE,setF,setG]
# We will use a map to store our new merged sets.
# This map will work as an reference abstraction, so it will
# map set ids to the set or to other set id.
# This map may have an indirection level greater than 1
merged_sets = {}
# We will also use a map between indexes and set ids.
index_to_id = {}
# Given a set id, returns an equivalent set id that refers directly
# to a set in the merged_sets map
def resolve_id(id):
if not isinstance(id, (int, long)):
return None
while isinstance(merged_sets[id], (int, long)):
id = merged_sets[id]
return id
# Points the informed set to the destination id
def link_id(id_source, id_destination):
point_to = merged_sets[id_source]
merged_sets[id_source] = id_destination
if isinstance(point_to, (int, long)):
link_id(point_to, id_destination)
empty_set_found = False
# For each set
for current_set_id, current_set in enumerate(list_of_sets):
if len(current_set) == 0 and empty_set_found:
continue
if len(current_set) == 0:
empty_set_found = True
# Create a set id for the set and place it on the merged sets map
merged_sets[current_set_id] = current_set
# For each index in the current set
possibly_merged_current_set = current_set
for index in current_set:
# See if the index is free, i.e., has not been assigned to any set id
if index not in index_to_id:
# If it is free, then assign the set id to the index
index_to_id[index] = current_set_id
# ... and then go to the next index
else:
# If it is not free, then we may need to merge the sets
# Find out to which set we need to merge the current one,
# ... dereferencing if necessary
id_to_merge = resolve_id(index_to_id[index])
# First we check to see if the assignment is to the current set or not
if id_to_merge == resolve_id(merged_sets[current_set_id]):
continue
# Merge the current set to the one found
print 'Merging %d with %d' % (current_set_id, id_to_merge)
merged_sets[id_to_merge] |= possibly_merged_current_set
possibly_merged_current_set = merged_sets[id_to_merge]
# Map the current set id to the set id of the merged set
link_id(current_set_id, id_to_merge)
# Return all the sets in the merged sets map (ignore the references)
print [x for x in merged_sets.itervalues() if not isinstance(x, (int, long))]
It prints:
Merging 2 with 1
Merging 3 with 0
Merging 3 with 1
Merging 5 with 4
[set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), set([10, 11, 12, 13, 14, 15]), set([16, 17, 18, 19])]

Categories