[Duda C#]Suma y Resta en un String

Iniciado por Reent, 26 Febrero 2020, 11:48 AM

0 Miembros y 2 Visitantes están viendo este tema.

Reent

Hola a todos!

Quiero hacer una suma y resta en un solo string. Solo que la idea que tengo por el momento solo me deja hacer una de las 2 cosas...

Código (csharp) [Seleccionar]

public void OnNumberPressed(object param)
       {
         
           if (param.ToString() == "c")
           {
               Input = "";
           }
           else if (param.ToString() == "=")
           {
               string[] res = Input.Split(new char[] { '+', '-' }, StringSplitOptions.RemoveEmptyEntries);
           }
           else
           {
               Input += param.ToString();
           }

           if (param.ToString() == "+")//aqui quiero que los numeros positivos se separen en un char array
           {

           }
           else if (param.ToString() == "-")// Lo mismo aqui
           {

           }
       }


ejemplo: "10+20-13+10"
char[] numPos {10,20,10};
char[] numNeg {13};

Si alguien tiene alguna idea, me vendria bien una mano.

Saludos!

[MOD] Usar la etiqueta GeSHi adecuada.

ThunderCls

En estos casos las expresiones regulares son tus aliadas

Código (perl) [Seleccionar]
(?<positivos>[^-\w\s]\d*)|(?<negativos>[^+\w\s]\d*)

En el grupo "positivos" tendrias los numeros positivos y en el grupo "negativos" los negativos

Suerte
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

Reent

Gracias al mod!! no sabia que esa funcion existia.

@Thunder

tu idea la entendi pero tu codigo no.

ThunderCls

Cita de: Reent en 27 Febrero 2020, 14:26 PM
tu idea la entendi pero tu codigo no.

Solo te he pasado la expresion regular que debes usar, todo lo que tienes que hacer es usarla en tu lenguaje

https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex?view=netframework-4.8
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

EdePC

- Wow, me he hecho líos para realizarlo usando RegEx y Split, en mis libros de Visual Basic .NET no hay nada de Expresiones Regulares, he tenido que echar mano de mis libros de C# donde se toca el tema en detalle. De momento solo tengo Visual Basic .NET 2005 y lo he implementado así:

Código (vbnet) [Seleccionar]
Imports System.Text.RegularExpressions

Public Class Form1

  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn.Click
    Dim rx As New Regex("(?<negativos>-\d+)|(?<positivos>\d+)")
    Dim positivos As String = ""
    Dim negativos As String = ""
    For Each match As Match In rx.Matches(txt.Text)
      If match.Groups("positivos").Value <> "" Then
        positivos = positivos & "," & match.Value
      Else
        negativos = negativos & "," & match.Value
      End If
    Next
    Dim numPos() As String = positivos.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
    Dim numNeg() As String = negativos.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
  End Sub
End Class


- He usado una RegEx más corta, me parece que le basta ..., luego voy concatenando una String para los positivos y otra para los negativos, unidas mediante una "coma", al final se usa esa misma coma para hacerle un Split quitando las vacias.

- Tengo entendido que pasarlo a C# es bastante sencillo, haber si me llega a descargar el C# 2005 para mañana y pasarlo si es que aún hay dudas.

**Aincrad**

Cita de: EdePC en 27 Febrero 2020, 22:36 PM
- Tengo entendido que pasarlo a C# es bastante sencillo, haber si me llega a descargar el C# 2005 para mañana y pasarlo si es que aún hay dudas.

Siempre puedes usar el conversor de Telerik Code Converter

Nunca falla, yo mismo he pasado codigos de C# a vb . y funcionan correctamente.

Codigo Convertido :

Código (csharp) [Seleccionar]
using System;
using System.Text.RegularExpressions;

public class Form1
{
    private void Button1_Click(System.Object sender, System.EventArgs e)
    {
        Regex rx = new Regex(@"(?<negativos>-\d+)|(?<positivos>\d+)");
        string positivos = "";
        string negativos = "";
        foreach (Match match in rx.Matches(txt.Text))
        {
            if (match.Groups["positivos"].Value != "")
                positivos = positivos + "," + match.Value;
            else
                negativos = negativos + "," + match.Value;
        }
        string[] numPos = positivos.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
        string[] numNeg = negativos.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
    }
}




ThunderCls

#6
Cita de: EdePC en 27 Febrero 2020, 22:36 PM
- He usado una RegEx más corta, me parece que le basta ..., luego voy concatenando una String para los positivos y otra para los negativos, unidas mediante una "coma", al final se usa esa misma coma para hacerle un Split quitando las vacias.

Tienes razon, la expresion se puede simplificar. Algun motivo por el que no usas un List/ArrayList directamente? En mi opinion la concatenacion/split de cadenas estaria de mas.

Código (csharp) [Seleccionar]
public int ExtractNumbersFromString(string input, ref ArrayList positives, ref ArrayList negatives)
{
    Regex rx = new Regex(@"(?<positivos>\d+)|(?<negativos>-\d+)");
    MatchCollection matches = rx.Matches(input);
    foreach (Match match in matches)
    {
        if (!string.IsNullOrEmpty(match.Groups["positivos"].Value))
        {
            positives.Add(match.Value);
        }
        else
        {
            negatives.Add(match.Value);
        }
    }

    return matches.Count;
}


Saludos

Edit: Supongo que seria mas util devolver la cantidad de matches en lugar de un bool  :silbar:
-[ "...I can only show you the door. You're the one that has to walk through it." – Morpheus (The Matrix) ]-
http://reversec0de.wordpress.com
https://github.com/ThunderCls/

Serapis

#7
Es mucho más potente (y sobre todo cómodo cuando la gente no domina las expresiones regulares), realizar un automáta que haga justo la parte que se precisa...

Podremos reconocer algo tan complejo como:  " 22  -134 +45 - 087 +170 voy a beber +44j82k"
cuya salida sería: 22 -134 +45 -87 +170 +44 +82 = lo que sume
" +  232"= 232
"+23" = 23
"23   " = 23
"  23"  = 23
" 2323tf" = 2323
" ++++23" = 23
" +-+-+23" = 23
" +-+-23" = -23
" --- 23ffhj"= -23
" 23.34" = +23  +34 (OJO: no se ha contemplado reconocer decimales, ni con punto ni con coma).
...etc... con negativos...

...cosa que para una expresión regular sería más complejo, dada una falta de uniformidad en el texto...
Nótese que si hay números en el ejemplo que no debieran ser reconocidos, debe ser expresado en las reglas más abajo de la forma conveniente... Como es un ejemplo, no voy a abordarlo en profundidad, lo dejo simple pero que se entienda y el interesado que lo amplíe a sus necesidades. Es decir, no considero 3 casos: decimales ni separadores de miles, tampoco otras bases numéricas que la decimal, una vez entendido el ejemplo, no debería resultar complicado al interesado ampliarlo para admitir tales casos.

Las reglas para número son (uno debe ternerlas claro y hacer los cambos precisos):
regla "=" componentes     // comentario
--------------------------------------------
entero = [signo] [spcs] digitos   // el signo es opcional y puede haber o no espacios entre el signo y los digitos
signo = (positivo|negativo)
positivo = "+"     // en ausencia de '-' se entenderá que es positivo.
negativo = "-"
spcs = espacio [spcs]      // uno o más espacios
espacio = " "
digitos = digito [digitos]    // uno o más dígitos
digito = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9" // los digitos decimales, aunque si fuera preciso podrían separararse por bases numéricas, y reconocer números hexadecimales, binarios, octal, etc...

* Como es un ejemplo, no contemplo números con signo (en el pseudocódigo, aquí sería modificar numero y reconocer el caracter separador del decimal. Se ha elegido como separador decimal, la comilla simple como usamos en español (cambiese al gusto).
 numero =  [signo][spcs] digitos  ["'" digitos]   // un número es un entero o un decimal.

* Tampoco contemplo separadores de miles, con las mismas consideraciones que para decimales, se ha elgido el punto como se acostumbra a usar en español:
 entero = [signo][spcs] gruposDig
 gruposDig = primeGrpDig [gruposDigitos]
 primeGrpDig = digito [[digito] digito]      //esto es 1, 2 ó 3 digitos
 gruposDigitos = grupoDigitos [gruposDigitos]   // uno o más grupos de 3 digitos
 grupoDigitos = ["."] digito digito digito   // 3 digitos antecedidos o no, por un punto.

El peudocodigo pués empieza por una enumeración y luego un array que contenga a dicha enumeración.

enumeracion TokenDeNumero  //tipo byte
   TOKEN_DESCONOCIDO = 0
   TOKEN_SEP_ESPACIO = 1
   TOKEN_DIGITO = 2
   '
   TOKEN_SIGNO = 4                  // para no disitnguir positivo de negativo, si no solo un signo.
   TOKEN_SIGNO_POS = 5
   TOKEN_SIGNO_NEG = 6
   // otros token separadores que se quieran considerar.
fin enumeracion

Array CharsToken(0 a 255)  // uno por cada byte...

funcion Inicializar
   byte k

   CharsToken(32) = TOKEN_SEP_ESPACIO   // el espacio es el 32, el espacio duro el 160, aquí se ignora...

   bucle para k desde 48 a 57
       CharsToken(k) = TOKEN_DIGITO         // dígitos del 0 al 9.
   siguiente

   CharsToken(43) = TOKEN_SIGNO_POS
   CharsToken(45) = TOKEN_SIGNO_NEG
   
   // y eso es todo, el resto de caracteres son ignorados... tienen valor 0 en el array.
fin funcion


Ahora las funciones de reconocimeinto del número:

// Si devuelve true, valor contiene un número en caso contrario, se debe ignorar
Buleano = Funcion SiguienteToken(arraybytes Data(), porreferencia entero Indice, porreferencia entero Valor)
   TokenDeNumero tkn
   buleano negativo  // false de entrada

   Si SaltarEspaciosEIgnorarDesconocidos(data, indice) = FALSE devolver FALSE

   tkn = CharsToken(data(indice))
   Hacer mientras ( tkn  AND TOKEN_SIGNO)
       si (tkn AND TOKEN_SIGNO) luego
           negativo  = (tkn = TOKEN_SIGNO_NEG) //true si el signo es negativo
           indice += 1
           Si SaltarEspacios(data, indice) = FALSE devolver FALSE  
           tkn = CharsToken(data(indice))
           si (tkn = TOKEN_DIGITO)
               salir del bucle
           sino
               negativo  = FALSE    // se invalida el resultado previo, hay caracteres desconodicos tras el signo que se encontró.
               Si SaltarEspaciosEIgnorarDesconocidos(data, indice) = FALSE devolver FALSE  
           fin si
       fin si
   repetir   ' es un  bucle porque podría ser falsos positivos, casos como: "+   qwrt +asd -zxc ... pero saltarían aquí +   23"
   
   ' si no es negativo, se asume que es positivo.
   si (negativo = TRUE)
        valor = - Data(indice) // [b]ojo: si el valor es 0[/b]... dejo a tu esfuerzo resolver el caso.
   Sino
        valor = Data(indice)
   fin si
   
   si (indice < Data.Length)
       indice += 1
       Hacer mientras (data(indice) = TOKEN_DIGITO)
           indice += 1
           valor *= 10 + data(indice)
           si (indice = Data.Length) Salir del bucle
       repetir
   fin si

   Devolver TRUE  //y valor contiene el valor correcto, si devuelve false, se ignora el valor que contenga...
fin funcion

// Esta función y la siguiente son casi iguales, y no es baladí asignar el valor 0 a desconocidos y 1 al espacio...
// solo salta espacios,
//  Este caso está permitido solo entre signo y digitos: entero = [signo][spcs] digitos
Buleano = Funcion SaltarEspacios(arraybytes Data(), porreferencia entero Indice)
   Hacer mientras (CharsToken(Data(indice)) =  TOKEN_SEP_ESPACIO)
       indice += 1
       si (indice = Data.Length) Devolver FALSE   //final del array salimos d ela función.
   repetir
   
   Devolver TRUE
fin funcion

// esta funcion ignora espacios y caracteres desconocidos.
//  Este caso está permitido cuando termina un número y hasta que comience otro.
Buleano = Funcion SaltarEspaciosEIgnorarDesconocidos(arraybytes Data(), porreferencia entero Indice)
   Hacer mientras (CharsToken(Data(indice)) <= TOKEN_SEP_ESPACIO)
       indice += 1
       si (indice = Data.Length) Devolver FALSE   //final del array salimos de la función.
   repetir

   Devolver TRUE
fin funcion


y finalmente la función que interesada en ello:


entero = funcion ParseStringToSumasYRestas(arrayBytes Data() )
   entero total, valor, indice
   
   Hacer mientras SiguienteToken(Data, Indice, valor ) = TRUE
       total += valor
       indice +=1
       si (indice = Data.Length) salir dle bucle
   repetir      
   
   Devolver total
fin funcion


Esto siempre será más veloz que una expresión regular, además recoge, casos muy distintos de lo que se considera números, que contemplarlos en una expresión regular complicacría y por tanto ralentizaría su resultado. Claro que usarlo o no, depende de si es muy necesario o no, a veces un código menos eficiente (sin llegar a ser deficiente), expresado en 4 líeas e spreferible a otro más eficiente exresado en 200 líneas:
" +  232"=232
"+23" = 23
"23   " = 23
"  23"  = 23
" 2323tf" = 2323
" ++++23" = 23
" +-+-+23" = 23
" +-+-23" = -23
" --- 23ffhj"= -23
" 23.34" = +23  +34 (OJO: no se ha contemplado reconocer decimales, ni con punto ni con coma).
...etc... con negativos...

Por último, notar que en el pseudocódigo tampoco se ha contemplado la posibiidad de desbordamiento, evidentemente un número declarado de tipo entero, dará desbordamiento si se intenta parsear con "987654321987654321987654321".
Se supone que son valores coherentes 'sumables', en un rango fijado a ser aceptado en el tipo de datos numérico apropiado.
Si fuera el caso, podría llevarse una cuenta de los dígitos que se van hallando y cuando se alcance uno más de lo permitido devolver un error... si se contempla posibles errore,s la funció debería llamarse TryParse... y no Parse...


p.d.: Añado nota, para despistados: "// ojo: si el valor es 0... dejo a tu esfuerzo resolver el caso."

**Aincrad**

@NEBIRE eres el dios del Pseudocodigo ? :v




Serapis

#9
Bueno, lo hice del tirón y con prisas pués tenía sueño.
Ahora despejado del cansancio y con más calma veo que se me han escapado algunos detalles... saco un tiempo y lo corrijo.




pongo corregido como cita, para colorear donde haya cambios (las tiquetas code, no dejan, y (razonablemente) no hay geshi para pseudocodigo.
Citar
// Si devuelve true, valor contiene un número en caso contrario, se debe ignorar
Buleano = Funcion SiguienteToken(arraybytes Data(), porreferencia entero Indice, porreferencia entero Valor)
   TokenDeNumero tkn
   buleano negativo  // false de entrada

   Si SaltarEspaciosEIgnorarDesconocidos(data, indice) = FALSE devolver FALSE

   tkn = CharsToken(data(indice))
   si (tkn AND TOKEN_SIGNO) luego  // <--- este condicional debía ir por encima del bucle
       Hacer
           negativo  = (tkn = TOKEN_SIGNO_NEG) //true si el signo es negativo
           indice += 1
           Si SaltarEspacios(data, indice) = FALSE devolver FALSE  
           tkn = CharsToken(data(indice))
           si (tkn = TOKEN_DIGITO)
               salir del bucle
           sino
               negativo  = FALSE    // se invalida el resultado previo, hay caracteres desconodicos tras el signo que se encontró.
               Si SaltarEspaciosEIgnorarDesconocidos(data, indice) = FALSE devolver FALSE  
               tkn = CharsToken(data(indice)) // <----linea olvidada
           fin si        
       repetir mientras ( tkn  <> TOKEN_DIGITO)   // <---- la condición de reentrada va mejor abajo
       // visto que al inicio entra si signo, y repite hasta encontrar un dígito, es básicamente lo mismo pero queda más claro así
   fin si
   
   // proveo la solución a los dígitos '0' a la derecha (simplemente se filtran antes):
   Hacer mientras (data(indice) = 48)   // 48 es el valor ASCII del cero.
       indice += 1
       si (indice = Data.Length) devolver FALSE
   repetir
   
   si (indice < Data.Length)  // <--- el bucle anterior anula su necesidad
       indice += 1  //  ídem     "  ----------  "  --------------- " ídem
   
   Si (CharsToken(data(indice)) = TOKEN_DIGITO)  // tras los ceros, podría haber caracteres no dígitos, se resolverá en otra llamada
       Hacer
           indice += 1
           valor *= 10 + (data(indice) - 48)   // <---- Faltaba el -48, tratamos con valores ASCII...
           si (indice = Data.Length) Salir del bucle
       repetir mientras (CharsToken(data(indice)) = TOKEN_DIGITO)   // <--- faltaba el CharsToken(...) para obtener el tipo de token, si no evaluabamos un byte...

       // si no es negativo, se asume que es positivo.
       si (negativo = TRUE)  valor = - valor  // <--- este condicional es preferible bajo el bucle de digitos,
       // (para no complicar la simplicidad del bucle), si no,  daría error con negativos:    ejemplo: -87; -->  -8 * 10 + 7 = -73; --> -73 es distinto de -87
   
       Devolver TRUE  //y valor contiene el valor correcto, si devuelve false, se ignora el valor que contenga...
   sino
       Devolver FALSE
   fin si
fin funcion

en la siguiente función simplemente sobra una línea:
Citarentero = funcion ParseStringToSumasYRestas(arrayBytes Data() )
   entero total, valor, indice
   
   Hacer mientras SiguienteToken(Data, Indice, valor ) = TRUE
       total += valor
       indice +=1 <--- sobra esta línea.
       si (indice = Data.Length) salir del bucle  //  <--- esta línea no sobra, pero podrría omitirse si se recibe info extra de vuelta
       // el último dígito de un número podría ser también el último del string...
   repetir      
   
   Devolver total
fin funcion

Finalmente indicar que es posible optimizarlo más... si uno se fija bien, podría llegar a haber un chorro de multiplicaciones "*10", tantos como dígitos haya, por cada número... técnicamente solo es necesario una única multiplicación, el resto pueden ser convertidas en sumas. Tiene su tiempito hacer dicha modificación, por lo que lo dejo al esfuerzo de los interesados...