2008-10-27 10 views
28

Todos los números que se dividen de manera uniforme en x.La mejor manera de encontrar todos los factores de un número determinado

puse en 4 vuelve: 4, 2, 1

edición: Sé que suena homeworky. Estoy escribiendo una pequeña aplicación para completar algunas tablas de productos con datos de prueba semialeatorios. Dos de las propiedades son ItemMaximum y Item Multiplier. Necesito asegurarme de que el multiplicador no cree una situación ilógica donde comprar 1 artículo más pondría la orden por encima del máximo permitido. Por lo tanto, los factores darán una lista de valores válidos para mis datos de prueba.

edición ++: Esto es lo que fui con toda la ayuda de todos. ¡Gracias de nuevo!

Editar #: Escribí 3 versiones diferentes para ver cuál me gustaba más y las comprueve contra factorizar números pequeños y números muy grandes. Pegaré los resultados.

static IEnumerable<int> GetFactors2(int n) 
{ 
    return from a in Enumerable.Range(1, n) 
        where n % a == 0 
        select a;      
} 

private IEnumerable<int> GetFactors3(int x) 
{    
    for (int factor = 1; factor * factor <= x; factor++) 
    { 
     if (x % factor == 0) 
     { 
      yield return factor; 
      if (factor * factor != x) 
       yield return x/factor; 
     } 
    } 
} 

private IEnumerable<int> GetFactors1(int x) 
{ 
    int max = (int)Math.Ceiling(Math.Sqrt(x)); 
    for (int factor = 1; factor < max; factor++) 
    { 
     if(x % factor == 0) 
     { 
      yield return factor; 
      if(factor != max) 
       yield return x/factor; 
     } 
    } 
} 

In ticks. cuando se toma el número 20, 5 veces cada uno:

  • GetFactors1-5,445,881
  • GetFactors2-4,308,234
  • GetFactors3-2,913,659

cuando se toma el número 20000, 5 veces cada uno:

  • GetFactors1-5,644,457
  • GetFactors2-12,117,938
  • GetFactors3-3,108,182
+1

Usted sabe, espero, que no existe una solución general de alto rendimiento conocida por su problema. La criptografía moderna se basa en que no existe tal solución. –

+0

Sí, pero no estaba seguro de si había una forma mejor de hacerlo que simplemente probando los números uno por uno, ha pasado un tiempo desde que asistí a una clase de matemáticas. – Echostorm

+0

pregunta relacionada en [python] (http://stackoverflow.com/q/3643725/6899) ​​ – tzot

Respuesta

32

pseudocódigo:

  • bucle de 1 a la raíz cuadrada del número, llame al índice "i".
  • si el número mod i es 0, agregue i y number/i a la lista de factores.

realocode:

public List<int> Factor(int number) { 
    List<int> factors = new List<int>(); 
    int max = (int)Math.Sqrt(number); //round down 
    for(int factor = 1; factor <= max; ++factor) { //test from 1 to the square root, or the int below it, inclusive. 
     if(number % factor == 0) { 
      factors.Add(factor); 
      if(factor != number/factor) { // Don't add the square root twice! Thanks Jon 
       factors.Add(number/factor); 
      } 
     } 
    } 
    return factors; 
} 

Como se mencionó Jon Skeet, podría implementar esto como una IEnumerable<int> así - yield utilizar en lugar de añadir a una lista. La ventaja con List<int> es que se puede ordenar antes de devolver si es necesario. Por otra parte, podría obtener un enumerador ordenado con un enfoque híbrido, obteniendo el primer factor y almacenando el segundo en cada iteración del ciclo, y luego arrojando cada valor almacenado en orden inverso.

También querrá hacer algo para manejar el caso en el que un número negativo pasó a la función.

+0

Una comprobación adicional para agregar: lo anterior agregue 2 dos veces :) –

+0

Math.Sqrt devuelve un doble. Además, eso debe redondearse. Intenta usar 20 como ejemplo. – Echostorm

+0

En lugar de tomar la raíz cuadrada, puede reestructurar el bucle: for (int factor = 1; factor * factor <= number; ++ factor) –

19

El operador % (resto) es el que debe utilizarse aquí. Si x % y == 0, entonces x es divisible por y. (Asumiendo 0 < y <= x)

Personalmente implementaría esto como un método que devuelve un IEnumerable<int> usando un bloque iterador.

+0

'%' es en realidad el [operador de módulo] (https://msdn.microsoft.com/en-us/library/h6zfzfy7%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396), no " recordatorio". El bloque 'IEnumerable ' tiene un límite superior que lo hace práctico para factorizar números 'pequeños' (computacionales). –

+0

@SamuelJackson: la especificación de lenguaje C# 5 sección 5.8.3 y [publicación de blog de Eric Lippert] (https://blogs.msdn.microsoft.com/ericlippert/2011/12/05/whats-the-difference-remainder-vs -modulus /) en desacuerdo. Su referencia de MSDN es para JScript, no para C#. –

+0

@SamuelJackson: (Y dado que esa página de MSDN habla sobre "El módulo, o el resto, operador", se ve un poco roto de todos modos, como si creyera que los dos son equivalentes ...) –

-2

solución LINQ:

IEnumerable<int> GetFactors(int n) 
{ 
    Debug.Assert(n >= 1); 
    return from i in Enumerable.Range(1, n) 
     where n % i == 0 
     select i; 
} 
+0

Esto está mal, solo devuelve la mitad de ellos. –

+0

Esto solo le proporciona la primera 1/2 de los factores. Por ejemplo, para 10, devolvería 1 y 2, pero no 5 y 10. –

+0

Cambio sugerido: Math.Sqrt devolverá un doble, lo que no funcionará con Enumerable.Range. Además, eso no devolverá 4: solo 1 y 2. –

4

Como métodos de extensión:

public static bool Divides(this int potentialFactor, int i) 
    { 
     return i % potentialFactor == 0; 
    } 

    public static IEnumerable<int> Factors(this int i) 
    { 
     return from potentialFactor in Enumerable.Range(1, i) 
       where potentialFactor.Divides(i) 
       select potentialFactor; 
    } 

Aquí hay un ejemplo de uso:

 foreach (int i in 4.Factors()) 
     { 
      Console.WriteLine(i); 
     } 

Tenga en cuenta que he optimizado para una mayor claridad, no para el rendimiento. Para valores grandes de i, este algoritmo puede llevar mucho tiempo.

1

Aquí está de nuevo, solo contando a la raíz cuadrada, como otros mencionaron. Supongo que la gente se siente atraída por esa idea si espera mejorar el rendimiento. Prefiero escribir un código elegante primero, y optimizar el rendimiento más adelante, después de probar mi software.

Sin embargo, para referencia, aquí está:

public static bool Divides(this int potentialFactor, int i) 
    { 
     return i % potentialFactor == 0; 
    } 

    public static IEnumerable<int> Factors(this int i) 
    { 
     foreach (int result in from potentialFactor in Enumerable.Range(1, (int)Math.Sqrt(i)) 
           where potentialFactor.Divides(i) 
           select potentialFactor) 
     { 
      yield return result; 
      if (i/result != result) 
      { 
       yield return i/result; 
      } 
     } 
    } 

No sólo es el resultado considerablemente menos legible, pero los factores que vienen fuera de esta manera, también.

+0

¿Por qué no edita la respuesta anterior? –

+1

Porque son dos respuestas diferentes, con diferente mérito. –

0

¿No tendría sentido también comenzar en 2 y dirigirse hacia un valor límite superior que continuamente se recalcula según el número que acaba de marcar? Vea N/i (donde N es el número que está tratando de encontrar el factor de y i es el número actual para verificar ...) Idealmente, en lugar de mod, usaría una función de división que también devuelve N/i como cualquier resto que pueda tener. De esa forma estás realizando una operación dividida para recrear tu límite superior, así como el resto verificará la división par.

Math.DivRem http://msdn.microsoft.com/en-us/library/wwc1t3y1.aspx

2

Otro estilo de LINQ y atar para mantener la junta (sqrt (n)) la complejidad

 static IEnumerable<int> GetFactors(int n) 
     { 
      Debug.Assert(n >= 1); 
      var pairList = from i in Enumerable.Range(1, (int)(Math.Round(Math.Sqrt(n) + 1))) 
        where n % i == 0 
        select new { A = i, B = n/i }; 

      foreach(var pair in pairList) 
      { 
       yield return pair.A; 
       yield return pair.B; 
      } 


     } 
12

muy tarde, pero la respuesta aceptada (un tiempo) no lo hicieron no dar los resultados correctos.

Gracias a Merlyn, ahora obtuve el motivo del cuadrado como 'máximo' debajo de la muestra corregida. aunque la respuesta de Echostorm parece más completa.

public static IEnumerable<uint> getFactors(uint x) 
    { 
     for (uint i = 1; i*i <= x; i++) 
     { 
      if (0 == (x % i)) 
      { 
       yield return i; 
       if (i != (x/i)) 
       { 
        yield return x/i; 
       } 
      } 
     } 
    } 
+1

Necesita agregar lo siguiente después del ciclo for para devolver x mismo. Un número es siempre un factor de sí mismo. 'yield return x;' – SFun28

+0

Creo que cualquier cosa que haya estado mal antes es correcta ahora. La razón por la que se detiene en la raíz cuadrada es porque ya ha pasado por los posibles factores debajo de la raíz cuadrada (sería necesario para multiplicar por cualquier número por encima de "máximo"), así como por cualquier número por encima de la raíz cuadrada (dado que se emite a sí mismo y al número por el que se multiplica para obtener el resultado). El valor máximo posible que aún no se emitiría al generar ambos lados del par en cada iteración de bucle es la raíz cuadrada. –

+1

Por ejemplo, si obtiene los factores de 81, las iteraciones de bucle entre 10 y 40 se desperdician. Ya sacaste 27 cuando sacaste 3, y si no obtuviste ambos lados, nunca obtendrías 81 como factor si te detuvieras en 81/2 (40). –

0

Si usa dobles, las siguientes opciones funcionan: utilice un bucle for para iterar desde 1 hasta el número que desee factorizar. En cada iteración, divida el número que será factorizado por i. Si (número/i)% 1 == 0, entonces yo es un factor, como lo es el cociente de número/i. Coloque uno o ambos en una lista, y usted tiene todos los factores.

1

Lo hice de la manera perezosa. No sé mucho, pero me han dicho que la simplicidad a veces puede implicar elegancia. Esta es una forma posible de hacerlo:

public static IEnumerable<int> GetDivisors(int number) 
    { 
     var searched = Enumerable.Range(1, number) 
      .Where((x) => number % x == 0) 
      .Select(x => number/x); 

     foreach (var s in searched)   
      yield return s; 
    } 

EDITAR: Como Kraang Primer señaló, esta función no puede exceder el límite de un número entero y es (la verdad) no es la forma más eficiente de manejar este problema.

+0

Esto solo puede procesar factores hasta el límite del número entero. –

+0

@spencer Solo curiosidad por saber por qué está utilizando yield return en un ciclo foreach ya que tiene un IEnumerable ¿por qué no devuelve el resultado de su consulta Linq en lugar de asignarlo a la búsqueda? –

Cuestiones relacionadas