/**
  * Creates a mapping by merging two mappings. There must be no clashes.
  *
  * <p>Unlike {@link #append}, sources and targets are not shifted.
  *
  * <p>For example, <code>merge({0:0, 1:1}, {2:2, 3:3, 4:4})</code> yields <code>
  * {0:0, 1:1, 2:2, 3:3, 4:4}</code>. <code>merge({0:0, 1:1}, {1:2, 2:3})</code> throws, because
  * there are two entries with source=1.
  */
 public static TargetMapping merge(TargetMapping mapping0, TargetMapping mapping1) {
   final int s0 = mapping0.getSourceCount();
   final int s1 = mapping1.getSourceCount();
   final int sMin = Math.min(s0, s1);
   final int sMax = Math.max(s0, s1);
   final int t0 = mapping0.getTargetCount();
   final int t1 = mapping1.getTargetCount();
   final int tMax = Math.max(t0, t1);
   final TargetMapping mapping = create(MappingType.INVERSE_SURJECTION, sMax, tMax);
   for (int s = 0; s < sMin; s++) {
     int t = mapping0.getTargetOpt(s);
     if (t >= 0) {
       mapping.set(s, t);
       assert mapping1.getTargetOpt(s) < 0;
     } else {
       t = mapping1.getTargetOpt(s);
       if (t >= 0) {
         mapping.set(s, t);
       }
     }
   }
   for (int s = sMin; s < sMax; s++) {
     int t = s < s0 ? mapping0.getTargetOpt(s) : -1;
     if (t >= 0) {
       mapping.set(s, t);
       assert mapping1.getTargetOpt(s) < 0;
     } else {
       t = s < s1 ? mapping1.getTargetOpt(s) : -1;
       if (t >= 0) {
         mapping.set(s, t);
       }
     }
   }
   return mapping;
 }
 /** Returns whether a mapping is the identity. */
 public static boolean isIdentity(TargetMapping mapping) {
   if (mapping.getSourceCount() != mapping.getTargetCount()) {
     return false;
   }
   for (int i = 0; i < mapping.getSourceCount(); i++) {
     if (mapping.getTargetOpt(i) != i) {
       return false;
     }
   }
   return true;
 }
 /**
  * Creates a mapping by appending two mappings.
  *
  * <p>Sources and targets of the second mapping are shifted to the right.
  *
  * <p>For example,
  *
  * <pre>append({0:0, 1:1}, {0:0, 1:1, 2:2})</pre>
  *
  * yields
  *
  * <pre>{0:0, 1:1, 2:2, 3:3, 4:4}</pre>
  *
  * .
  *
  * @see #merge
  */
 public static TargetMapping append(TargetMapping mapping0, TargetMapping mapping1) {
   final int s0 = mapping0.getSourceCount();
   final int s1 = mapping1.getSourceCount();
   final int t0 = mapping0.getTargetCount();
   final int t1 = mapping1.getTargetCount();
   final TargetMapping mapping = create(MappingType.INVERSE_SURJECTION, s0 + s1, t0 + t1);
   for (int s = 0; s < s0; s++) {
     int t = mapping0.getTargetOpt(s);
     if (t >= 0) {
       mapping.set(s, t);
     }
   }
   for (int s = 0; s < s1; s++) {
     int t = mapping1.getTargetOpt(s);
     if (t >= 0) {
       mapping.set(s0 + s, t0 + t);
     }
   }
   return mapping;
 }
 /**
  * Returns a mapping that shifts a given mapping's target by a given offset.
  *
  * <p>For example, given {@code mapping} with sourceCount=2, targetCount=8, and (source, target)
  * entries {[0: 5], [1: 7]}, offsetTarget(mapping, 3) returns a mapping with sourceCount=2,
  * targetCount=11, and (source, target) entries {[0: 8], [1: 10]}.
  *
  * @param mapping Input mapping
  * @param offset Offset to be applied to each target
  * @param targetCount New target count; must be at least {@code mapping}'s target count plus
  *     {@code offset}
  * @return Shifted mapping
  */
 public static TargetMapping offsetTarget(
     final TargetMapping mapping, final int offset, final int targetCount) {
   if (targetCount < mapping.getTargetCount() + offset) {
     throw new IllegalArgumentException("new target count too low");
   }
   return target(
       new Function1<Integer, Integer>() {
         public Integer apply(Integer source) {
           int target = mapping.getTargetOpt(source);
           return target < 0 ? null : target + offset;
         }
       },
       mapping.getSourceCount(),
       targetCount);
 }
 /**
  * Returns a mapping that shifts a given mapping's source by a given offset.
  *
  * <p>For example, given {@code mapping} with sourceCount=2, targetCount=8, and (source, target)
  * entries {[0: 5], [1: 7]}, offsetSource(mapping, 3) returns a mapping with sourceCount=5,
  * targetCount=8, and (source, target) entries {[3: 5], [4: 7]}.
  *
  * @param mapping Input mapping
  * @param offset Offset to be applied to each source
  * @param sourceCount New source count; must be at least {@code mapping}'s source count plus
  *     {@code offset}
  * @return Shifted mapping
  */
 public static TargetMapping offsetSource(
     final TargetMapping mapping, final int offset, final int sourceCount) {
   if (sourceCount < mapping.getSourceCount() + offset) {
     throw new IllegalArgumentException("new source count too low");
   }
   return target(
       new Function1<Integer, Integer>() {
         public Integer apply(Integer source) {
           int source2 = source - offset;
           return source2 < 0 || source2 >= mapping.getSourceCount()
               ? null
               : mapping.getTargetOpt(source2);
         }
       },
       sourceCount,
       mapping.getTargetCount());
 }
 /**
  * Returns a mapping that shifts a given mapping's source by a given offset, incrementing the
  * number of sources by the minimum possible.
  *
  * @param mapping Input mapping
  * @param offset Offset to be applied to each source
  * @return Shifted mapping
  */
 public static TargetMapping offsetSource(final TargetMapping mapping, final int offset) {
   return offsetSource(mapping, offset, mapping.getSourceCount() + offset);
 }