Manejando arrays

Manejando arrays

Postby antolin » Fri Oct 25, 2013 11:08 am

Hola foreros.

Como continuacion a mi post:

viewtopic.php?f=6&t=27442

LLevo algún tiempo desarrollando una herramienta, para mi trabajo, que me permita consultar, editar y modificar arrays mediante tablas/listbox, y que pueda trabajar con matrices complejas multidimensionales. Esto me está produciendo algunos quebraderos de cabeza que voy solventando sobre la marcha, pero a la vez me ha llevado a implementar algunas funciones interesantes que a lo mejor se pueden aplicar en otros ámbitos, por eso las expongo aquí, en los siguientes posts, por si a alguien le interesa.

********************

El primer "inconveniente" con del que me topé nada más empezar, fue la presentación de los elementos de la tabla. Los array de Clipper/Harbour son tan versátiles y potentes que un elemento puede contener cualquier tipo de dato: Numérico, String, Fecha, etc., incluso arrays multidimensionales, objetos o CodeBlocks (además pueden mesclarse). Con cValToChar() se solucionan los tipos normales, pero los arrays, por ejemplo, me los representa con la palabra "Array", o como { ... }, según los casos, y eso no me dice mucho. Por eso escribí la siguiente función:
Code: Select all  Expand view
FUNCTION InfoArray(aDat)
   LOCAL xDat := "{ "
   *
   IF aDat = NIL
      RETURN ""
   ELSEIF Empty(aDat) .OR. aDat[1] = NIL
      xDat += ""
   ELSEIF ValType(aDat[1]) == "A"
      xDat += InfoArray(aDat[1])
   ELSE
      xDat += ALLTRIM(cValToChar(aDat[1]))
   ENDIF
   *
   IF Len(aDat) < 2
      xDat += " }"
   ELSE
      xDat += ",... }"
   ENDIF
RETURN xDat

Por Ejemplo, el array  { "Antonio","Juan","Pedro" } lo escribe como: "{ Antonio,... }",
y el array { { 1,2,3 },{ 12,13,14 } } lo escribe como: "{ {1,...},... }".


No es como ValToPrg() que desglosa el array completo, pero cabe en la celda de la tabla y me da información básica. En este caso me indica que el primer array es de una sola dimensión y contiene nombres, y que el segundo es bidimensional y contiene números.

En cuanto a los datos tipo objeto lo solventé escribiendo su ClassName ¿?. Por ahora me sirve.
Peaaaaaso de foro...
antolin
 
Posts: 492
Joined: Thu May 10, 2007 8:30 pm
Location: Sevilla

Re: Manejando arrays

Postby antolin » Fri Oct 25, 2013 11:10 am

El segundo problema se daba a la hora de ordenar las columnas de mi tabla/listbox.

¿Ordanar una columna de arrays, para qué? Bueno es sólo cuestión de feedback, se pueden ordenar todas las columnas y así da la impresión de que el programa sabe lo que hace, y que hasta es capaz de ordenar arrays. En cuanto al criterio de ordeación, no me calenté la cabeza, ordenar por el primer elemento válido del array. Ese elemento lo extraigo con la sencilla función siguiente:

Code: Select all  Expand view
STATIC FUNCTION _PrimArray(xDat)
   IF ValType(xDat) == "A"
      RETURN _PrimArray(xDat[1])
   ELSEIF xDat = NIL
      RETURN ""
   ENDIF
RETURN xDat

Para ordenar la columna, que no es más que un array de arrays, no tengo más que hacer:

ASORT(::aItem[nCol],,,{ |X,Y| _PrimArray(X) < _PrimArray(Y) })

Por ahora me sirve. Quizá más adelante tenga que ordenar entre sí los array con mismo primer elemento, entonces ya buscaré la solución pertinente.

Pero ahí no se acabaron los problemas de ordenación. Cuando todas las columnas contienen todas un mismo tipo de datos, la cosa va bien, pero me surgió el caso de que una misma columna tenía distintos tipos de datos, por lo que tuve que resolverlo así:

Code: Select all  Expand view
STATIC FUNCTION _OrdTodo(xDat)
   IF ValType(xDat) == "A"
      RETURN "ZZ"+_PrimArray(xDat)
   ELSEIF ValType(xDat) == "D"
      RETURN DTOS(xDat)
   ELSEIF ValType(xDat) == "O"
      RETURN "ZZâ"
   ELSEIF ValType(xDat) == "B"
      RETURN "ZZé"
   ELSEIF ValType(xDat) = "L"
      IF xDat
     RETURN "ZZ#"
      ELSE
     RETURN "ZZ!"
      ENDIF
   ENDIF
RETURN cValToChar(xDat)

Así, para ordenar las columnas no tengo más que hacer:

TRY
   IF ValType(::aItem[nCol,1]) == "A"
      ASORT(::aItem[nCol],,,{ |X,Y| _PrimArray(X) < _PrimArray(Y) })
   ELSEIF ValType(::aItem[nCol,1]) == "D"
      ASORT(::aItem[nCol],,,{ |X,Y| DTOS(X) < DTOS(Y) })
   ELSEIF ValType(::aItem[nCol,1]) == ...
      ASORT(...
      ...
   ENDIF
CATCH
   ASORT(::aItem[nCol],,,{ |X,Y| _OrdTodo(X) < _OrdTodo(Y) })
END

De esta forma, si una misma columna contiene diferentes tipos de datos, falla el TRY y manda el flujo del programa por el CATCH ordenándome la columna sin tener que comprobar previamente si ésta es uniforme o no.

En cuanto a las "ZZ" de la función _OrdTodo(), también tiene su explicación. Está echo para que una vez ordenada la columna, primero aparezcan los datos normales; string, números, fechas, etc, luego los "ZZ": primero los tipo lógico, pues los caracteres "#" y "!" son ASCII inferiores al de cualquier otra letra. Detrás vendrían los array y finalmebnte los codeblocks seguidos de los tipo objeto, porque "é" viene antes que "â" (ASCII 130 y 131 respectivamente).
Peaaaaaso de foro...
antolin
 
Posts: 492
Joined: Thu May 10, 2007 8:30 pm
Location: Sevilla

Re: Manejando arrays

Postby antolin » Fri Oct 25, 2013 11:11 am

Tercera complicación: la edición de la celda de la tabla.

Editar cualquier tipo dato en un GET no es nada complicado, el problema es editar un array. Lo he solventado ofreciendo dos alternativas conjuntas: la primera mandar el dato array de la celda a otra función (con un POPUP MENU) donde el array aparezca en forma de tabla editable y moficable (en realidad la mando de froma recursiva a esta misma función). La segunda, para arrays pequeños, escribir directamente en el GET el array en formato string y después convertirlo a array. Para ello he programado las siguientes funciones:

Code: Select all  Expand view
FUNCTION BuildArray(cDat)
   LOCAL nAbre   := 0
   LOCAL nCierra := 0
   LOCAL aDat,nAt,cSeg
   *
   cSeg := cDat
   *
   IF ( nAt := At("{",cSeg) ) > 0   // CUENTO CUANTOS ARRAYS SE ABREN
      DO WHILE nAt > 0
     ++nAbre
     cSeg := SubStr(cSeg,nAt+1)
     nAt  := At("{",cSeg)
      ENDDO
   ENDIF
   cSeg := cDat
   IF ( nAt := At("}",cSeg) ) > 0   // CUENTO CUANTOS ARRAYS SE CIERRAN
      DO WHILE nAt > 0
     ++nCierra
     cSeg := SubStr(cSeg,nAt+1)
     nAt  := At("}",cSeg)
      ENDDO
   ENDIF
   *
   IF nAbre = 0 .AND. nCierra = 0   // SI NO HAY ARRAY, DEVUELVO EL DATO
      RETURN cDat
   ELSEIF nAbre = nCierra       // SI COINCIDEN ABIERTOS Y CERRADOS
      cDat := SubStr(cDat,2,Len(cDat)-2)
      aDat := ExtractItem(cDat)
   ELSE                 // SI NO COINCIDEN
      aDat := {cDat}
   ENDIF
RETURN aDat
*
FUNCTION ExtractItem(cDat)  // EXTRAE ELEMENTOS DE CUALQUIER TIPO, UNO A UNO
   LOCAL nHasta,cTrozo,cDec,nPunt
   LOCAL aDat := {}
   *
   cDat := ALLTRIM(cDat)
   DO WHILE !Empty(cDat)
       nHasta := ItemLength(cDat)
       cTrozo := SubStr(cDat,1,nHasta)
       IF Left(cTrozo,1) == "{"
      cTrozo := SubStr(cTrozo,2,Len(cTrozo)-2)
      AADD(aDat,ExtractItem(cTrozo))    // ARRAY ANIDADO
       ELSE
      cTrozo := StrTran(StrTran(StrTran(StrTran(cTrozo,",",""),"}",""),"'",""),'"',"")
      TRY
         AADD(aDat,&cTrozo) // PARA NUMERO, fechas, LOGICOS,ETC. Y FUNCIONES
      CATCH
         AADD(aDat,cTrozo)  // PARA STRINGS
      END
       ENDIF
       cDat := Substr(cDat,nHasta+1)
   ENDDO
RETURN aDat
*
FUNCTION ItemLength(cDat)   // MIDE LA LONGITUD DE CADA ELEMENTO
   LOCAL nHasta := Len(cDat)
   LOCAL nBrak  := 0
   LOCAL nQ
   *
   FOR nQ = 1 TO Len(cDat)
       IF SubStr(cDat,nQ,1) = "{"
       ++nBrak
       ELSEIF SubStr(cDat,nQ,1) = "}"
       --nBrak
       ELSEIF  SubStr(cDat,nQ,1) = "," .AND. nBrak = 0
       nHasta := nQ
       EXIT
       ENDIF
   NEXT
RETURN nHasta


Ahora si escribo en el GET el string:
"{ { 'HOLA',12.34,.T.,DATE(),{ 'HOLA3',3.00 } },{ 'HOLA2',24.34,.F.,DATE()+10,'FINAL' } }"
me lo convierte en un array y puedo editarlo como tal en otra tabla, o guardarlo en un campo MEMO o en el disco ( con ASave() ).

Post data: La funcion ExtractItem() no la he probado con objetos ni codeblocs, pero debería funcionar.
Peaaaaaso de foro...
antolin
 
Posts: 492
Joined: Thu May 10, 2007 8:30 pm
Location: Sevilla

Re: Manejando arrays

Postby antolin » Fri Oct 25, 2013 11:13 am

El último escollo que me surgio tiene que ver con el clipboard. En mi tabla/listbox necesitaba poder pasar datos de una celda a otra, de un array a otro con el típico POPUP MENU cortar, copiar y pegar, etc.

Un array se puede guardar en el clipboard codificándolo primero con ASave() y recuperarlo después con ARead(), pero hay un grave inconvenoiente: los array transformado en strings con ASave() contienen muchos ASCII 0, Chr(0), que al recuperarlos del clipboard el sistema los convierte automáticament a ASCII 32 (espaco). Significa que no se pueden restituir adecuadamente con ARead().

Se me ocurrió que una vez transformados a string con ASave() podía sustituir los Chr(0) por la cadena "\0@" (o cualquier otra cadena). Después, una vez recuperados del clipboard no hay más que devolverlo a su estado normal volviendo a sustituir las cadenas "\0@" por el correspondiente Chr(0) antes de reconstruir el array con ARead(). Y funcionó a las mil maravillas.

Code: Select all  Expand view
Los arrays se guardan en el clipboard con:
oClp:SetText(StrTran(ASave(aDat),CHR(0),"\0@"))

Y se recuperan del clipboard con:
aDat := ARead(StrTran(oClp:GetText(),"\0@",Chr(0)))

Así de sencillo.

******************

A lo mejor ya existen funciones en (x)Harbour que pueden hacer todas estas cosas y estoy haciendo el ridículo implementando un montón de funciones innecesarias con sudor y lágrimas, pero no he sido capaz de encontrarlas. Así que, si alguien tenía necesidad de manejar arrays y no sabía cómo, ahí os dejo parte de mi trabajo con la esperanza de que sea de utilidad.

Por último, si teneis modificar alguna de estas funciones para adaptarlas a vestras necesidades, tened mucha precaución, pués la mayoría son recursivas y si os equivocais podeis incurrir en infinitas recursiones que bloquearán vuestro ordenador y tendreis que reiniciarlo para poder seguir trabajando. Por otro lado, si conoceis otras funciones más eficientes, o mejorais o perfeccionais las aqui expuestas, pues, todos los consejos son siempre bienvenidos y serían motivo de agradecimiento.

Un saludo.
Peaaaaaso de foro...
antolin
 
Posts: 492
Joined: Thu May 10, 2007 8:30 pm
Location: Sevilla

Re: Manejando arrays

Postby horacio » Fri Oct 25, 2013 1:31 pm

Antolín, muchísimas gracias por estos aportes. Saludos
horacio
 
Posts: 1358
Joined: Wed Jun 21, 2006 12:39 am
Location: Capital Federal Argentina

Re: Manejando arrays (corregid BUG)

Postby antolin » Mon Nov 18, 2013 1:14 pm

Hola de nuevo.

He detectado un pequeño bug en mi función PrimArray(), y es que ésta fallaba con arrays vacios, o cuando uno de sus primeros elementos es un array vacio.

La nueva versión:
Code: Select all  Expand view
FUNCTION PrimArray(xDat)
   IF xDat = NIL
      RETURN ""
   ELSEIF ValType(xDat) == "A"
      IF Len(xDat) = 0
          RETURN """
      ELSE
          RETURN PrimArray(xDat[1])
      ENDIF
   ENDIF
RETURN xDat


Perdonen las molestias.

Un saludo
Peaaaaaso de foro...
antolin
 
Posts: 492
Joined: Thu May 10, 2007 8:30 pm
Location: Sevilla

Re: Manejando arrays

Postby antolin » Mon Nov 18, 2013 1:15 pm

Ups...

Code: Select all  Expand view
No es """ es ""
Peaaaaaso de foro...
antolin
 
Posts: 492
Joined: Thu May 10, 2007 8:30 pm
Location: Sevilla


Return to FiveWin para Harbour/xHarbour

Who is online

Users browsing this forum: Google [Bot] and 14 guests