Python enumerated array syntax - python

I'm writing in Python and writing to a console with an array I am manipulating and have a syntax question.
The array is passed in and I create another another array for tracking position using along with sort the array but I dont see why when I want to access the iterable array it becomes a 2d array
posarr = [*enumerate(arr)]
posarr.sort(key = lambda it:it[1])
Then to access from a loop it is:
posarr[i][0] == blah
instead of
posarr[i] == blah
?

It's not a 2D array, it's a list of tuples. In each tuple, the first item is the index, and the second is the value (as you would expect from enumerate).
Forget about the sorting for now, just look at what posarr contains, for example:
>>> posarr = [*enumerate('cab')]
>>> posarr
[(0, 'c'), (1, 'a'), (2, 'b')]
BTW this has nothing to do with syntax.

Note: The enumerate() function assigns an index to each item in an iterable object
So, posarr = [*enumerate(arr)] will create a new array of tuples, where the first item of the tuple will be the index and the second item be the array element at that index.
After posarr = posarr.sort(key = lambda it:it[1]), posarr will be None because list.sort() does not return anything. It sorts an array in-place.
It should be like this: posarr.sort(key = lambda it:it[1]).
posarr[i] will return the tuple at index i, so to access the element at that index, you have to do something like this posarr[i][1]. posarr[i][0] is the index of the element posarr[i][1].
So, posarr is not a 2-D array, it is just an array of tuples.

Related

Why did we use Lambda as function argument here?

Few questions on the below code to find if a list is sorted or not:
Why did we use lambda as key here ? Does it always mean key of a list can be derived so ?
In the enumerate loop , why did we compare key(el) < key(lst[i]) and not key(el) <key(el-1) or lst[i+1] <lst[i] ?
def is_sorted(lst, key=lambda x:x):
for i, el in enumerate(lst[1:]):
if key(el) < key(lst[i]): # i is the index of the previous element
return False
return True
hh=[1,2,3,4,6]
val = is_sorted(hh)
print(val)
(NB: the code above was taken from this SO answer)
This code scans a list to see if it is sorted low to high. The first problem is to decide what "low" and "high" mean for arbitrary types. Its easy for integers, but what about user defined types? So, the author lets you pass in a function that converts a type to something whose comparison works the way you want.
For instance, lets say you want to sort tuples, but based on the 3rd item which you know to be an integer, it would be key=lambda x: x[2]. But the author provides a default key=lamba x:x which just returns the object its supplied for items that are already their own sort key.
The second part is easy. If any item is less than the item just before it, then we found an example where its not low to high. The reason it works is literally in the comment - i is the index of the element directly preceding el. We know this because we enumerated on the second and following elements of the list (enumerate(lst[1:]))
enumerate yields both index and current element:
for i, el in enumerate(lst):
print(i,el)
would print:
0 1
1 2
2 3
3 4
4 6
By slicing the list off by one (removing the first element), the code introduces a shift between the index and the current element, and it allows to access by index only once (not seen as pythonic to use indexes on lists when iterating on them fully)
It's still better/pythonic to zip (interleave) list and a sliced version of the list and pass a comparison to all, no indices involved, clearer code:
import itertools
def is_sorted(lst, key=lambda x:x):
return all(key(current) < key(prev) for prev,current in zip(lst,itertools.islice(lst,1,None,None)))
The slicing being done by islice, no extra list is generated (otherwise it's the same as lst[1:])
The key function (here: identity function by default) is the function which converts from the value to the comparable value. For integers, identity is okay, unless we want to reverse comparison, in which case we would pass lambda x:-x
The point is not that the lambda "derives" the key of a list. Rather, it's a function that allows you to choose the key. That is, given a list of objects of type X, what attribute would you use to compare them with? The default is the identity function - ie use the plain value of each element. But you could choose anything here.
You could indeed write this function by comparing lst[i+1] < lst[i]. You couldn't however write it by comparing key(el) < key(el-1), because el is the value of the element itself, not the index.
This is a function that test if a list has been sorted, as an example with the builtin sorted function. This function takes an keyword argument key which is used on every single element on the list to compute its compare value:
>>> sorted([(0,3),(1,2),(2,1),(3,0)])
[(0, 3), (1, 2), (2, 1), (3, 0)]
>>> sorted([(0,3),(1,2),(2,1),(3,0)],key=lambda x:x[1])
[(3, 0), (2, 1), (1, 2), (0, 3)]
The key keyword in your function is to be able to mimic the behavior of sorted:
>>> is_sorted([(0,3),(1,2),(2,1),(3,0)])
True
>>> is_sorted([(0,3),(1,2),(2,1),(3,0)],key=lambda x:x[1])
False
The default lambda is just there to mimic a default behavior where nothing is changed.

Popping first element from a Python tuple

Is there any way to pop the first element from a Python tuple?
For example, for
tuple('A', 'B', 'C')
I would like to pop off the 'A' and have a tuple containing 'B' and 'C'.
Since tuples are immutable I understand that I need to copy them to a new tuple. But how can I filter out only the first element of the tuple?
With this tuple
x = ('A','B','C')
you can get a tuple containing all but the first element using a slice:
x[1:]
Result:
('B','C')

Understanding "ValueError: need more than 1 value to unpack" w/without enumerate()

I've been searching around for a succinct explanation of what's going on "under the hood" for the following, but no luck so far.
Why, when you try the following:
mylist = ["a","b","c","d"]
for index, item in mylist:
print item
I get this error:
ValueError: need more than 1 value to unpack
But when I try:
for item in mylist:
print item
This is returned:
a
b
c
d
If indexes are a part of the structure of a list, why can't I print them out along with the items?
I understand the solution to this is to use enumerate(), but I'm curious about why iterating through lists (without using enumerate()) works this way and returns that ValueError.
I think what I'm not understanding is: if you can find items in a list by using their index (such as the case with item = L[index] ) — doesn't that mean that one some level, indexes are an inherent part of a list as a data structure? Or is item = L[index] really just a way to get Python to count the items in a list using indexes (starting at 0 obviously)? In other words, item = L[index] is "applying" indexes to the items in the list, starting at 0.
If indexes are a part of the structure of a list...
Except they aren't. Not when you iterate over the list. The indexing becomes a matter of time/occurrence, and they are no longer associated with the elements themselves.
If you were to actually print out the result of the enumerate() function as a list:
print(list(enumerate(["a","b","c","d"])))
You would see this:
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]
Therefore, if you wanted to print the index and item at that index using enumerate(), you could technically write this:
for pair in enumerate(mylist):
print pair[0], pair[1]
However, that's not the best (i.e. Pythonic) way of doing things. Python lets you write the above much more nicely like so:
for index, item in enumerate(mylist):
print index, item
This works because when you use the index, item syntax, you are telling Python to "unpack" each pair in that list by treating the components of each pair separately.
For more on how this tuple unpacking magic works, see:
Tuple unpacking in for loops

Replace first elements of tuple with zero over a certain length

I have a sorted list based on first element:
A = [(0.11, '201405'), (0.41, '201402'),.....,(1.5, '201430')] # values and time
and want to change first element of each tuple:
x = len(C) # C is a list and its length is 2
y = len(D) # D is a list and its length is 1
if x > y:
A[0[0:x]] = 0.0 # if x > y then set first element of A equal to zero (over length of C)
But I get following error:
TypeError: 'int' object is not subscriptable
Please suggest to fix it.
If I understand your question correctly, you want to replace the first element in each of the first few tuples with 0. Tuples are not modifyable, so you can not modify the existing tuples in the list, but you have to create a new list, holding new tuples. You can do this using a list comprehension.
Example:
>>> A = [(1,"a"), (2,"b"), (3,"c"), (4,"d")]
>>> A = [(x if i > 1 else 0, y) for i, (x, y) in enumerate(A)]
>>> print A
[(0, 'a'), (0, 'b'), (3, 'c'), (4, 'd')]
This will enumerate all the entries in the list, and for each entry create a tuple with the first element being 0, if the index i of that entry is lower than some threshold, or the original value otherwise.
As iharob mentioned, you have a subscript notation error.
Tuples are immutable. You can't just replace 1 element of the tuple in your list. You have to replace the whole tuple with another tuple containing 0.0 as first value and the existing second value. Note that lists are modifiable but tuples aren't. That's why you can update the list A, but you have to replace the tuple with a new tuple.
Here's an example that does not recreate the whole list:
for i, t in enumerate(A):
if i < 1:
A[i] = (0, t[1])
You have an error in A[0[0:x]] = 0.0 you are using subscript notation for the number 0.
Furthermore, the tuple is an immutable type. Hence, it is impossible to change the content of a tuple. You'd better to generate a new list such as:
A = [0.0]+A[1:]

Using variable tuple to access elements of list

Disclaimer:beginner, self-teaching Python user.
A pretty cool feature of ndarrays is their ability to accept a tuple of integers as indices (e.g. myNDArray[(1,2)] == myNDArray[1][2]). This allows me to leave the indices unspecified as a variable (e.g. indicesTuple ) until a script determines what part of an ndarray to work with, in which case the variable is specified as a tuple of integers and used to access part of an ndarray (e.g. myNDArray[indicesTuple]). The utility in using a variable is that the LENGTH of the tuple can be varied depending on the dimensions of the ndarray.
However, this limits me to working with arrays of numerical values. I tried using lists, but they can't take in a tuple as indices (e.g. myList[(1,2)] gives an error.). Is there a way to "unwrap" a tuple for list indices as one could for function arguments? Or something far easier or more efficient?
UPDATE: Holy shite I forgot this existed. Basically I eventually learned that you can initialize the ndarray with the argument dtype=object, which allows the ndarray to contain multiple types of Python objects, much like a list. As for accessing a list, as a commenter pointed out, I could use a for-loop to iterate through the variable indicesTuple to access increasingly nested elements of the list. For in-place editing, see the accepted comment, really went the extra mile there.
I'm interpreting your question as:
I have an N-dimensional list, and a tuple containing N values (T1, T2... TN). How can I use the tuple values to access the list? I don't know what N will be ahead of time.
I don't know of a built-in way to do this, but you can write a method that iteratively digs into the list until you reach the innermost value.
def get(seq, indices):
for index in indices:
seq = seq[index]
return seq
seq = [
[
["a","b"],
["c","d"]
],
[
["e","f"],
["g","h"]
]
]
indices = [0,1,0]
print get(seq, indices)
Result:
c
You could also do this in one* line with reduce, although it won't be very clear to the reader what you're trying to accomplish.
print reduce(lambda s, idx: s[idx], indices, seq)
(*if you're using 3.X, you'll need to import reduce from functools. So, two lines.)
If you want to set values in the N-dimensional list, use get to access the second-deepest level of the list, and assign to that.
def set(seq, indices, value):
innermost_list = get(seq, indices[:-1])
innermost_list[indices[-1]] = value
Say you have a list of (i,j) indexes
indexList = [(1,1), (0,1), (1,2)]
And some 2D list you want to index from
l = [[1,2,3],
[4,5,6],
[7,8,9]]
You could get those elements using a list comprehension as follows
>>> [l[i][j] for i,j in indexList]
[5, 2, 6]
Then your indexes can be whatever you want them to be. They will be unpacked in the list comprehension, and used as list indices. For your specific application, we'd have to see where your index variables were coming from, but that's the general idea.
Python doesn't have multidimensional lists, so myList[(1,2)] could only conceivably be considered a shortcut for (myList[1], myList[2]) (which would be pretty convenient sometimes, although you can use import operator; x = operator.itemgetter(1,2)(myList) to accomplish the same).
If your myList looks something like
myList = [ ["foo", "bar", "baz"], ["a", "b", c" ] ]
then myList[(1,2)] won't work (or make sense) because myList is not a two-dimensional list: it's a list that contains references to lists. You use myList[1][2] because the first index myList[1] returns the references to ["a", "b", "c"], to which you apply the second index [2] to get "c".
Slightly related, you could use a dictionary to simulate a sparse array precisely by using tuples as keys to a default dict.
import collections
d = collections.defaultdict(str)
d[(1,2)] = "foo"
d[(4,5)] = "bar"
Any other tuple you try to use as a key would return the empty string. It's not a perfect simulation, as you can't access full rows or columns of the array without using something like
row1 = [d[1, x] for x in range(C)] # where C is the number of columns
col3 = [d[x, 3] for x in range(R)] # where R is the number of columns
Use dictionaries indexed by tuple
>>> width, height = 7, 6
>>> grid = dict(
((x,y),"x={} y={}".format(x,y))
for x in range(width)
for y in range(height))
>>> print grid[3,1]
x=3 y=1
Use lists of lists
>>> width, height = 7, 6
>>> grid = [
["x={} y={}".format(x,y) for x in range(width)]
for y in range(width)]
>>> print grid[1][3]
x=3 y=1
In this case, you could make a getter and setter function:
def get_grid(grid, index):
x, y = index
return grid[y][x]
def set_grid(grid, index, value):
x, y = index
grid[y][x] = value
You could go a step further and create your own class that contains a list of lists and defines an indexer that takes tuples as indexes and does this same process. It can do slightly more sensible bounds-checking and give better diagnostics than the dictionary, but it takes a bit of setup. I think the dictionary approach is fine for quick exploration.

Categories

Resources