Huffman en c++.
A continuación explico el código (ver la explicación del algoritmo de huffman).
Escritura a un archivo.
No se puede escribir bit por bit a un archivo. Para que nuestro programa pueda ir escribiendo bit por bit la información requerida necesitamos crear una clase (objeto) que actúe como intermediario entre el algoritmo de compresión y el archivo. La llamaremos ARCH_OUT:
class ARCH_OUT { FILE *Arch; unsigned char indice; unsigned char byte; public: ARCH_OUT(unsigned char*,unsigned char*); ~ARCH_OUT(); bool Fallo(){return !Arch;} void Bit(unsigned int); void Buffer(unsigned char*,unsigned int); };
Esta clase contiene tres variables. La primera es de tipo FILE y sirve para almacenar el puntero al archivo abierto.
Dijimos que no podemos escribir bit por bit a un archivo. Entonces haremos lo siguiente: Escribiremos los datos en un buffer de un byte, llenándolo bit a bit, y cuando esté lleno lo vaciaremos al archivo.
Entonces la segunda variable es la que nos indica en que bit del buffer vamos escribiendo, y la tercera variable es el buffer.
Ésta clase tiene un constructor y un destructor. El constructor se encarga de crear el archivo con el nombre y atributos especificados. El destructor tiene que escribir lo último que nos quedó en el buffer (si es que quedó) y cerrar el archivo.
También tiene tres métodos. El primero, llamado “Fallo”, retorna un valor verdadero si el archivo no pudo ser creado y un valor falso si fue creado exitosamente. El segundo, llamado “Bit”, recibe un bit y lo almacena en el buffer para guardarlo al archivo. Si éste último está lleno entonces vacía el buffer al archivo y pone a cero el índice.
El tercer método se encarga de escribir un buffer de bits al archivo. Esto es muy útil ya que dijimos que los caracteres codificados podían tener una longitud de bits variable. Entonces esta función simplemente recibe el puntero al buffer y la cantidad de bits que debe tomar de allí y los escribe al archivo. Debemos tener en cuenta que esta función escribirá primeramente los bits existentes en el buffer si los hay, luego los bits recién indicados, y por último dejará en el buffer los bits que no alcanzaron a completar un byte para ser escritos.
Lectura desde archivo.
Así como no se puede escribir a un archivo bit por bit, tampoco se puede leer desde un archivo bit por bit. Así que nuestra clase intermedia se llamará ARCH_IN:
class ARCH_IN { FILE *Arch; unsigned char indice; unsigned char byte; public: ARCH_IN(unsigned char*,unsigned char*); ~ARCH_IN(); bool Fallo(){return !Arch;} unsigned int Bit(); unsigned int Byte(); FILE *Base(){return Arch;} };
Las tres variables tienen el mismo propósito que las de la clase anterior (ARCH_OUT). El constructor solamente abre el archivo y pone a cero el índice. El destructor simplemente cierra el archivo. El método “Fallo” tiene el mismo objetivo que el de la clase anterior.
El método “Bit” tiene se fija si hay bits en el buffer. Si los hay, lee el que corresponde e incrementa el índice. Si no los hay, lee un byte del archivo (cargando así el buffer) y lee el primer bit incrementando luego el índice.
El método “Byte” es utilizado para leer un byte desde el archivo. Nótese que parecería que uno simplemente podría leer ese byte normalmente con las funciones estándares del lenguaje. Pero esto no es así debido a que ese byte puede, por ejemplo, ser los últimos tres bits de un byte y los primeros 5 del siguiente. Éste fenómeno se da al leer el árbol.
En esta clase no es necesaria una función para leer una cantidad de bits variable. Esto se debe a que cuando leemos el archivo original leemos byte por byte, y cuando leemos el archivo comprimido leemos bit a bit o byte a byte, pero nunca otra longitud de bits.
Y por último el método “Base” devuelve un puntero al archivo abierto para que el descompresor pueda verificar por su cuenta, de corrido (sin sucesivas llamadas al método “Byte”), los primeros caracteres del archivo. Éstos contienen el sello “hfm” que indica que el archivo está comprimido mediante este algoritmo. Ese sello lo ponemos nosotros al crear el archivo comprimido.
Hojas del árbol compresor.
Las hojas del árbol creado al comprimir tienen métodos y variables diferentes a las utilizadas para descomprimir.
Ésta es la clase “HOJA”, utilizada para comprimir (también es utilizada por los nodos):
class HOJA { unsigned int cCod; class HOJA *HojaIzquierda; class HOJA *HojaDerecha; class HOJA *pNIzq; class HOJA *pNDer; unsigned int lFrec; bool EsNodo; public: HOJA(unsigned int Codigo){pNIzq=NULL;pNDer=NULL; HojaIzquierda=NULL;HojaDerecha=NULL;lFrec=0; cCod=Codigo;EsNodo=false;} HOJA(HOJA *Izquierda, HOJA *Derecha); ~HOJA(); unsigned int Frecuencia(){return lFrec;} void Frecuencia(unsigned int Cuanto){lFrec=Cuanto;} class HOJA *HojaDer(){return HojaDerecha;} void HojaDer(HOJA *Cual){HojaDerecha=Cual;} class HOJA *HojaIzq(){return HojaIzquierda;} void HojaIzq(HOJA *Cual){HojaIzquierda=Cual;} unsigned char Codigo(){return cCod;} bool MoverADer(); bool MoverAIzq(); void CrearCodigo(class CABECERA*,struct CODIGO*); void CrearCabecera(class ARCH_OUT*); };
La variable “cCod” almacenará el código del carácter correspondiente a esa hoja si es que es una hoja y no un nodo. Esto último lo determina la variable booleana “EsNodo”. La variable “lFrec” almacenará cuantas veces se repite ese carácter en el archivo, o sea, su frecuencia.
Las variables “HojaIzquierda” y “HojaDerecha” apuntarán a la hoja inmediatamente a la izquierda o derecha mientras dure el proceso de conteo de frecuencias y ordenación según frecuencia. Recordemos que en este periodo el árbol no es un árbol todavía sino una alfombra de hojas.
Cuando ya hayamos recorrido todo el archivo y contado todas sus frecuencias y ordenado las hojas según su frecuencia empezaremos a armar el árbol. Las variables “pNIzq” y “pNDer” apuntarán a la hoja u nodo que se encuentre inmediatamente abajo a la derecha o abajo a la izquierda de este nodo. En el caso de que este objeto fuera una hoja y no un nodo, ambas variables estarían puestas a cero, ya que no tendrían nada abajo.
Veamos ahora el constructor. Como dijimos que este objeto puede ser tanto una hoja como un nodo, el constructor está sobrecargado. Así, si queremos crear una hoja llamaremos al constructor pasándole como argumento solamente el código del carácter para esa hoja. Si en cambio le pasamos dos argumentos, la clase interpretará que es un nodo y supondrá que le estamos pasando el puntero a la hoja izquierda y a la derecha.
El destructor también es interesante. Éste destruye primero las hojas u nodos que tenga abajo, si es que los tiene. De esta forma uno elimina el nodo de la raíz del árbol y éste se encarga de eliminar todo el árbol antes de desaparecer.
Como las variables del objeto son privadas y no públicas, los siguientes 7 métodos simplemente devuelven el valor de alguna de estas variables al que lo solicite.
Cuando queramos armar el árbol necesitaremos ir ordenando las hojas y nodos según su frecuencia. Para eso necesitaremos moverlas a la derecha o a la izquierda de la otra hoja u nodo. De eso se encargan los métodos “MoverADer” y “MoverAIzq”.
La función “CrearCabecera” es recursiva. La primera llamada debe ser a la raíz del árbol. Éste se encargará de ir llamando a cada nodo y hoja para que vayan escribiendo en el archivo su parte del código de la composición del árbol, que debe ser leído después por el descompresor para saber como era el árbol. Este método recibe como parámetro el puntero a la clase intermedia al archivo de salida.
La función “CrearCodigo” también es recursiva, y se encarga de crear los nuevos códigos para cada carácter de acuerdo a su ubicación en el árbol.
Hojas del árbol descompresor.
Este árbol es más simple que el anterior:
class HOJA_DESC { class HOJA_DESC* pHDer; class HOJA_DESC* pHIzq; bool EsNodo; unsigned int Cod; public: HOJA_DESC(class ARCH_IN*); ~HOJA_DESC(); unsigned int Descomprimir(class ARCH_IN*,FILE*, unsigned int); };
Este árbol no tendrá una hoja u nodo a la izquierda ni a la derecha, sino solo abajo a la izquierda o la derecha. Esto es así porque este árbol no se arma ordenando frecuencias sino leyendo directamente como es el árbol. Por lo tanto tampoco tiene la variable que contiene la frecuencia de aparición del carácter, no es necesaria.
El constructor recibe como parámetro el puntero a la clase intermedia al archivo de entrada. El constructor es recursivo, según lo que encuentre en el archivo de entrada seguirá creando todas las hojas y nodos necesarios hasta terminar de construir el árbol. De esta forma es suficiente crear una hoja pasándole el puntero a la clase intermedia para que se construya todo el árbol automáticamente.
El método “Descomprimir” también es recursivo, y basta con llamarlo en la raíz del árbol para que descomprima todo el archivo.
Código del caracter.
Esta estructura guarda el código binario que representará a tal o cual caracter.
struct CODIGO { unsigned char *Cod; unsigned int Tam; };
Por ejemplo, si la letra ‘a’ (ASCII=97) ahora está ubicada dos nodos a la derecha, su código será 11 binario (3 decimal). Entonces en este ejemplo la variable Cod apuntaría a un array de un carácter que contendrá un 3 (decimal) y la variable Tam será igual a 2, ya que la longitud del código binario es igual a 2 (11, son dos unos).
Clase “CABECERA”.
Esta clase es utilizada por la clase “HOJA” para ir guardando el código de cómo fue armado el árbol para luego ser volcada al archivo. Esta información será necesaria cuando queramos descomprimir, ya que deberemos saber como es el árbol para este archivo.
class CABECERA { unsigned char Buf[322]; unsigned int nIndice; public: CABECERA(){nIndice=0;} bool Bit(); void Bit(bool); unsigned char Byte(); void Byte(unsigned char); unsigned int Tamano(){return nIndice;} unsigned char *Buffer(){return Buf;} void Reset(){nIndice=0;} void Adelante(unsigned int nBits){nIndice+=nBits;} void Atras(unsigned int nBits){nIndice-=nBits;} };
Los métodos sobrecargados “Bit” sirven para leer o escribir un bit en el buffer. Los métodos sobrecargados “Byte” sirven para leer o escribir un byte en el buffer. El método “Tamano” devuelve el tamaño en bits del buffer. El método “Buffer” devuelve un puntero al Buffer. Reset pone a cero el contador. “Adelante” se mueve un bit hacia delante y “Atrás” se mueve un bit hacia atrás.
Clase HUFFMAN.
La clase principal, “HUFFMAN”, es la única que debe ser definida en nuestro programa compresor. Ésta se encargará de definir todas las otras e inclusive creará y manejará los archivos con solo pasarle el nombre. Esta es la definición de la clase:
class HUFFMAN { unsigned int lFrec[257]; HOJA *pUltima; CODIGO Caracter[257]; void UnoMas(unsigned char Cual){lFrec[Cual]++;} void CrearHojas(); void OrdenarHojas(); void CrearArbol(); void CrearCodigos(); public: HUFFMAN(); ~HUFFMAN(); bool Comprimir(char*); bool Descomprimir(char*); unsigned int Procesar(char*); };
Las variables que contiene la clase son:
- lFrec. Es un array que contiene la frecuencia con la que se repite cada valor.
- pUltima. Un puntero a la última hoja de nuestra alfombra. También será la raíz del árbol cuando sea creado.
- Caracter. Es un puntero a las estructuras que contienen el código que ahora representará a cada carácter.
El método “UnoMas” “avisa” que apareció una vez más dicho caracter. Los métodos “CrearHojas”, “OrdenarHojas”, “CrearArbol”, son obvios. El método “CrearCódigos” es llamado luego de la creación del árbol y lo que hace es crear los códigos para cada caracter de acuerdo a su ubicación en el árbol.
El método “Comprimir” recibe como argumento el nombre del archivo a comprimir. El método “Descomprimir” recibe como argumento el nombre del archivo a descomprimir. Y el método “Procesar” recibe como argumento un nombre de archivo, y él se encarga de ver si está comprimido o no. Si está comprimido lo descomprime y viceversa.
Programa.
Para implementar la clase HUFFMAN hice un programita que se utiliza de la siguiente manera: Uno arrastra un archivo con el ratón sobre el programita y lo suelta. Si el archivo está comprimido lo descomprimirá, y viceversa.
Este es el código:
#include "huffman.h" void main(int argc, char* argv[]) { HUFFMAN *Compresor; Compresor = new HUFFMAN(); if(argc > 1) Compresor->Procesar(argv[1]); delete Compresor; }
El algoritmo de huffman.
(ver la implementación del algoritmo de huffman en c++)
El algoritmo de Huffman es un algoritmo de compresión de información. Este algoritmo está basado en la idea de que algunos caracteres aparecen muchas más veces que otros. Es especialmente eficaz en archivos de texto, donde más de la mitad de los caracteres difícilmente aparezcan alguna vez. Y entre los caracteres que aparecen, hay algunos que aparecen mucho más que otros.
Cada caracter se almacena en una secuencia de 8 bits. Este algoritmo se encarga de asignar códigos más cortos a los caracteres que más se repiten, y códigos más largos a los que menos se repiten. De esta manera a cada caracter se le asigna una longitud que puede variar entre 1 y 255 bits.
¿Cómo se hace esto?
Mediante un árbol binario. Veamos un ejemplo.
Supongamos que en un archivo tenemos el texto: “mi mama me mima”. Este texto ocupa 120 bits (15 bytes). Lo que propone el algoritmo de Huffman es lo siguiente:
-
Contemos cuantas veces aparece cada letra: m(6), i(2), “espacio”(3), a(3), e(1) (De ahora en adelante llamemos “~” al “espacio” para hacer las cosas más faciles).
-
Ordenamos las letras por su frecuencia, de mayor a menor:
m(6)~(3)a(3)i(2)e(1) -
Sumamos las últimas dos “hojas”, y en su lugar ponemos un “nodo” que tendrá la frecuencia de las dos hojas sumadas (y hagamos esto sucesivamente).
m(6) ~(3) a(3) (3)// Sumamos.
/ \
i(2) e(1)
m(6) ~(3) (6)// Sumamos.
/ \
a(3) (3)
/ \
i(2) e(1)
m(6) (6) ~(3)// Reordenamos por frecuencia.
/ \
a(3) (3)
/ \
i(2) e(1)
m(6) (9)// Sumamos.
/ \
(6) ~(3)
/ \
a(3) (3)
/ \
i(2) e(1)
(15)// Reordenamos y sumamos.
/ \
(9) m(6)
/ \
(6) ~(3)
/ \
a(3) (3)
/ \
i(2) e(1)
-
Ahora debemos crear una convención. En este ejemplo, convengamos: “0” a la izquierda y “1” a la derecha. De esta manera debemos recorrer el árbol hasta las hojas para crear los códigos de los caracteres. Por ejemplo, el carácter “i” está a: izquierda-izquierda-derecha-izquierda. Esto equivale a: 0010. Podemos hacer lo mismo con los otros, y obtendríamos estos códigos:
m = 1.
~ = 01.
a = 000.
i = 0010.
e = 0011.
-
Y ahora nos resta leer el archivo y por cada caracter leído escribir su correspondiente código en el archivo de salida. Para hacerlo más simple para nuestro análisis humano pondremos un punto entre cada código, pero en el archivo lo escribiremos todo junto. En el caso de “mi mama me mima”, tendríamos un archivo de salida así:
1.0010.01.1.000.1.000.01.1.0011.01.1.0010.1.000.
Esto suma un total de 33 bits. Mucho menos que el archivo original de 120 bits.
¿No habrá ambigüedades?
Ahora, como el lector sabe que en el archivo no escribiremos los puntos, se estará preguntando: ¿Y que pasa si al leer la información comprimida imagino los puntos en otras ubicaciones, que voy a interpretar?.
Hagamos la prueba, no le pongamos ningún punto: 100100110001000011001101100101000.
Pero recordemos seguir esta regla: Teniendo el árbol “a mano” situémonos en el nodo principal y vayamos leyendo la información. Cada vez que leamos un bit, movámonos al nodo inferior que esté a la derecha o a la izquierda según indique el bit. Cuando nos encontremos con que nos movimos a una hoja en vez de a un nodo, escribamos el carácter de la hoja, movámonos al principio del árbol y sigamos leyendo bit por bit desde donde nos habíamos quedado. Repitamos este proceso hasta el último bit.
Esto es lo que leeremos para nuestro ejemplo:
- Derecha (hoja). Ponemos la m. Volvemos arriba.
- Izquierda-izquierda-derecha-izquierda (hoja). Ponemos la i. Volvemos arriba.
- Izquierda-derecha (hoja). Ponemos ~. Volvemos arriba.
- Etc…
Al final habremos formado: mi mama me mima. Y no habrá posibilidades de formar otra frase con ese árbol y esa secuencia de bits. Haga algunos intentos y quedará convencido de que es así.
Debemos notar que para cada archivo habrá un árbol diferente de acuerdo a las frecuencias de los caracteres en ese archivo en particular, y una secuencia de bits diferente de acuerdo a la ubicación de los caracteres.
Ahora el lector habrá notado que el descompresor abre el archivo comprimido y “ve” la secuencia de bits, pero se estará preguntando ¿cómo sabe el descompresor cual es el árbol de ese archivo? ¡Claro, habíamos dicho que para cada archivo hay un árbol y una secuencia de bits diferentes! Así que el descompresor también deberá conocer el árbol.
Guardando el árbol.
Veamos una manera sencilla de guardar el árbol, aunque no es tan sencillo explicarlo.
Para guardar la secuencia de bits nosotros sabíamos si estábamos en un nodo o en una hoja, y lo que anotábamos era la dirección hacia la que nos movíamos.
Ahora hagamos justamente lo opuesto. Recorramos el árbol en una dirección (secuencia) conocida y anotemos si estamos en un nodo o en una hoja, y si estamos en una hoja anotemos su caracter.
Para eso convengamos esta regla: Cada nodo/hoja al ser llamado anotará un 1 si es una hoja y un 0 si es un nodo. Luego si es una hoja anotará los 8 bits que representan el caracter de esa hoja. Y si es un nodo, llamará al nodo/hoja de la izquierda y luego al de la derecha para que hagan lo mismo (recursivamente). Por último, después de hacer todo lo que tiene que hacer, devolverá “el mando” al nodo que lo llamó.
De esta forma nuestro árbol de ejemplo (más abajo) quedaría guardado así:
- (15) dice: Soy nodo, escribo 0. Llamo a nodo (9)
- (9) dice: Soy nodo, escribo 0. Llamo a nodo (6).
- (6) dice: Soy nodo, escribo 0. Llamo a hoja a(3).
- a(3) dice: Soy hoja, escribo 1. Escribo mi código: 01100001. Retorno a (6).
- (6) dice: Ahora llamo a nodo (3).
- (3) dice: Soy nodo, escribo 0. Llamo a hoja i(2).
- i(2) dice: Soy hoja, escribo 1. Escribo mi código: 01101001. Retorno a (3).
- (3) dice: Ahora llamo a hoja e(1).
- e(1) dice: Soy hoja, escribo 1. Escribo mi código: 01100101. Retorno a (3).
- (3) dice: Retorno a (6).
- (6) dice: Retorno a (9).
- (9) dice: Ahora llamo a hoja ~(3).
- ~(3) dice: Soy hoja, escribo 1. Escribo mi código: 00100000. Retorno a (9).
- (9) dice: Retorno a (15).
- (15) dice: Ahora llamo a hoja m(6).
- m(6) dice: Soy hoja, escribo 1. Escribo mi código: 01101101. Retorno a (15).
- (15) dice: Terminamos (retorno a quien sea que haya preguntado el código del árbol).
Árbol:
(15)
/ \
(9) m(6)
/ \
(6) ~(3)
/ \
a(3) (3)
/ \
i(2) e(1)
Tamaño de la descripción del árbol.
El código que se acaba de generar sería el siguiente:
0001011000010101101001101100101100100000101101101 (49 bits).
Éste código debe ir antes que la secuencia de bits del archivo para que el descompresor pueda armar el árbol antes de leer la secuencia de bits. En total el archivo generado tendrá 82 bits. Esto equivale a 10,25 bytes. Pero como no podemos guardar medio byte, tendremos que redondear a 11 bytes. Así un archivo de 15 bytes quedó reducido a 11 bytes.
Note el lector que casi el 60% del archivo comprimido está ocupado por la descripción del árbol. Ya que una descripción de árbol varía entre los 0 bits, en caso de un archivo vacío, y los 2559 bits (320 bytes), en caso de que un caracter se repita más que todos los otros juntos, y que el siguiente que más se repite se repita más que todos los otros que se repiten menos que él juntos y así sucesivamente, vemos que la descripción del árbol ocupará a lo sumo 320 bytes.
Esto es prácticamente insignificante para archivos de varios KB, por lo tanto el porcentaje de compresión va a ser mayor que para este ejemplo ya que aquí más del 59,75% del archivo es la pura descripción del árbol, (en este caso es muy significante).
Leyendo la descripción del árbol.
Para descomprimir el archivo basta armar el árbol a medida que vamos leyendo su descripción. Dejémoslo como tarea para el lector… Está bien, hagámoslo juntos. En la descripción leemos:
0: Dibujamos un nodo.
( )
/ \
0: Dibujamos otro nodo.
( )
/ \
( )
/ \
0: Dibujamos otro nodo.
( )
/ \
( )
/ \
( )
/ \
1: Dibujamos una hoja y leemos los 8 bits de su código (01100001=a).
( )
/ \
( )
/ \
( )
/ \
a( )
Nos movemos al próximo lugar vacío y leemos: 0: dibujamos un nodo.
( )
/ \
( )
/ \
( )
/ \
a( ) ( )
1: Dibujamos una hoja y leemos los 8 bits de su código (01101001=i).
( )
/ \
( )
/ \
( )
/ \
a( ) ( )
/ \
i( )
1: Dibujamos una hoja y leemos los 8 bits de su código (01100101=e).
( )
/ \
( )
/ \
( )
/ \
a( ) ( )
/ \
i( ) e( )
1: Dibujamos una hoja y leemos los 8 bits de su código (00100000=~).
( )
/ \
( )
/ \
( ) ~( )
/ \
a( ) ( )
/ \
i( ) e( )
1: Dibujamos una hoja y leemos los 8 bits de su código (01101101=m).
( )
/ \
( ) m( )
/ \
( ) ~( )
/ \
a( ) ( )
/ \
i( ) e( )
El nodo principal devuelve el control al que lo llamó. Y podemos ver que esto ocurre justamente cuando se terminan los bits. No hay ambigüedades.
Esto es todo.
Bueno, con esta información ya somos capaces de comprimir y descomprimir archivos mediante el algoritmo de Huffman. Ahora veremos como implementar el algoritmo de huffman en c++.
Cambiar nivel crítico de bateria en Windows 7
Tuve un problema con Windows 7. La notebook hibernaba apenas la desconectaba de la red eléctrica. Entré al Panel de control -> Opciones de energía y, elegí "Cambiar la configuración del plan": Equilibrado, que es el que estaba usando. Hice clic en "Cambiar la configuración avanzada de energía", y busqué, dentro de la lista, la opción "Batería".
Ví que el nivel de batería crítica estaba en 98%, y la acción era Hibernar cuando bajaba a este nivel. Por eso hibernaba inmediatamente al desconectarla (porque en seguida bajaba al 98%, mi batería ya está viejita).
Traté de cambiarlo pero no pude, lo cambiaba y se volvía a poner a 98%. Luego de dar muchas vueltas en internet encontré como hacerlo:
Inicio -> gpedit.msc -> Configuración del equipo -> Plantillas administrativas -> Todos los valores -> Nivel de notificación de batería crítica. Hay que poner "Habilitada" y establecer el nivel en Opciones -> Nivel de notificación de batería crítica. El valor es en porcentaje. Clic en aceptar y listo. Ya debería estar seteado el nivel.
Actualizado 16/07/2010:
Aunque esto cambió el nivel crítico la compu sigue hibernando al bajar de 98% (aunque su nivel crítico está puesto a 20%).
Y no tengo la opción de que la "acción" para batería crítica sea "no hacer nada". La forma de poner esta opción es ir a inicio y pegar:
powercfg -setdcvalueindex SCHEME_CURRENT SUB_BATTERY BATACTIONCRIT 0
O sea, iniciar powercfg con esas opciones. Se verá por un instante una ventana negra (línea de comandos) y luego se cerrará. Ahora sí podés ir a: Panel de control -> Opciones de energía -> "Cambiar la configuración del plan"-> Equilibrado (o el que estés usando) -> "Cambiar la configuración avanzada de energía" -> "Batería". Y fijate que la acción a realizar para batería crítica es "no hacer nada" a pesar que no estaba esa opción.
A ver si esto me soluciona el problema para que no se me siga apagando apenas la desconecto.
Método circuital o de las corrientes de mallas con matlab.
Comparto un script de matlab que hice para resolver circuitos en AC mediante el método de las corrientes de malla o método circuital (ver también método nodal).
Por ejemplo, para resolver el siguiente circuito:
Habría que ejecutar lo siguiente en matlab (muestro en negrita lo ingresado, el resto es generado por el script):
>> mallas
MÉTODO DE LAS MALLAS EN EL DOMINIO DE LAPLACE.
---------------------------------------------------------------------
¿Cuantas mallas tiene el circuito?: 3
¿Cuantos dígitos de precisión quiere ver en el resultado?: 3
---------------------------------------------------------------------
MATRIZ DE IMPEDANCIAS.
A continuación ingrese los elementos de la matriz de impedancias en el
dominio de Laplace utilizando "s" como variable generalizada de Laplace.
***Sepa que debe ingresar las coimpedancias con su SIGNO CORRESPONDIENTE**
Ingrese z[1,1]: 6
Ingrese z[1,2]: -2
Ingrese z[1,3]: -2
Ingrese z[2,2]: 6
Ingrese z[2,3]: -2
Ingrese z[3,3]: 6
Así quedó la matriz de impedancias:
[ 6, -2, -2]
[ -2, 6, -2]
[ -2, -2, 6]
¿Está bien?(S/N): s
---------------------------------------------------------------------
MATRIZ DE TENSIONES.
Ingrese V[1]: 10/s
Ingrese V[2]: 0
Ingrese V[3]: 0
Así quedó la matriz de Tensiones:
10/s
0
0
¿Está bien?(S/N): s
---------------------------------------------------------------------
DETERMINANTE PRINCIPAL
El determinante principal (Dp) es:
128
---------------------------------------------------------------------
PARA ENCONTRAR i1(t).
La matriz que origina el determinante sustituto 1 (Ds1) es:
[ 10/s, -2, -2]
[ 0, 6, -2]
[ 0, -2, 6]
El Ds1 es:
320
---
s
Ds1/Dp:
5
---
2 s
Expresado en fracciones parciales:
5
---
2 s
Antitransformando tenemos i1(t):
5/2
Expresado i1(t) con formato punto decimal:
2.5
---------------------------------------------------------------------
PARA ENCONTRAR i2(t).
La matriz que origina el determinante sustituto 2 (Ds2) es:
[ 6, 10/s, -2]
[ -2, 0, -2]
[ -2, 0, 6]
El Ds2 es:
160
---
s
Ds2/Dp:
5
---
4 s
Expresado en fracciones parciales:
5
---
4 s
Antitransformando tenemos i2(t):
5/4
Expresado i2(t) con formato punto decimal:
1.25
---------------------------------------------------------------------
PARA ENCONTRAR i3(t).
La matriz que origina el determinante sustituto 3 (Ds3) es:
[ 6, -2, 10/s]
[ -2, 6, 0]
[ -2, -2, 0]
El Ds3 es:
160
---
s
Ds3/Dp:
5
---
4 s
Expresado en fracciones parciales:
5
---
4 s
Antitransformando tenemos i3(t):
5/4
Expresado i3(t) con formato punto decimal:
1.25
Método nodal en matlab
Comparto un script de matlab que hice para resolver circuitos en AC mediante el método nodal (ver también método de mallas).
Por ejemplo, para resolver el siguiente circuito:
Habría que ejecutar lo siguiente en matlab (muestro en negrita lo ingresado, el resto es generado por el script):
>> nudos
MÉTODO DE LOS NUDOS EN EL DOMINIO DE LAPLACE.
---------------------------------------------------------------------
¿Cuantos nudos tiene el circuito?: 4
¿Cuantos dígitos de precisión quiere ver en el resultado?: 5
---------------------------------------------------------------------
MATRIZ DE ADMITANCIAS.
A continuación ingrese los elementos de la matriz de admitancias en el
dominio de Laplace utilizando "s" como variable generalizada de Laplace.
***Sepa que debe ingresar las coadmitancias con su SIGNO CORRESPONDIENTE**
Ingrese y[1,1]: 1/0.5+1+1/0.5
Ingrese y[1,2]: -1/0.5
Ingrese y[1,3]: 0
Ingrese y[1,4]: -1
Ingrese y[2,2]: 1/0.5+1+1/0.5
Ingrese y[2,3]: -1/0.5
Ingrese y[2,4]: -1
Ingrese y[3,3]: 1/0.5+1+1/0.5
Ingrese y[3,4]: -1
Ingrese y[4,4]: 1+1+1+1
Así quedó la matriz de admitancias:
[ 5, -2, 0, -1]
[ -2, 5, -2, -1]
[ 0, -2, 5, -1]
[ -1, -1, -1, 4]
¿Está bien?(S/N): s
---------------------------------------------------------------------
MATRIZ DE CORRIENTES.
Ingrese I[1]: -2/s
Ingrese I[2]: -1/s
Ingrese I[3]: 2/s+2/s
Ingrese I[4]: 1/s-2/s
Así quedó la matriz de Corrientes:
-2/s
-1/s
4/s
-1/s
¿Está bien?(S/N): s
---------------------------------------------------------------------
DETERMINANTE PRINCIPAL
El determinante principal (Dp) es:
225
---------------------------------------------------------------------
PARA ENCONTRAR v1(t).
La matriz que origina el determinante sustituto 1 (Ds1) es:
[ -2/s, -2, 0, -1]
[ -1/s, 5, -2, -1]
[ 4/s, -2, 5, -1]
[ -1/s, -1, -1, 4]
El Ds1 es:
120
- ---
s
Ds1/Dp:
8
- ----
15 s
Expresado en fracciones parciales:
8
- ----
15 s
Antitransformando tenemos v1(t):
-8/15
Expresado v1(t) con formato punto decimal:
-0.53333
---------------------------------------------------------------------
PARA ENCONTRAR v2(t).
La matriz que origina el determinante sustituto 2 (Ds2) es:
[ 5, -2/s, 0, -1]
[ -2, -1/s, -2, -1]
[ 0, 4/s, 5, -1]
[ -1, -1/s, -1, 4]
El Ds2 es:
45
- --
s
Ds2/Dp:
1
- ---
5 s
Expresado en fracciones parciales:
1
- ---
5 s
Antitransformando tenemos v2(t):
-1/5
Expresado v2(t) con formato punto decimal:
-0.2
---------------------------------------------------------------------
PARA ENCONTRAR v3(t).
La matriz que origina el determinante sustituto 3 (Ds3) es:
[ 5, -2, -2/s, -1]
[ -2, 5, -1/s, -1]
[ 0, -2, 4/s, -1]
[ -1, -1, -1/s, 4]
El Ds3 es:
150
---
s
Ds3/Dp:
2
---
3 s
Expresado en fracciones parciales:
2
---
3 s
Antitransformando tenemos v3(t):
2/3
Expresado v3(t) con formato punto decimal:
0.66667
---------------------------------------------------------------------
PARA ENCONTRAR v4(t).
La matriz que origina el determinante sustituto 4 (Ds4) es:
[ 5, -2, 0, -2/s]
[ -2, 5, -2, -1/s]
[ 0, -2, 5, 4/s]
[ -1, -1, -1, -1/s]
El Ds4 es:
60
- --
s
Ds4/Dp:
4
- ----
15 s
Expresado en fracciones parciales:
4
- ----
15 s
Antitransformando tenemos v4(t):
-4/15
Expresado v4(t) con formato punto decimal:
-0.26667
Función de Transferencia con Matlab
El siguiente script para matlab grafica los diagramas de Bode, Nyquist y Polar para una función de transferencia dada.
Por ejemplo, si ejecutamos el script y ponemos 1/(p^2+3*p+1) como función de transferencia:
>> diagramas
T(s)=1/(p^2+3*p+1)
Un nombre para el ejercicio: ejemplo
El script genera un archivo html que dice lo siguiente:
Informe de la Función de transferencia (ejemplo)
El script de matlab guarda una imagen, así que puede ser estudiado como ejemplo para hacer eso y otras cosas interesantes, como crear una función de transferencia a partir de una función simbólica en matlab.
HyperTerminal en Windows 7 o Windows Vista.
Como acabarás de descrubrir, el HyperTerminal desapareció en el Windows Vista.
Pasos para poder usarlo en el Windows 7 o Vista:
- Descarga el HyperTerminal.
- Descomprimí el rar en la carpeta que quieras instalar el HyperTerminal.
- Ejecutalo. No hay que instalar nada. Podés crearte un acceso directo si querés.
Simplemente copié los archivos necesarios de Windows XP y los comprimí.
Lista negra
¿Cansados de recibir SPAM? Yo si.
Me llegan e-mails con promociones y me aseguran que no son SPAM porque si les pido que me remuevan de sus listas lo harán. Algunos te remueven, otros no. Pero de todas formas es SPAM porque es publicidad por la que nunca pedí. Juntaron mi dirección de algún sitio en internet y ahora me envían publicidad.
Así que a continuación voy a ir poniendo esas direcciones desde donde me envían SPAM en mi lista negra, así otros que quieran juntar direcciones para enviar publicidad se encarguen de enviárselas a estos señores también. A ver si se aguantan entre ellos
wilsonblanco@hipuu.com.py
info@coicom.com
info@misdramascristianos.com
donsala@donsala.com.ar
semiventas_bd@hipuu.com.py
blancodigital@hipuu.com.py
contacto@miamihardimport.com.ar
gaston@miamihardimoprt.com.ar
pptcristiano@hotmail.com
info@powerpointcristiano.com
tuministerioinfantil@hotmail.com
info@tuministerioinfantil.com
johnburrows.rrpp@gmail.com
El que quiera colaborar deje las direcciones desde las que les envían SPAM (y que son leídas por alguien si responden) en los comentarios.
¿Qué le da su valor al dinero?
¿Te has preguntado porqué todos queremos dinero? Siendo que es un simple pedazo de papel, ¿de qué nos sirve?
Desde el punto de vista estrictamente legal (por lo menos en Argentina), sólo la moneda es dinero. Ésta es el medio de pago físico impuesto por la ley.
Pero desde un punto de vista más amplio, el dinero es todo lo que sirve como medio de cambio, en el sentido de que se acepte ampliamente como medio de pago. Por ejemplo: papel moneda, cheque, tarjetas de crédito, tarjetas de débito, etc.
Oculta en el párrafo anterior está la respuesta a nuestra pregunta original: ¿por qué vale el dinero?. El dinero vale porque es aceptado. En el caso de la moneda del país, vale porque el gobierno la establece como medio de pago por ley.
Antes de que existiera el dinero las transacciones comerciales se llevaban a cabo por medio del trueque. O sea, yo tengo algo que necesitás y al mismo tiempo vos tenés algo que necesito, así que hacemos el intercambio.
Pero esto trae muchas complicaciones. Lo tuyo podría ser más valioso que lo mío, por lo tanto tendríamos que fijarnos si yo tengo algo más que te pueda interesar. Si no lo tuviera se complicaría la transacción. Yo tendría que conseguir algo más para vos, tal vez haciendo otro trueque con otra persona. Y la transacción se complica más y más.
Eso limita el comercio al punto de hacerlo casi imposible. Muchas sociedades utilizaban arroz, café, cacao, semillas, y hasta cigarrillos como dinero.
Luego aparecieron las monedas, con valor intrínseco. O sea que valían por si mismas, por los metales con las que estaban fabricadas.
Los primeros billetes, con valor representativo, fueron emitidos por el Banco de Inglaterra en 1694. Su valor dependía de los depósitos en oro del país. Si llevabas el billete al banco te daban su valor en oro y vice versa: para emitir billetes tenían que depositar oro como respaldo.
Hoy en día los gobiernos no tienen respaldo en oro para sus billetes. A veces los países simplemente emiten billetes, los sacan de la imprenta. Esto produce inflación y es un tipo de impuesto disimulado. Estados Unidos, por ejemplo, dejó de respaldar el dolar con oro en 1971.
Otro día hablamos más sobre como se emite dinero, o como se crea el dinero. También analizaremos el problema del "virus del débito". Pondré el link aquí. Mientras tanto los dejo con un video sobre otras causas de la inflación.
Asignación de teclas para HP 50g/49g+.
Antes que nada quiero hacer una aclaración para que no te confundas si tu pantalla no se ve como la mía.
Mi pantalla, por ejemplo, se podría ver así:
Mientras que, para la misma acción, la tuya se podría ver así:
Eso se debe a que yo tengo instaladas las librerías necesarias para poder ver las entradas SysRPL, que decodifican los nombres de las entradas.
Si querés hacer lo mismo podés instalar extable2.lib. Copiala al puerto 2:FLASH y luego reiniciá la calculadora (ON+F3). También podés ver el tutorial sobre como instalar una librería. Además tenés que tener la bandera --85 set (SysRPL stk disp).
Pero si no querés instalarlo, por ahora no te preocupes, porque aunque veas "External... FlashPtr..." etc en vez del nombre correcto, el programa va a funcionar igual. Simplemente no lo vas a poder ver correctamente.
Otra cosa: Tengo la calcu en modo RPN (el otro es algebráico). Para cambiar entre RPN y ALG tenés que presionar ,
,
. Esto cambia el "Operating Mode.." de RPN a Algebraic (o viceversa) y le da el OK. Para seguir estos ejemplos tenés que tener la calcu en ese modo (RPN).
Bueno, aclarado esto, manos a la obra...
El modo USER.
Jugá con esta secuencia de teclas y fijate si notás algún cambio en la pantalla de tu calculadora: ,
,
,
,
,
...
Como podrás ver, lo que cambia es una etiqueta arriba en la pantalla. Cambia entre "USR", "1US" y luego desaparece.
El orden en el que cambia, está determinado por la bandera numero -61 del sistema (-61 system flag). Si está set ([USR] locks User), primero aparece USR yluego 1USR. Si está clear ([USR][USR] locks), primero aparece 1USR y luego USR.
Si querés probar, para entrar a las banderas del systema tenés que ir a ,
(FLAGS).
En realidad decir que la bandera -61 determina "el orden en el que cambia" es simplemente un modo didáctico (o introductorio) de decirlo. A decir verdad USR quiere decir que se está en el modo USER. 1US queire decir que una tecla (la próxima) se evaluará en modo USER y luego se abandonará el modo USER. Y cuando desaparece la etiqueta es que no se está más en modo USER.
Así que la bandera -61 determina de que modo se traba (lock) el modo USER. Si está visible la etiqueta USR se considera que está trabado. Si está la etiqueta 1US no está trabado, pues luego de presionar una tecla (una funcional, no los shifts) ya dejaremos de estar en modo USER, por lo tanto no está trabado.
Si "[USR][USR] locks", debemos presionar ,
,
,
para la etiqueta USR. (presionando solamente
,
quedaríamos en 1US).
Si "[USR] locks User" con presionar ,
es suficiente para que aparezca la etiqueta USR. Si lo volvemos a hacer aparece 1US.
Y al final ¿qué es el modo USER? La siguiente bandera, la -62, determina si estamos o no en modo USER (la -61 tenía que ver con cómo entrabamos al modo USER). Al estar set, se muestra la etiqueta USR, al estar clear no se muestra. El modo USER quiere decir "Modo teclas de usuario" (User keys on).
Cuando uno presiona una tecla funcional de la calculadora, ésta comprueba si se está en modo USER o no:
- Si está en modo USER, se fija si el usuario le dió alguna asignación a la tecla. Si es así la ejecuta. Sinó ejecuta la asignación predefinida.
- Si no está en modo USER ejecuta la asignación predefinida de la tecla sin importar si el usuario le asignó otra función o no.
Instalando KeymanPlus.
El manual de referencia avanzado (en inglés) de la calculadora documenta algunas funciones para asignar teclas de usuario y mostrar las asignaciones hechas (ASN,DELKEYS, RCLKEYS, STOKEYS).
Pero en este post vamos a utilizar una librería que le da mucha mas potencia a las teclas de usuario: Keyman.
Instalá la librería Keyman (KeymanPlus) en tu calculadora (ver como). En el archivo zip también va a estar la documentación en castellano si le querés dar una mirada (KeymanE.htm).
Luego de haber reiniciado, presioná ,
, Keyma para ingresar al menú Keyman. Vas a ver algo así:
Fijate que el menú sige. Presioná para ver el resto (y NXT otra vez para ver el primero):
El comando A?D.
En la librería Keyman un signo de interrogación (?) en el nombre del comando significa que si presionás ese comando ejecuta la función que está antes del signo, y si presionás y mantenés presionado el comando por un instante (unos 300 ms aproximadamente) ejecuta la función que está después del signo.
En el caso del comando A?D, A significa Assign (asignar) y D Delete (eliminar). Asigna o elimina la asignación de una tecla.
Hagamos una asignación simple. Presioná ,
. Y luego presioná el comando A?D. Se tiene que ver así:
Se muestra el mensaje AsnK, que significa que lo que está en el nivel "1:" del stack se va a asignar a la tecla que presionés a continuación. Presioná la por ejemplo. Ya está asignado el 1 a la X. Ahora volvé a presionar la X y fijate como la X hace aparecer un 1.
Probá alguna combinación, como ,
por ejemplo. La función es la normal. Esto es porque la asignación afectó solo a la X. Las otras variaciones como
,
o
,
no fueron afectadas. Ya vamos a ver las diferentes combinaciones. Pero primero vemos como borrar una asignación.
Para borrar la asignación presioná A?D por unos instantes. Se va a ver algo así:
A continuación presioná la tecla cuya asignación querés borrar, en este caso la X. Listo. Todo ha vuelto a la normalidad.
Las combinaciones de teclas.
Cada tecla tiene su código, que está dado por su número de fila, su número de columna, un punto y un código de combinación, en ese orden.
La tecla (sola, sin ningún shift ni alpha) por ejemplo tiene código 41.1, donde fila=4, columna=1 y combinación = 1.
Las combinaciones que puede tener una tecla son:
- 1: La tecla sola.
- 2:
y luego la tecla. A esta combinación de presionar una tecla y luego la otra la simbolizaremos por la primera tecla, una coma, y luego la otra:
, la tecla.
- 21: Mantengo presionada
y, sin soltarla, presiono la tecla. A esta combinación de presionar una tecla y sin soltarla presionar la siguiente la simbolizaremos por la primera tecla, un signo más, y luego la otra:
+ la tecla.
- 3:
, la tecla.
- 31:
+ la tecla.
- 4:
, la tecla.
- 41:
+ la tecla.
- 5:
,
+ la tecla. O sea, presionás y soltas
, luego presionas
, y sin soltarla presionás la tecla.
- 51:
,
+ la tecla.
- 6:
,
, la tecla.
- 61:
,
+ la tecla.
Entonces, por ejemplo, la combinación ,
+
sería: 41.61. Y la combinación
+
sería: 44.21.
Cuando uno asigna una tecla de usuario mediante A?D, la asignación se realiza solamente a la combinación que uno utilizó cuando A?D te pidió la tecla (diciendo: AsnK).
Si en vez de , hubieras presionado
,
+
, la asignación solamente se habría realizado para 63.61. Por lo tanto solo esa combinación escribiría el 1 que le asignamos. Cualquier otra combinación funcionaría de la forma predeterminada (la "normal").
Tené en cuenta que si asignás algo a una combinación de teclas, a la hora de borrar esa asignación (presionando A?D por unos instantes), tenés que darle la combinación correcta que querés borrar. ¿Se entiende? A?D va a borrar solamente esa combinación (la que le des), las otras no se tocarán.
Los comandos RclK y K&SA.
El comando RclK espera a que presiones una tecla, o cualquier variante de ella (combinación), y te devuelve la asignación de usuario que tiene esa combinación si es que tiene alguna. Sino simplemente dice XX.XX not asnd, siendo XX.XX la combinación que hayas presionado.
El comando K&SA también espera a que presiones alguna combinación. Pero en vez de darte la asignación de usuario que tiene esa combinación, te da, en el nivel "2:" (o "3:") del stack el código XX.XX de la combinación que presionaste y en el nivel "1:" la función del sistema que se ejecuta predeterminadamente (la "normal") para esa combinación. También puede mostrar otras cosas. Fijate en la documentación de Keyman que otras cosas muestra a veces.
Lo que más nos interesa de K&SA es que devuelve la combinación que presionaste. Esto ayuda cuando uno no se acuerda de memoria el código para una combinación. De paso, el código se llama rc.p: r=row(fila), c=column(columna), p=plane(plano). El resto que devuelve lo podemos borrar nomás y la utilizamos para ver el código.
Asignar programas.
Uno puede asignar cualquier cosa que esté en el stack a una combinación rc.p.
Por ejemplo. En electrónica es muy común tener que realizar el cálculo del valor final de una combinación de resistores en paralelo. La fórmula del valor de resistencia equivalente, teniendo resistores R1, R2, R3... Rn en paralelo, es 1/(1/R1+1/R2+1/R3+...+1/Rn).
El siguiente programita hace ese cálculo:
Lo puedo asignar, por ejemplo, a la combinación +
(44.31), que me recuerda a Paralelo y no interfiere con la llamada a CAT, ya que a CAT puedo acceder mediante
,
(44.3).
Entonces ahora puedo poner los valores de los resistores (8, 5 y 6) en el stack seguidos por el número de resistores (3):
Y luego, presionando +
ya los tengo metidos en la fórmula:
Doble clic y clic largo.
¿Qué pasaría si a una rp.p le asignara un programa que quedara a la espera a ver si la vuelvo presionar luego de algunas décimas de segundo (doble clic), y en tal caso hiciera una cosa, y en caso de que pasaran las décimas de segundo y no la volviera a presionar hiciera otra? Pues eso hace el comando IFD (if double clic).
También podría hacer otro programa que esperara a ver si sigo presionando la tecla por algunas décimas de segundo, y si es así hiciera una cosa, y sino otra (clic largo). El comando IFL (if long clic) lo hace.
Poné en el stack una cadena que diga "DOBLE" y otra "NORMAL" (abrí comillas con ,
y escribí la cadena presionando
para poder ingresar las letras) . Así:
Luego presioná IFD. Queda así si no tenés instalado extable2:
Y así si tenés extable2 y la flag -85 set:
Eso que hay en el stack es un programa en SysRPL que dice que si detecta doble clic, escribe la cadena "DOBLE", sino, escribe "NORMAL".
Asigná el programa a la tecla X por ejemplo. O sea, presioná A?D y luego . Probá haciendo clic o doble clic en
. Para borrar la asignación presioná A?D por unos instantes y luego
.
Ese código se lo podés asignar a cualquier combinación. Lo mismo podés hacer con IFL para detectar clics largos.
Repetí los pasos de arriba pero no lo asignes a la tecla. Ahora escribí "LARGO":
Presioná para invertir las posiciones (para que "LARGO" quede arriba):
Ahora presioná IFL. Asigná (con A?D) el resultado a algún rc.p que quieras, por ejemplo a ,
. Ahora probá haciendo
,
. Haciendo
, doble clic
. Y
, manteniendo
presionada.
Como verás, ya le asignamos tres códigos diferentes para una combinación. Una si la combinación recibe un click, otra si recibe doble clic y otra si recibe un clic largo. Así se puede hacer para cada combinación para cada tecla, por lo tanto las posibilidades son muchísimas.
Si querés asignar a una tecla un comportamiento con doble clic, y para el clic común querés el comportamiento "normal", tenés que poner arriba lo que querés que haga la tecla para doble clic, abajo el código rc.p de la tecla y presionar IFD. De este modo para doble clic se ejecuta lo de arriba y sino se ejecuta lo que haría normalmente el código rc.p que pusiste. Consultá el manual de Keyman para más información y ejemplos.
Otros comandos.
Los otros comandos están explicados en la documentación de Keyman (KeymanE.html) en el archivo zip. Creo que con esta explicación ya se puede tener una base suficiente como para ir directamente a la documentación, que tiene varios ejemplos.
Documentar las asignaciones.
Cuando empieces a crearte muchos accesos directos en tu teclado te vas a empezar a olvidar de todo lo que tenés.
Una forma simple de documentar tus teclas es la siguiente.
- Elegí una combinación de documentación. Por ejemplo
+ tecla.
- Cada vez que asignes algo a alguna tecla, asignale a
+ esa tecla la explicación de lo que hace. O agregale a una explicación anterior si es que ya tenías una.
Ejemplo:
Le vamos a asignar a +
un programa que ponga "HOLA" en el stack:
Asignalo (con A?D) a +
.
Ahora hacé lo mismo con un programa que diga << "CHAU" >> pero asignalo a ,
(soltá el shift antes de presionar X).
Ahora creá un programa que diga: << "LS+: DICE CHAU(este símbolo rojo)RS: DICE HOLA" >>
Se ve así si lo editás (EDIT):
Asignalo a +
. Ahora cada vez que presiones
+
vas a poder ver todas las asignaciones que tenés para la tecla
. Obviamente tenés que ir actualizando la información cada vez que asignás algo nuevo.
En este caso LS+ significa LEFT SHIFT (el blanco) y sin soltar (por eso el +) la tecla en cuestión. Y RS significa RIGHT SHIFT (el rojo), pero soltándolo antes de presionar la tecla en cuestión.
Otra cosa que me sirve es asignar << 1200 MENU >> a la tecla para clic largo. De esta forma haciendo un clic largo en
se abre el menú de la librería Keyman.


