How to count the key from the nested dictionary? - python-3.x

I'd like to count and return the largest number of keys from the root('key1') to reach the end. How to compute and return the number of the deepest nest to the dictionary without using any libraries?
{'key1': {'key2': {'key3': {'key4': {'key5': {'key6': 'Y',
'key7': 'N'}},
'key8': {'key9': {'key10': 'Y', 'key11': 'N'}},
'key12': {'key13': {'key14': 'N', 'key15': 'Y'}},
'key16': {'key17': {'N': 'Y'}}}},
'key18': {'key19': {'key20': 'N', 'key21': 'N', 'key22': 'N', 'key23': 'Y'}}}}
Under the case, I expect to return 6 as a counted number.

Here's a recursive solution that doesn't use any libraries (though there might be a better way to do this using collections):
def deepest_nesting(data):
max_depth = 0
if not isinstance(data, dict):
return max_depth
for v in data.values():
path_depth = deepest_nesting(v)
max_depth = max(path_depth, max_depth)
return 1 + max_depth
This returns 6 for your example, 1 for {'key1': 0}, 0 for a non-dict, 4 for {'one': {'two': 0, 'three': 0, 'four': 0}, 'five': {'six': {'seven': 0, 'eight': 0, 'nine': {'ten': 0}}}}, etc.

Related

list elements replacing with specific values in python

I have a list list1=['a','b','c','a','c','d','a','b',10,20] , the list may contain some more elements with 'a','b','c','d' and 'e' in randomized position. I want to replace 'a' with 10, 'b' with 0, 'c' with 20, 'd' with 100, 'e' with -10. so basically the output list should be(for list1):[10,0,20,10,20,100,10,0,10,20]
I have a list list1=['a','b','c','a','c','d','a','b',10,20] , the list may contain some more elements with 'a','b','c','d' and 'e' in randomized index position. I want to replace 'a' with 10, 'b' with 0, 'c' with 20, 'd' with 100, 'e' with -10 in the list. so basically the output list should be(for list1):[10,0,20,10,20,100,10,0,10,20]
note: I dont want to replace numerical elements
What you want to do is a basic mapping of a value to another. This is usually done using a dictionary which defines the mapping and then iterate over all the values that you want to map and apply the mapping.
Here are to approaches which will get you the expected result. One is using a list comprehension, the second alternative way is using the map() built-in function.
list1 = ['a', 'b', 'c', 'a', 'c', 'd', 'a', 'b']
mapping = {
"a": 10,
"b": 0,
"c": 20,
"d": 100,
"e": -10
}
# option 1 using a list comprehension
result = [mapping[item] for item in list1]
print(result)
# another option using the built-in map()
alternative = list(map(lambda item: mapping[item], list1))
print(alternative)
Expected output:
[10, 0, 20, 10, 20, 100, 10, 0]
[10, 0, 20, 10, 20, 100, 10, 0]
Edit
As per request in the comments, here a version which only maps the values for which a mapping is defined. If no mapping is defined the original value is returned. Again I've implemented both variants.
# I have added some values which do not have a mapping defined
list1 = ['a', 'b', 'c', 'a', 'c', 'd', 'a', 'b', 'z', 4, 12, 213]
mapping = {
"a": 10,
"b": 0,
"c": 20,
"d": 100,
"e": -10
}
def map_value(value):
"""
Maps value to a defined mapped value or if no mapping is defined returns the original value
:param value: value to be mapped
:return:
"""
if value in mapping:
return mapping[value]
return value
# option 1 using a list comprehension
result = [map_value(item) for item in list1]
print(result)
# another option using the built-in map()
alternative = list(map(map_value, list1))
print(alternative)
Expected output
[10, 0, 20, 10, 20, 100, 10, 0, 'z', 4, 12, 213]
[10, 0, 20, 10, 20, 100, 10, 0, 'z', 4, 12, 213]
As you can see 'z', 4, 12, 213 are not affected as for them there is no mapping defined.

How can I improve this algorithm to count the frequency of characters in a string?

In order to sort in a descending manner, the frequency of char appearance in a string, I've developed the following algorithm.
First I pass the string to a dictionary using each char as a key along with its frequency of appearance as value. Afterwards I have converted the dictionary to a descending sorted multi-dimension list.
I'd like to know how to improve the algorithm, was it a good approach? Can it be done diferently? All proposals are welcome.
#Libraries
from operator import itemgetter
# START
# Function
# String to Dict. Value as freq.
# of appearance and char as key.
def frequencyChar(string):
#string = string.lower() # Optional
freq = 0
thisDict = {}
for char in string:
if char.isalpha(): # just chars
freq = string.count(char)
thisDict[char] = freq # {key:value}
return(thisDict)
str2Dict = frequencyChar("Would you like to travel with me?")
#print(str2Dict)
# Dictionary to list
list_key_value = [[k,v] for k, v in str2Dict.items()]
# Descending sorted list
list_key_value = sorted(list_key_value, key=itemgetter(1), reverse=True)
print("\n", list_key_value, "\n")
#END
You're doing way too much work. collections.Counter counts things for you automatically, and even sorts by frequency:
from collections import Counter
s = "Would you like to travel with me?"
freq = Counter(s)
# Counter({' ': 6, 'o': 3, 'l': 3, 'e': 3, 't': 3, 'u': 2, 'i': 2, 'W': 1, 'd': 1, 'y': 1, 'k': 1, 'r': 1, 'a': 1, 'v': 1, 'w': 1, 'h': 1, 'm': 1, '?': 1})
If you want to remove the spaces from the count:
del freq[' ']
# Counter({'o': 3, 'l': 3, 'e': 3, 't': 3, 'u': 2, 'i': 2, 'W': 1, 'd': 1, 'y': 1, 'k': 1, 'r': 1, 'a': 1, 'v': 1, 'w': 1, 'h': 1, 'm': 1, '?': 1})
Also just in general, your algorithm is doing too much work. string.count involves iterating over the whole string for each character you're trying to count. Instead, you can just iterate once over the whole string, and for every letter you just keep incrementing the key associated with that letter (initialize it to 1 if it's a letter you haven't seen before). That's essentially what Counter is doing for you.
Spelling it out:
count = {}
for letter in the_string:
if not letter.isalpha():
continue
if letter not in count:
count[letter] = 1
else:
count[letter] += 1
And then to sort it you don't need to convert to a list first, you can just do it directly:
ordered = sorted(count.items(), key=itemgetter(1), reverse=True)

Remove all elements from an array python

1) The question I'm working on:
Write an algorithm for a function called removeAll which takes 3 parameters: an array of array type, a count of elements in the array, and a value. As with the remove method we discussed in class, elements passed the count of elements are stored as None. This function should remove all occurrences of value and then shift the remaining data down. The last populated element in the array should then be set to None. The function then returns the count of “valid” (i.e. non-removed) data elements left. This function should do the removal “by hand” and SHOULD NOT use the remove method.
2) Below I have what I think works for the question, but it seems inefficient and repetitive. Is there any way to simplify it?
'''
def myremove(mylist, elements, val):
for i in range(elements): # Changes all val that needs to be removed to None
if mylist[i] == val:
mylist[i] = None
for i in range(elements):
if mylist[i] is None: # Moves elements remaining left
for j in range(i, elements- 1):
mylist[j] = mylist[j + 1]
mylist[elements- 1] = None
while mylist[0] is None: # If the first element is None move left until it is not
for j in range(i, elements - 1):
mylist[j] = mylist[j + 1]
mylist[elements - 1] = None
for i in range(elements): # Counts remaining elements
if mylist[i] is None:
elements -= 1
return mylist, elements
"""
"""
# Testing the function
print(removeAll([8, 'N', 24, 16, 1, 'N'], 6, 'N'))
print(removeAll([1, 'V', 3, 4, 2, 'V'], 6, 3))
print(removeAll([0, 'D', 5, 6, 9, 'D'], 6, 'N'))
print(removeAll(['X', 'X', 7, 'X', 'C', 'X'], 6, 'X'))
"""
"""
OUTPUT
([8, 24, 16, 1, None, None], 4)
([1, 'V', 4, 2, 'V', None], 5)
([0, 'D', 5, 6, 9, 'D'], 6)
([7, 'C', None, None, None, None], 2)
"""
You can just sort the list based on whether or not the value equals the hole value.
l = [8, 'N', 24, 16, 1, 'N']
sorted(l, key=lambda x: x == 'N')
output:
[8, 24, 16, 1, 'N', 'N']
If you need None instead of the hole value in the output, use a list comprehension and then sort based on None first.
l = [i if i != 'N' else None for i in [8, 'N', 24, 16, 1, 'N']]
sorted(l, key=lambda x: x == None)
[8, 24, 16, 1, None, None]
Then all thats left is to add in the count which you can just get by counting how many elements are None and subtract that from your input parameter.
def myremove(mylist, elements, val):
ret_list = sorted([i if i != val else None for i in mylist], key=lambda x: x == None)
return ret_list, elements - ret_list.count(None)

How to aggregate string length sequence base on an indicator sequence

I have a dictionary with two keys and their values are lists of strings.
I want to calculate string length of one list base on an indicator in another list.
It's difficult to frame the question is words, so let's look at an example.
Here is an example dictionary:
thisdict ={
'brand': ['Ford','bmw','toyota','benz','audi','subaru','ferrari','volvo','saab'],
'type': ['O','B','O','B','I','I','O','B','B']
}
Now, I want to add an item to the dictionary that corresponds to string cumulative-length of "brand-string-sequence" base on condition of "type-sequence".
Here is the criteria:
If type = 'O', set string length = 0 for that index.
If type = 'B', set string length to the corresponding string length.
If type = 'I', it's when things get complicated. You would want to look back the sequence and sum up string length until you reach to the first 'B'.
Here is an example output:
thisdict ={
"brand": ['Ford','bmw','toyota','benz','audi','subaru','ferrari','volvo','saab'],
'type': ['O','B','O','B','I','I','O','B','B'],
'cumulative-length':[0,3,0,4,8,14,0,5,4]
}
where 8=len(benz)+len(audi) and 14=len(benz)+len(audi)+len(subaru)
Note that in the real data I'm working on, the sequence can be one "B" and followed by an arbitrary number of "I"s. ie. ['B','I','I','I','I','I','I',...,'O'] so I'm looking for a solution that is robust in such situation.
Thanks
You can use the zip fucntion to tie the brand and type together. Then just keep a running total as you loop through the dictionary values. This solution will support any length series and any length string in the brand list. I am assuming that len(thisdict['brand']) == len(thisdict['type']).
thisdict = {
'brand': ['Ford','bmw','toyota','benz','audi','subaru','ferrari','volvo','saab'],
'type': ['O','B','O','B','I','I','O','B','B']
}
lengths = []
running_total = 0
for b, t in zip(thisdict['brand'], thisdict['type']):
if t == 'O':
lengths.append(0)
elif t == 'B':
running_total = len(b)
lengths.append(running_total)
elif t == 'I':
running_total += len(b)
lengths.append(running_total)
print(lengths)
# [0, 3, 0, 4, 8, 14, 0, 5, 4]
Generating random data
import random
import string
def get_random_brand_and_type():
n = random.randint(1,8)
b = ''.join(random.choice(string.ascii_uppercase) for _ in range(n))
t = random.choice(['B', 'I', 'O'])
return b, t
thisdict = {
'brand': [],
'type': []
}
for i in range(random.randint(1,20)):
b, t = get_random_brand_and_type()
thisdict['brand'].append(b)
thisdict['type'].append(t)
yields the following result:
{'type': ['B', 'B', 'O', 'I', 'B', 'O', 'O', 'I', 'O'],
'brand': ['O', 'BSYMLFN', 'OF', 'SO', 'KPQGRW', 'DLCWW', 'VLU', 'ZQE', 'GEUHERHE']}
[1, 7, 0, 9, 6, 0, 0, 9, 0]

Determining if all items in a range are equal to a specific value

This question is related to the tic tac toe problem using python: Let's say I have a list - my_list = ['X', 'O', 'X', 'O', 'X', '-', 'O', 'X', 'X']. I want to determine if all the items in range(0, 2) or range(3, 5) or range(6, 8) == X So far I have tried the following, but get a syntax error:
my_list = ['X', 'O', 'X', 'O', 'X', '-', 'O', 'X', 'X']
for i in range(0, 3):
if all(board[i]) == 'X':
print('X is the winner')
elif all(board[i]) == 'Y':
print('Y is the winner')
The problem really stems from setting up the range on the second line, but I also feel I am not using the all function correctly. Can you shed light my mistake here? Side note: I also want to check to see if index items[0, 3, 6], [1, 4, 7], and [2, 5, 8]-the "columns" as well as the diagonals index[0, 4, 8] and [6, 4, 2] are all of a specific value.
Listing the winner indices explicitly works:
my_list = ['X', 'O', 'X', 'O', 'X', '-', 'O', 'X', 'X']
winner_indices = [[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [6, 4, 2]]
no_winner = True
for indices in winner_indices:
selected = [my_list[index] for index in indices]
for party in ['X', 'O']:
if all(item == party for item in selected):
print('{} is the winner'.format(party))
no_winner = False
if no_winner:
print('nobody wins')
You are not considering all winning combinations when deciding the winner.
The approach I am going to use can be used to generate winning combination grid in a generic way. That can help even if you wish to scale.
My solution uses numpy package. Install it if you don't already have.
import numpy as np
from itertools import chain
#Define size of your grid
n_dim = 3
#Input list from players
my_list = ['X', 'O', 'X', 'O', 'X', '-', 'O', 'X', 'X']
#Generate a n_dim*n_dim grid and reshape it in rows and columns
grid = np.arange(n_dim*n_dim).reshape(n_dim, n_dim)
#Get all rows and columns out from the grid as they all are winning combinations
grid_l = list(chain.from_iterable((grid[i].tolist(), grid[:,i].tolist()) for i in range(n_dim)))
#Get the forward diagonal
grid_l.append(grid.diagonal().tolist())
#Get reverse diagonal
grid_l.append(np.diag(np.fliplr(grid)).tolist())
#Check if any player's combination matches with any winning combination
result = [i for i in grid_l if all(my_list[k] == 'X' for k in i) or all(my_list[k] == 'O' for k in i)]
#result [[0,4,8]]

Resources