Cálculos de rangos de números en PHP

votos
3

y antes que nada, gracias por tomarse el tiempo para leer mi pregunta.

Estoy tratando de escribir un guión, y me he encontrado con un problema que me resulta difícil de resolver. Estoy trabajando con un par de números (por ejemplo, 1000 y 2000), y tengo una matriz de pares de números:

$pairs = array(
    array(800, 1100),
    array(1500, 1600),
    array(1900, 2100)
)

Lo que estoy tratando de encontrar es cómo obtener los rangos no cubiertos por los pares de números, entre 1000 y 2000. En este ejemplo, 1000-1100 está cubierto por una matriz (800, 1100), 1500-1600 está cubierto por una matriz ( 1500, 1600) y 1900-2000 está cubierto por una matriz (1900, 2100), lo que me deja con 1101-1499 y 1599-1899 para cubrir. Espero ser lo suficientemente claro.

Lo que me pregunto es cómo haría que PHP me devolviera una variedad de rangos no cubiertos por la variable $ pairs. En este ejemplo, volvería:

array(
    array(1101, 1499),
    array(1599, 1899)
)

¿Tienes alguna idea de cuál sería la mejor manera de hacer esto?

Gracias de antemano.

Publicado el 29/11/2009 a las 23:29
fuente por usuario
En otros idiomas...                            


3 respuestas

votos
3

Bueno, primero tienes que definir el problema:

  1. ¿Están los pares ordenados?
  2. ¿Los pares se superponen?
  3. ¿Desea encontrar los rangos faltantes para un rango particular (este parece ser el caso)?

Si los pares no están ordenados, primero ordénelos:

usort($pairs, 'cmp_pair');

function cmp_pair($a, $b) {
  if ($a[0] == $b[0]) {
    if ($a[1] == $b[1]) {
      return 0;
    } else {
      return $a[1] < $b[1] ? -1 : 1;
    }
  } else {
    return $a[0] < $b[0] ? -1 : 1;
  }
}

Si se permiten rangos superpuestos, transfiera la lista de pares a un conjunto no superpuesto. Aquí hay una sugerencia sobre cómo hacer eso:

$prev = false;
$newpairs = array();
foreach ($pairs as $pair) {
  if ($prev) {
    // this also handles the case of merging two ranges
    // eg 100-199 with 200 to 250 to 100-250
    if ($prev[1] >= $pair[0]-1) {
      $prev = array($prev[0], max($prev[1], $pair[1]));
    } else {
      $newpairs[] = $prev;
    }
  }
  $prev = $pair;
}
$pairs = $newpairs;

Ahora no debería haber pares superpuestos, por lo que el problema se vuelve un poco más simple ya que también tienes una matriz ordenada.

function missing($start, $end, $pairs) {
  $missing = array();
  $prev = false;
  foreach ($pairs as $pair) {
    // if the current pair starts above the end, we're done
    if ($pair[0] > $end) {
      break;
    }

    // we can ignore any pairs that end before the start
    if ($pair[1] < $start) {
      continue;
    }

    // if the pair encompasses the whole range, nothing is missing
    if ($pair[0] <= $start && $pair[1] >= $end) {
      break;
    }

    // if this is our first overlapping pair and it starts above
    // the start we can backfill the missing range
    if ($pair[0] > $start && !$missing) {
      $missing[] = array($start, $pair[0]);
    }

    // compare this pair to the previous one (if there is one) and
    // fill in the missing range
    if ($prev) {
      $missing[] = array($prev[1]+1, $pair[0]-1);
    }

    // set the previous
    $prev = $pair;
  }

  // if this never got set the whole range is missing
  if (!$prev) {
    $missing[] = array($start, $end);

  // if the last overlapping range ended before the end then
  // we are missing a range from the end of it to the end of
  // of the relevant range
  } else if ($prev[1] < $end) {
    $missing[] = array($prev[1]+1, $end);
  }

  // done!
  return $missing;
}

Espero que ayude.

Respondida el 29/11/2009 a las 23:39
fuente por usuario

votos
0
// your original arrays of integers
$pairs = array(
    array(800, 1100),
    array(1500, 1600),
    array(1900, 2100)
);

// first, normalize the whole thing into a giant list of integers that
// are included in the array pairs, combine and sort numerically
$numbers_in_pairs = array();
foreach($pairs as $set) {
    $numbers_in_pairs = array_merge($numbers_in_pairs, range($set[0], $set[1]));
}
sort($numbers_in_pairs);

// find the min
$min = $numbers_in_pairs[0];

// find the max
$max = $numbers_in_pairs[count($numbers_in_pairs)-1];

Encuentra la diferencia de array

// create an array of all numbers inclusive between the min and max
$all_numbers = range($min, $max);

// the numbers NOT included in the set can be found by doing array_diff
// between the two arrays, we need to sort this to assure no errors when
// we iterate over it to get the maxes and mins
$not_in_set = array_diff($all_numbers, $numbers_in_pairs);
sort($not_in_set);

Metadatos sobre los conjuntos que usaremos más tarde:

// gather metadata about the numbers that are not inside the set
// $not_in_set_meta['min'] = lowest integer
// $not_in_set_meta['max'] = highest integer
// $not_in_set_meta['mins'] = min boundary integer
// $not_in_set_meta['maxes'] = max boundary integer
$not_in_set_meta = array();
for($i=0;$i<count($not_in_set);$i++) {
    if ($i == 0) {
        $not_in_set_meta['min'] = $not_in_set[$i];
        $not_in_set_meta['mins'][] = $not_in_set[$i];
    } else if ($i == count($not_in_set)-1 ) {
        $not_in_set_meta['max'] = $not_in_set[$i];
        $not_in_set_meta['maxes'][] = $not_in_set[$i];
    } else {
        // in the event that a number stands alone
        // that it can be BOTH the min and the max
        if (($not_in_set[$i+1] - $not_in_set[$i]) > 1) {
            $not_in_set_meta['maxes'][] = $not_in_set[$i];
        }
        if (($not_in_set[$i] - $not_in_set[$i-1]) > 1) {
            $not_in_set_meta['mins'][] = $not_in_set[$i];
        }
    }
}

Salida final:

// The final variable which we'll dump the ranges not covered into:
$non_sets = array();

while(count($not_in_set_meta['mins']) > 0 && count($not_in_set_meta['maxes'])) {
    $non_sets[] = array(array_shift($not_in_set_meta['mins']), 
                        array_shift($not_in_set_meta['maxes']));
}
// print it out:
print var_export($non_sets);

Resultado:

array (
  0 => 
  array (
    0 => 1101,
    1 => 1499,
  ),
  1 => 
  array (
    0 => 1601,
    1 => 1899,
  ),
)

?>
Respondida el 30/11/2009 a las 14:50
fuente por usuario

votos
0

Haría algo así:

begin = 1000
end   = 2000
uncovered = ()
foreach pairs as pair
  if (pair[0] > begin)
    push (uncovered, begin, pair[0])
    begin = pair[1]
  end if
end foreach

Esto es solo una idea, pero este es el punto: Considera que tienes un gran segmento (de 1000 a 2000) y uno pequeño. Desea obtener cada uno de los segmentos del grande que no están cubiertos por el pequeño. ¡Imagina que tienes un bolígrafo!

Inicia el comienzo. Itera en cada "pequeño segmento" que tiene. Si busca (estrictamente) el comienzo, entonces hay un "agujero", por lo que debe memorizar desde el comienzo hasta el comienzo del segmento actual.

Espero que esto ayude, ¡y que esto sea correcto!

Respondida el 29/11/2009 a las 23:39
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more