docs: def cond_mi(x, y, z):
      '''
      compute and return the mutual information between x and y given z, I(x, y | z)
  
      inputs:
      -------
          x, y, z:   iterables with discrete symbols
  
      output:
      -------
          mi:     float
  
      implementation notes:
      ---------------------
          I(x, y | z) = H(x | z) - H(x | y, z)
                      = H(x, z) - H(z) - ( H(x, y, z) - H(y,z) )
                      = H(x, z) + H(y, z) - H(z) - H(x, y, z)
      '''
      # dict.values() returns a view object that has to be converted to a list before being converted to an array
      probXZ = symbols_to_prob(combine_symbols(x, z)).prob()
      probYZ = symbols_to_prob(combine_symbols(y, z)).prob()
      probXYZ =symbols_to_prob(combine_symbols(x, y, z)).prob()
      probZ = symbols_to_prob(z).prob()
  
      return entropy(prob=probXZ) + entropy(prob=probYZ) - entropy(prob=probXYZ) - entropy(prob=probZ)