Use array as index in python - python

So I have an array like so ['test', 'testtwo'].
I wish to able to use that as an index for a dictionary like so myDict['test']['testtwo'].
Is this possible in python? Sorry for the short explanation.
EDIT:
exampleDict = {
'test': {
'testtwo': [
'',
''
]
}
}
And when doing some stuff in python I end up with the array ['test', 'testtwo'] and then need to use that to access exampleDict['test']['testtwo']. The keys change though and the number of keys in the array changes as well.

You could either use a loop, iterating the indices in the list and updating the "current" dict as you go:
>>> exampleDict = {'test': {'testtwo': [ '', '']}}
>>> d = exampleDict
>>> for x in idx:
... d = d[x]
>>> d
['', '']
Or you could even use reduce (functools.reduce in Python 3):
>>> reduce(lambda d, x: d[x], idx, exampleDict)
['', '']
You could use a similar approach to update the dict, but 1) you should use setdefault in case part of the index-list is not yet in the dict, and 2) you have to remove the last item from the list and use that as a regular index to the returned dictionary.
>>> idx2 = ['test', 'testthree', 'four']
>>> reduce(lambda d, x: d.setdefault(x, {}), idx2[:-1], exampleDict)[idx2[-1]] = "foo"
>>> exampleDict
{'test': {'testthree': {'four': 'foo'}, 'testtwo': ['', '']}}
In Python 3, you could make that line a bit easier to use using tuple-unpacking with *:
>>> *path, last = idx2
>>> reduce(lambda d, x: d.setdefault(x, {}), path, exampleDict)[last] = "foo"

You cannot use a list for a dictionary key because lists are mutable, and mutable keys aren't allowed, but I think that what you want is to use each element of the list as an index. Without more context, it's not easy to say whether this is a good idea without proper checks, but:
my_list = ['a', 'b']
my_dict[my_list[0]][my_list[1]] # access at ['a']['b']
I have a feeling that whichever problem you wish to solve might be solved in a different way.

Related

Is there an elegant way of combining the Python vars() and filter() functions to display the values of a subset of variables?

Is there a way of displaying the names and values of a subset of variables in a string?
The following filters the names of certain variables, but is there an elegant way, within one line, to show the values of the variables too?
>>> result=list(filter(lambda x : x in ['a'], vars()))
>>> result
['a']
Yes, it can be done:
>>> a = 123
>>> result = list(filter(lambda x: x[0] in {'a'}, vars().items()))
>>> result
[('a', 123)]
But imo a much more elegant way is to use a list comprehension:
>>> a = 123
>>> result = [(name, value) for name, value in vars().items() if name in {'a'}]
>>> result
[('a', 123)]
Use vars().items() to get the keys and values.
But you can't use filter() becau
result = list(filter(lambda x: x[0] in ['a'], vars().items()))
This will return
[('a', 'value_of_a')]
Best thing I've managed so far:
foodict = {k: v for k, v in vars().items() if k in ['a']}

Counting the amount of occurrences in a list of tuples

I am fairly new to python, but I haven't been able to find a solution to my problem anywhere.
I want to count the occurrences of a string inside a list of tuples.
Here is the list of tuples:
list1 = [
('12392', 'some string', 'some other string'),
('12392', 'some new string', 'some other string'),
('7862', None, 'some other string')
]
I've tried this but it just prints 0
for entry in list1:
print list1.count(entry[0])
As the same ID occurs twice in the list, this should return:
2
1
I also tried to increment a counter for each occurrence of the same ID but couldn't quite grasp how to write it.
*EDIT:
Using Eumiro's awesome answer. I just realized that I didn't explain the whole problem.
I actually need the total amount of entries which has a value more than 1. But if I try doing:
for name, value in list1:
if value > 1:
print value
I get this error:
ValueError: Too many values to unpack
Maybe collections.Counter could solve your problem:
from collections import Counter
Counter(elem[0] for elem in list1)
returns
Counter({'12392': 2, '7862': 1})
It is fast since it iterates over your list just once. You iterate over entries and then try to get a count of these entries within your list. That cannot be done with .count, but might be done as follows:
for entry in list1:
print(sum(1 for elem in list1 if elem[0] == entry[0]))
But seriously, have a look at collections.Counter.
EDIT: I actually need the total amount of entries which has a value more than 1.
You can still use the Counter:
c = Counter(elem[0] for elem in list1)
sum(v for k, v in c.iteritems() if v > 1)
returns 2, i.e. the sum of counts that are higher than 1.
list1.count(entry[0]) will not work because it looks at each of the three tuples in list1, eg. ('12392', 'some string', 'some other string') and checks if they are equal to '12392' for example, which is obviously not the case.
#eurmiro's answer shows you how to do it with Counter (which is the best way!) but here is a poor man's version to illustrate how Counter works using a dictionary and the dict.get(k, [,d]) method which will attempt to get a key (k), but if it doesn't exist it returns the default value instead (d):
>>> list1 = [
('12392', 'some string', 'some other string'),
('12392', 'some new string', 'some other string'),
('7862', None, 'some other string')
]
>>> d = {}
>>> for x, y, z in list1:
d[x] = d.get(x, 0) + 1
>>> d
{'12392': 2, '7862': 1}
I needed some extra functionality that Counter didn't have. I have a list of tuples that the first element is the key and the second element is the amount to add. #jamylak solution was a great adaptation for this!
>>> list = [(0,5), (3,2), (2,1), (0,2), (3,4)]
>>> d = {}
>>> for x, y in list1:
d[x] = d.get(x, 0) + y
>>> d
{0: 7, 2: 1, 3: 6}

Python - tuple unpacking in dict comprehension

I'm trying to write a function that turns strings of the form 'A=5, b=7' into a dict {'A': 5, 'b': 7}. The following code snippets are what happen inside the main for loop - they turn a single part of the string into a single dict element.
This is fine:
s = 'A=5'
name, value = s.split('=')
d = {name: int(value)}
This is not:
s = 'A=5'
d = {name: int(value) for name, value in s.split('=')}
ValueError: need more than 1 value to unpack
Why can't I unpack the tuple when it's in a dict comprehension? If I get this working then I can easily make the whole function into a single compact dict comprehension.
In your code, s.split('=') will return the list: ['A', '5']. When iterating over that list, a single string gets returned each time (the first time it is 'A', the second time it is '5') so you can't unpack that single string into 2 variables.
You could try: for name,value in [s.split('=')]
More likely, you have an iterable of strings that you want to split -- then your dict comprehension becomes simple (2 lines):
splitstrs = (s.split('=') for s in list_of_strings)
d = {name: int(value) for name,value in splitstrs }
Of course, if you're obsessed with 1-liners, you can combine it, but I wouldn't.
Sure you could do this:
>>> s = 'A=5, b=7'
>>> {k: int(v) for k, v in (item.split('=') for item in s.split(','))}
{'A': 5, ' b': 7}
But in this case I would just use this more imperative code:
>>> d = {}
>>> for item in s.split(','):
k, v = item.split('=')
d[k] = int(v)
>>> d
{'A': 5, ' b': 7}
Some people tend to believe you'll go to hell for using eval, but...
s = 'A=5, b=7'
eval('dict(%s)' % s)
Or better, to be safe (thanks to mgilson for pointing it out):
s = 'A=5, b=7'
eval('dict(%s)' % s, {'__builtins__': None, 'dict': dict})
See mgilson answer to why the error is happening. To achieve what you want, you could use:
d = {name: int(value) for name,value in (x.split('=',1) for x in s.split(','))}
To account for spaces, use .strip() as needed (ex.: x.strip().split('=',1)).
How about this code:
a="A=5, b=9"
b=dict((x, int(y)) for x, y in re.findall("([a-zA-Z]+)=(\d+)", a))
print b
Output:
{'A': 5, 'b': 9}
This version will work with other forms of input as well, for example
a="A=5 b=9 blabla: yyy=100"
will give you
{'A': 5, 'b': 9, 'yyy': 100}
>>> strs='A=5, b=7'
>>> {x.split('=')[0].strip():int(x.split('=')[1]) for x in strs.split(",")}
{'A': 5, 'b': 7}
for readability you should use normal for-in loop instead of comprehensions.
strs='A=5, b=7'
dic={}
for x in strs.split(','):
name,val=x.split('=')
dic[name.strip()]=int(val)
How about this?
>>> s
'a=5, b=3, c=4'
>>> {z.split('=')[0].strip(): int(z.split('=')[1]) for z in s.split(',')}
{'a': 5, 'c': 4, 'b': 3}
Since Python 3.8, you can use walrus operator (:=) for this kind of operation. It allows to assign variables in the middle of expressions (in this case, assign the list created by .split('=') to kv).
s = 'A=5, b=7'
{(kv := item.split('='))[0]: int(kv[1]) for item in s.split(', ')}
# {'A': 5, 'b': 7}
One feature is that it leaks the assigned variable, kv, outside the scope it was defined in. If you want to avoid that, you can use a nested for-loop where the inner loop is over a singleton list (as suggested in mgilson's answer).
{k: int(v) for item in s.split(', ') for k,v in [item.split('=')]}
Since Python 3.9, loops over singleton lists are optimized to be as fast as simple assignments, i.e. y in [expr] is as fast as y = expr.

Python dictionary initialization?

I am not sure if this is a bug or a feature.
I have a dictionary to be initialized with empty lists.
Lets say
keys =['one','two','three']
sets = dict.fromkeys(keys,[])
What I observed is if you append any item to any of the lists all the lists are modified.
sets = dict.fromkeys(['one','two','three'],[])
sets['one'].append(1)
sets
{'three': [1],'two': [1], 'one': [1]}
But when I do it manually using loop,
for key in keys:
sets[key] = []
sets['one'].append(1)
sets
{'three': [], 'two': [], 'one': [1]}
I would think the second behavior should be the default.
This is how things work in Python.
When you use fromkeys() in this manner, you end with three references to the same list. When you change one list, all three appear to change.
The same behaviour can also be seen here:
In [2]: l = [[]] * 3
In [3]: l
Out[3]: [[], [], []]
In [4]: l[0].append('one')
In [5]: l
Out[5]: [['one'], ['one'], ['one']]
Again, the three lists are in fact three references to the same list:
In [6]: map(id, l)
Out[6]: [18459824, 18459824, 18459824]
(notice how they have the same id)
Other answers covered the 'Why', so here's the how.
You should use a comprehension to create your desired dictionary:
>>> keys = ['one','two','three']
>>> sets = { x: [] for x in keys }
>>> sets['one'].append(1)
>>> sets
{'three': [], 'two': [], 'one': [1]}
For Python 2.6 and below, the dictionary comprehension can be replaced with:
>>> sets = dict( ((x,[]) for x in keys) )

In python is there something like updated that is to update what sorted is to sort?

In python if I do the following:
>>> list = [ 3, 2, 1]
>>> sorted_list = k.sort()
Then sorted_list is None and list is sorted:
>>> sorted_list = k.sort()
>>> print list, sorted_list
[1, 2, 3] None
However, if I do the following:
>>> list = [ 3, 2, 1]
>>> sorted_list = sorted(list)
Then list remains unsorted and sorted_list contains a copy of the sorted list:
>>> print list, sorted_list
[3, 2, 1] [1, 2, 3]
I am wondering if there is an equivalent for the update function for dictionaries.
That way I could do something like this:
def foo(a, b, extra={}):
bar = { 'first': a, 'second': b }
special_function(**updated(bar, extra))
normal_function(**bar)
rather than having to do something like this:
def foo(a, b, extra={}):
bar = { 'first': a, 'second': b }
special_bar = bar.copy()
special_bar.update(extra) # [1]
special_function(**special_bar)
normal_function(**bar)
[1] Yes I realize I could simply replace these two lines with extra.update(bar) but let's assume I want to retain extra as is for later on in the function.
I realize I could implement this myself thusly:
def updated(old_dict, extra={}):
new_dict = old_dict.copy()
new_dict.update(extra)
return new_dict
Or the following highly unreadable in-place statement:
special_function(**(dict(bar.items()+extra.items())))
But I was hoping there was something built in that I could already use.
You can simply use the built-in dict():
updated_dict = dict(old_dict, **extra_dict)
If you need non-string keys, you can use a function like that: (It is not as ugly as your "in-place" expression + it works for any number of dictionaries)
from itertools import chain # ← credits go to Niklas B.
def updated(*dicts):
return dict(chain(*map(dict.items, dicts)))
updated({42: 'the answer'}, {1337: 'elite'}) # {42: 'the answer', 1337: 'elite'}
Otherwise Sven’s suggestion is just fine.
Edit: If you are using Python 2.7 or later, you can also use a dictionary comprehension, as Sven suggested in the comments:
def updated(*dicts):
return {k: v for d in dicts for k, v in d.items()}
I don't really see what's wrong in using two lines, like you do:
new_bar = bar.copy()
new_bar.update(extra)
It's clean and readable.
>>> d = {1:2, 3:4}
>>> e = {3:9, 5:25}
>>> f = d.copy()
>>> f.update(e)
>>> d
{1: 2, 3: 4}
>>> f
{1: 2, 3: 9, 5: 25}
>>> e
{3: 9, 5: 25}
In three words: Zen of Python.
To be more clear: My point is that I wouldn't replace those two lines with an updated() function that's not coming from the standard library.
If I was to stumble in a line of code like:
new_bar = updated(bar, extra)
I'd have to track that function down to see what it does. I couldn't trust that it doesn't something strange.
The OP also compared that with sorted(), but sorted() has it's reason to exist, it acts on everything that's iterable and does that with the amazing timsort. Instead what should be the behaviour of an hypothetical updated()? Should that maybe be a dict class method? It's really not clear IMHO.
Said so one could choose the OP two lines, or Sven's solution, or a dict comprehension/generator-expression, I think it's really just a matter of taste.

Categories