I've got a system in which I often (but not constantly) have to find the next item in a tuple. I'm currently doing this like so:
mytuple = (2,6,4,8,7,9,14,3)
currentelement = 4
def f(mytuple, currentelement):
return mytuple[mytuple.index(currentelement) + 1]
nextelement = f(mytuple, currentelement)
All elements are unique and I am not stuck with the tuple, I can make it something else earlier in the program if needed.
Since I need to do this a lot, I was wondering whether there is any more efficient way to do this?
Use a dict here, dicts provide O(1) lookup compared to list.index which is an O(N) operation.
And this will work for strings as well.
>>> lis = (2,6,4,8,7,9,14,3)
>>> dic = dict(zip(lis, lis[1:]))
>>> dic[4]
8
>>> dic[7]
9
>>> dic.get(100, 'not found') #dict.get can handle key errors
'not found'
A memory efficient version to create the above dict:
>>> from itertools import izip
>>> lis = (2,6,4,8,7,9,14,3)
>>> it1 = iter(lis)
>>> it2 = iter(lis)
>>> next(it2)
2
>>> dict(izip(it1,it2))
{2: 6, 4: 8, 6: 4, 7: 9, 8: 7, 9: 14, 14: 3}
You might wish to build an index using a dictionary:
# The list
>>> lis = (2,6,4,8,7,9,14,3)
# build the index
>>> index = dict(zip(lis, range(len(lis))))
>>> index
{2: 0, 3: 7, 4: 2, 6: 1, 7: 4, 8: 3, 9: 5, 14: 6}
# Retrieve position by using the index
>>> index[6]
1
>>> lis[index[6]+1]
4
If your list change over time, you will have to rebuild the index. For a more memory efficient solution, you might prefer using izip instead of `zip̀ as suggested in an other answer.
Related
I tried to add the string elements to the dictionary but it doesnt work
adatok = ["marha", 1, "kacsa", 2, "kacsa", 1, "birka", 3, "marha", 4, "marha", 2]
sorszámok = list(set([x for x in adatok if type(x) == int]))
szótár = {}
for i in sorszámok:#Létrehozzuk a sorszámokat egy szótárba
szótár.update({i:set()})
for j in adatok:
if type(j) == int:
szótár[j].add(adatok[adatok.index(j)-1])
#Expected output:{1: {'marha','kacsa'}, 2: {'kacsa','marha'}, 3: {'birka'}, 4: {'marha'}}
#Output:{1: {'marha'}, 2: {'kacsa'}, 3: {'birka'}, 4: {'marha'}}
Instead of starting with unique keys and then trying to find the values for each key, iterate over the key, value pairs and add them to the sets. Using defaultdict saves you the trouble of setting up the dict ahead of time.
>>> from collections import defaultdict
>>> adatok = ["marha", 1, "kacsa", 2, "kacsa", 1, "birka", 3, "marha", 4, "marha", 2]
>>> szótár = defaultdict(set)
>>> for k, v in zip(adatok[1::2], adatok[0::2]):
... szótár[k].add(v)
...
>>> dict(szótár)
{1: {'marha', 'kacsa'}, 2: {'marha', 'kacsa'}, 3: {'birka'}, 4: {'marha'}}
counts = defaultdict(int)
for elem in sets: #list of sets
for _ in elem: #each set contains elements that may be duplicates among the sets
counts[_] += 1
Is there a way to use dict comprehension for code like this?
Don't use a list comprehension for side effects (i.e. updating counts).
Instead, if you replace defaultdict(int) with Counter, you can write the solution in one line. (I assume your end goal is to make your code shorter/cleaner.)
sets = [
{1, 2, 3},
{1, 2},
{3},
{3}]
# ---
from collections import Counter
counts = Counter(x for s in sets for x in s)
print(counts) # -> Counter({3: 3, 1: 2, 2: 2})
Note: Instead of the nested generator expression, you might prefer to use itertools.chain.from_iterable(sets).
If you are adamant at using list comprehension, you can do this way:
>>> counts = {1:4, 2:5, 3:8, 4:2, 5:9}
>>> sets = [{2,3}, {3,1}, {2}]
>>> [[counts.update({e: counts[e]+1}) for e in si] for si in sets]
[[None, None], [None, None], [None]]
>>> counts
{1: 5, 2: 7, 3: 10, 4: 2, 5: 9}
But yes, as others suggested, you can do it using Counter:
#1: use Counter to get new counts from sets
#2: merge both new & default counters
#3: get Counter back into your dictionary
With sample input:
>>> counts = {1:4, 2:5, 3:8, 4:2, 5:9}
>>> sets = [{2,3}, {3,1}, {2}]
>>> from collections import Counter
>>> cntr_new = Counter(x for s in sets for x in s)
>>> cntr_new
Counter({2: 2, 3: 2, 1: 1})
>>> cntr_orig = Counter(counts)
>>> cntr_final = cntr_orig + cntr_new
Final result:
>>> dict(cntr_final)
{1: 5, 2: 7, 3: 10, 4: 2, 5: 9}
Here is a list a=[1,1,1,2,4,2,4,32,1,4,35,23,24,23]
I do this in python:
unique_number=list(set(a))
ans=map(lambda x:a.index(x),unique_number)
output:
<map at 0x2b98b307828>
I want to know what's wrong with my code and find an more efficient way to achieve this.
This code would work as you expected in Python 2. In Python 3, map returns an iterator. You could, e.g., convert it to a list:
>>> ans=map(lambda x:a.index(x),unique_number)
>>> list(ans)
[7, 0, 3, 10, 4, 11, 12]
You can avoid keep re-indexing and building a set first - simply build a dict iterating over a backwards as the dictionary will only keep the last value for a key (in this case - the earliest appearing index), eg:
a=[1,1,1,2,4,2,4,32,1,4,35,23,24,23]
first_index = {v:len(a) - k for k,v in enumerate(reversed(a), 1)}
# {1: 0, 2: 3, 4: 4, 23: 11, 24: 12, 32: 7, 35: 10}
This way you're only scanning the sequence once.
Try this:
for value in map(lambda x:a.index(x),unique_number):
print(value)
or append this:
for var in ans:
print(var)
I need to unite two lists in Python3,where duplicates can exist,and for one set of these the resulting list will contain as many as max in both lists.An example might clarify it:
[1,2,2,5]( some operator)[2,5,5,5,9]=[1,2,2,5,5,5,9]
Ideas?
You can use the collections.Counter class:
>>> from collections import Counter
>>> combined = Counter([1,2,2,5]) | Counter([2,5,5,5,9])
>>> list(combined.elements())
[1, 2, 2, 5, 5, 5, 9]
It functions as a multiset (an unordered collection where each element can appear multiple times). The | operator gives you the union of the multisets, where each element appears max(apperances_in_counter1, appearances_in_counter2) times.
This class was added in Python 2.7 and 3.1.
Why use lists in the first place? That data looks like a dict to me:
[1,2,2,5] -> {1: 1, 2: 2, 5: 1}
[2,5,5,5,9] -> {2: 1, 5: 3, 9: 1}
Then it's simple:
keys = set(a.keys()+b.keys())
vals = [max(a.get(n, 0), b.get(n, 0)) for n in keys]
d = dict(zip(keys, vals))
print d
Result:
{1: 1, 2: 2, 5: 3, 9: 1}
Convert arrays to dictionaries with a[key] = count
Create new dictionary with rules c[key] = a.get(key, 0) > b.get(key, 0) and a[key] or b[key]. You need to iterate through both keys in a and in b dicts.
Expand dictionary, result += [value] * key
this is what I did. is there a better way in python?
for k in a_list:
if kvMap.has_key(k):
kvMap[k]=kvMap[k]+1
else:
kvMap[k]=1
Thanks
Use defaultdict
from collections import defaultdict
kvmap= defaultdict(int)
for k in a_list:
kvmap[k] += 1
Single element:
a_list.count(k)
All elements:
counts = dict((k, a_list.count(k)) for k in set(a_list))
I dunno, it basically looks fine to me. Your code is simple and easy to read which is an important part of what I consider pythonic.
You could trim it up a bit like so:
for k in a_list:
kvMap[k] = 1 + kvMap.get(k,0)
Such an old question, but considering that adding to a defaultdict(int) is such a common use, It should come as no surprise that collections has a special name for that (since Python 2.7)
>>> from collections import Counter
>>> Counter([1, 2, 1, 1, 3, 2, 3, 4])
Counter({1: 3, 2: 2, 3: 2, 4: 1})
>>> Counter("banana")
Counter({'a': 3, 'n': 2, 'b': 1})
Another solution exploits setdefault():
for k in a_list:
kvMap[k] = kvMap.setdefault(k, 0) + 1
If your list is sorted, an alternative way would be to use itertools.groupby. This might not be the most effective way, but it's interesting nonetheless. It retuns a dict of item > count :
>>> import itertools
>>> l = [1,1,2,3,4,4,4,5,5,6,6,6,7]
>>> dict([(key, len([e for e in group]))
for (key, group)
in itertools.groupby(l)])
{1: 2, 2: 1, 3: 1, 4: 3, 5: 2, 6: 3, 7: 1}