human
Can you provide a the Python code you requested given the following:
I found an interesting problem on https://leetcode.com/problems/smallest-string-with-swaps/:
You are given a string s, and an array of pairs of indices in the string pairs where pairs[i] = [a, b] indicates 2 indices(0-indexed) of the string.
You can swap the characters at any pair of indices in the given pairs any number of times.
Return the lexicographically smallest string that s can be changed to after using the swaps.
 
Example 1:
Input: s = "dcab", pairs = [[0,3],[1,2]]
Output: "bacd"
Explaination: 
Swap s[0] and s[3], s = "bcad"
Swap s[1] and s[2], s = "bacd"

Example 2:
Input: s = "dcab", pairs = [[0,3],[1,2],[0,2]]
Output: "abcd"
Explaination: 
Swap s[0] and s[3], s = "bcad"
Swap s[0] and s[2], s = "acbd"
Swap s[1] and s[2], s = "abcd"
Example 3:
Input: s = "cba", pairs = [[0,1],[1,2]]
Output: "abc"
Explaination: 
Swap s[0] and s[1], s = "bca"
Swap s[1] and s[2], s = "bac"
Swap s[0] and s[1], s = "abc"

 
Constraints:

1 <= s.length <= 10^5
0 <= pairs.length <= 10^5
0 <= pairs[i][0], pairs[i][1] < s.length
s only contains lower case English letters.

I tried it in Python, but could not do it. Can you solve it?
assistant
#Here is the Python code you requested.

from collections import defaultdict

class DisjointSet:
    class Node:
        def __init__(self, x):
            self.val = x
            self.parent = self
            self.rank = 0
            
    def __init__(self, node_num):
        self.val_to_node = {}
        
        for val in range(node_num):
            self.val_to_node[val] = DisjointSet.Node(val)
            
    def find(self, x):
        return self._find(self.val_to_node[x]).val
    
    def _find(self, node):
        if node.parent is node:
            return node
        
        node.parent = self._find(node.parent)
        return node.parent
    
    def union(self, val1, val2):
        root1 = self._find(self.val_to_node[val1])
        root2 = self._find(self.val_to_node[val2])
        
        if root1 is root2:
            return
        
        if root2.rank > root1.rank:
            root1, root2 = root2, root1
            
        if root1.rank == root2.rank:
            root1.rank += 1
        
        root2.parent = root1
            

class Solution:
    def smallestStringWithSwaps(self, s: str, pairs: List[List[int]]) -> str:
        if not pairs:
            return s
        
        disjoint_set = DisjointSet(len(s))
        
        for u, v in pairs:
            disjoint_set.union(u, v)
        
        connected_components = defaultdict(list)
        
        for i in range(len(s)):
            connected_components[disjoint_set.find(i)].append(i)
        
        res = [None] * len(s)
        for group in list(connected_components.values()):
            sorted_chars = sorted([s[i] for i in group])
            
            for idx, s_i in enumerate(sorted(group)):
                res[s_i] = sorted_chars[idx]
                
        return ''.join(res)