code: def split_train_dev_v2(
      all_data, num_dev, outfile, allowed_deviation=0, max_solutions=50
  ):
      """Group all paths listed in args.datalist according to their name prefix,
      and split all groups into train and dev subsets.
  
      Each subset has distinct groups.
  
      This is designed for splitting similar-size groups into train and dev subsets.
      That is, all groups must have, if not the same, a similar number of paths.
  
      Args:
          all_data (Dict[group_name: List[str]]): all paths grouped by their category
          num_dev (int or Fraction): number/percentage of the samples for the dev set
          outfile (str): template path to the ourput file
          allowed_deviation (int): Allowed number of samples for the final dev split to be
                                  less than or more than the specified `num_dev`
          max_solutions (int): maximum number of possible coin change solutions to search
      """  # noqa: H405
  
      lengths = [len(v) for v in all_data.values()]
      total_length = sum(lengths)
      mean_length = total_length / len(lengths)
      print(
          f"len(group_lengths)={len(lengths)}\n"
          f"max(group_lengths)={max(lengths)}, min(group_lengths)={min(lengths)}, "
          f"mean(group_lengths)={mean_length:.2f}\n"
      )
  
      # determine number of groups for dev subset
      if isinstance(num_dev, int):
          num_dev_samples = num_dev
      elif isinstance(num_dev, Fraction):
          num_dev_samples = num_dev * total_length
          if num_dev_samples.denominator == 1:
              num_dev_samples = num_dev_samples.numerator
          else:
              num_dev_samples = round(num_dev_samples)
              print(
                  "Warning: num_dev_samples is rounded to the nearest integer "
                  f"({num_dev_samples})."
              )
      else:
          raise TypeError("Unsupported data type: %s" % type(num_dev))
  
      # Solve this assignment problem like the recursive Coin Change Problem
      choices = find_all_coin_change_ways(
          lengths,
          num_dev_samples,
          allowed_deviation=allowed_deviation,
          max_solutions=max_solutions,
      )
      if len(choices) == 0:
          raise ValueError(
              "Current find an exact solution to match num_dev_samples (=%d)\n"
              "Please modify --num_dev or consider using a larger value for "
              "--allowed_deviation" % num_dev_samples
          )
  
      choice = random.choice(choices)
      groups_count = Counter(choice)
      pool = defaultdict(list)
      groups = list(all_data.keys())
      for i, length in enumerate(lengths):
          if length in groups_count:
              pool[length].append(i)
  
      selected_idx = []
      dev_groups = []
      for length, num in groups_count.items():
          name_idxs = random.sample(pool[length], num)
          selected_idx.extend(name_idxs)
          for idx in name_idxs:
              dev_groups.append(groups[idx])
      for idx in sorted(selected_idx, reverse=True):
          groups.pop(idx)
      train_groups = groups
  
      outdir = Path(outfile).expanduser().resolve().parent
      outdir.mkdir(parents=True, exist_ok=True)
      assert "{}" in outfile, outfile
      with Path(outfile.format("dev")).open("w") as out:
          for room in dev_groups:
              for wav in all_data[room]:
                  out.write(wav)
      with Path(outfile.format("train")).open("w") as out:
          for room in train_groups:
              for wav in all_data[room]:
                  out.write(wav)
  
file_path: espnet/egs2/aishell4/enh1/local/split_train_dev.py
imports: import math
  import random
  from collections import Counter, defaultdict
  from fractions import Fraction
  from pathlib import Path
  from typing import List, Tuple
label_desc: Use image node_id 3 for calling a global function with example usage: split_train_dev_v2(all_data, num_dev, outfile, allowed_deviation, max_solutions) without return types