def range_union(ranges):
    """
    Returns total size of ranges, expect range as (chr, left, right)

    >>> ranges = [("1", 30, 45), ("1", 40, 50), ("1", 10, 50)]
    >>> range_union(ranges)
    41
    >>> ranges = [("1", 30, 45), ("2", 40, 50)]
    >>> range_union(ranges)
    27
    >>> ranges = [("1", 30, 45), ("1", 45, 50)]
    >>> range_union(ranges)
    21
    >>> range_union([])
    0
    """
    if not ranges:
        return 0

    ranges.sort()

    total_len = 0
    cur_chr, cur_left, cur_right = ranges[0]  # left-most range
    for r in ranges:
        # open new range if left > cur_right or chr != cur_chr
        if r[1] > cur_right or r[0] != cur_chr:
            total_len += cur_right - cur_left + 1
            cur_chr, cur_left, cur_right = r
        else:
            # update cur_right
            cur_right = max(r[2], cur_right)

    # the last one
    total_len += cur_right - cur_left + 1

    return total_len