Pregunta Crear GUID / UUID en JavaScript?


Intento crear identificadores únicos globales en JavaScript. No estoy seguro de qué rutinas están disponibles en todos los navegadores, de qué tan "aleatorio" y sembrado está el generador de números aleatorios incorporado, etc.

El GUID / UUID debe tener al menos 32 caracteres y debe permanecer en el rango ASCII para evitar problemas al pasarlos.


3207


origen


Respuestas:


Ha habido un par de intentos en esto. La pregunta es: ¿quieres GUID reales o solo números aleatorios que Mira como GUIDs? Es bastante fácil generar números aleatorios.

function guid() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
      .toString(16)
      .substring(1);
  }
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

Sin embargo, tenga en cuenta que tales valores no son GUID genuinos.

No hay forma de generar GUID reales en Javascript, ya que dependen de las propiedades de la computadora local que los navegadores no exponen. Tendrá que usar servicios específicos del sistema operativo como ActiveX: http://p2p.wrox.com/topicindex/20339.htm

Editar: no es correcto: RFC4122 permite GUID aleatorios ("versión 4"). Ver otras respuestas para detalles.

Nota: el fragmento de código proporcionado no sigue RFC4122 que requiere que la versión (4) tiene que estar integrado en la cadena de salida generada. No use esta respuesta si necesita GUIDs obedientes

Utilizar:

var uuid = guid();

Manifestación:

function guid() {
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
    s4() + '-' + s4() + s4() + s4();
}

function s4() {
  return Math.floor((1 + Math.random()) * 0x10000)
    .toString(16)
    .substring(1);
}

document.getElementById('jsGenId').addEventListener('click', function() {
  document.getElementById('jsIdResult').value = guid();
})
input { font-family: monospace; }
<button id="jsGenId" type="button">Generate GUID</button>
<br>
<input id="jsIdResult" type="text" placeholder="Results will be placed here..." readonly size="40"/>


1895



Por un RFC4122 solución compatible con la versión 4, esta solución de una sola línea (ISH) es la más compacta que pude encontrar:

function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

console.log(uuidv4())

Actualización, 2015-06-02: Tenga en cuenta que la singularidad UUID se basa en gran medida en el generador de números aleatorios subyacente (RNG). La solución anterior usa Math.random() por brevedad, sin embargo Math.random() es no garantizado para ser un RNG de alta calidad. Ver Adam Hyland's excelente reseña en Math.random () para detalles. Para una solución más robusta, considere algo así como el módulo uuid[Descargo de responsabilidad: soy el autor], que utiliza API de RNG de mayor calidad cuando esté disponible.

Actualización, 2015-08-26: Como nota al margen, esto esencia describe cómo determinar cuántos identificadores se pueden generar antes de alcanzar una cierta probabilidad de colisión. Por ejemplo, con 3.26x1015 versión 4 RFC4122 UUID tiene 1 en un millón de posibilidades de colisión.

Actualización, 2017-06-28: UN buen artículo de desarrolladores de Chrome discutiendo el estado de la calidad Math.random PRNG en Chrome, Firefox y Safari. tl; dr: a finales de 2015 es "bastante bueno", pero no de calidad criptográfica. Para abordar ese problema, aquí hay una versión actualizada de la solución anterior que usa ES6, el crypto API, y un poco de JS Wizardy no me puedo atribuir el mérito:

function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  )
}

console.log(uuidv4());


3141



Realmente me gusta lo limpio La respuesta de Broofa es, pero es desafortunado que implementaciones deficientes de Math.random deja la posibilidad de colisión.

Aquí hay un similar RFC4122 la solución conforme a la versión 4 que resuelve ese problema al compensar los primeros 13 números hexadecimales por una porción hexadecimal de la marca de tiempo. De esa forma, incluso si Math.randomestá en la misma semilla, ambos clientes tendrían que generar el UUID exactamente en el mismo milisegundo (o más de 10.000 años después) para obtener el mismo UUID:

function generateUUID() { // Public Domain/MIT
    var d = new Date().getTime();
    if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
        d += performance.now(); //use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
}


Aquí hay un violín para probar.


671



La respuesta de broofa es bastante hábil, de hecho: impresionantemente inteligente, realmente ... rfc4122 obediente, algo legible y compacta. ¡Increíble!

Pero si estás mirando esa expresión regular, esos muchos replace() devoluciones de llamada, toString()y Math.random() Llamadas a función (donde solo usa 4 bits del resultado y desperdicia el resto), puede comenzar a preguntarse sobre el rendimiento. De hecho, joelpt incluso decidió lanzar RFC para la velocidad GUID genérica con generateQuickGUID.

Pero, ¿podemos obtener velocidad y Cumplimiento RFC? ¡Yo digo si!  ¿Podemos mantener la legibilidad? Bueno ... No realmente, pero es fácil si sigues.

Pero primero, mis resultados, en comparación con broofa, guid (la respuesta aceptada), y el no-rfc-obediente generateQuickGuid:

                  Desktop   Android
           broofa: 1617ms   12869ms
               e1:  636ms    5778ms
               e2:  606ms    4754ms
               e3:  364ms    3003ms
               e4:  329ms    2015ms
               e5:  147ms    1156ms
               e6:  146ms    1035ms
               e7:  105ms     726ms
             guid:  962ms   10762ms
generateQuickGuid:  292ms    2961ms
  - Note that results will vary by browser/cpu.

Entonces, en mi sexta iteración de optimizaciones, superé la respuesta más popular por más de 12X, la respuesta aceptada por más 9X, y la respuesta rápida no conforme por 2-3X. Y todavía estoy rfc4122 obediente.

Interesado en como? He puesto la fuente completa en http://jsfiddle.net/jcward/7hyaC/3/ y en http://jsperf.com/uuid-generator-opt/4

Para una explicación, comencemos con el código de broofa:

'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
  return v.toString(16);
});

Entonces reemplaza x con cualquier dígito hexadecimal al azar, y con datos aleatorios (excepto forzar los 2 bits superiores a 10 según la especificación RFC), y la expresión regular no coincide con el - o 4 personajes, por lo que no tiene que lidiar con ellos. Muy, muy resbaladizo.

Lo primero que debe saber es que las llamadas a funciones son costosas, al igual que las expresiones regulares (aunque solo usa 1, tiene 32 devoluciones de llamada, una para cada coincidencia, y en cada una de las 32 devoluciones de llamada se llama Math.random () y v. toString (16)).

El primer paso hacia el rendimiento es eliminar el RegEx y sus funciones de devolución de llamada y utilizar un bucle simple en su lugar. Esto significa que tenemos que lidiar con el - y 4 caracteres mientras que broofa no. Además, tenga en cuenta que podemos utilizar la indexación String Array para mantener su arquitectura de plantilla de Cadena resbaladiza:

function e1() {
  var u='',i=0;
  while(i++<36) {
    var c='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'[i-1],r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);
    u+=(c=='-'||c=='4')?c:v.toString(16)
  }
  return u;
}

Básicamente, la misma lógica interna, excepto que verificamos - o 4y usando un ciclo while (en lugar de replace() devoluciones de llamada) nos da una mejora de casi 3 veces.

El siguiente paso es pequeño en el escritorio, pero hace una diferencia decente en el móvil. Hagamos menos llamadas Math.random () y utilicemos todos esos bits aleatorios en lugar de tirar el 87% de ellos lejos con un buffer aleatorio que se desplaza cada iteración. También alejemos esa definición de plantilla del ciclo, por si acaso ayuda:

function e2() {
  var u='',m='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx',i=0,rb=Math.random()*0xffffffff|0;
  while(i++<36) {
    var c=m[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
    u+=(c=='-'||c=='4')?c:v.toString(16);rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
  }
  return u
}

Esto nos ahorra un 10-30% dependiendo de la plataforma. No está mal. Pero el próximo gran paso se deshace de las llamadas a la función toString junto con un clásico de optimización: la tabla de búsqueda. Una simple tabla de búsqueda de 16 elementos realizará el trabajo de toString (16) en mucho menos tiempo:

function e3() {
  var h='0123456789abcdef';
  var k='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
  /* same as e4() below */
}
function e4() {
  var h=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
  var k=['x','x','x','x','x','x','x','x','-','x','x','x','x','-','4','x','x','x','-','y','x','x','x','-','x','x','x','x','x','x','x','x','x','x','x','x'];
  var u='',i=0,rb=Math.random()*0xffffffff|0;
  while(i++<36) {
    var c=k[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
    u+=(c=='-'||c=='4')?c:h[v];rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
  }
  return u
}

La próxima optimización es otro clásico. Como solo manejamos 4 bits de salida en cada iteración de bucle, reduzcamos el número de bucles a la mitad y procesemos 8 bits en cada iteración. Esto es complicado ya que todavía tenemos que manejar las posiciones de bits que cumplen con RFC, pero no es demasiado difícil. Luego tenemos que crear una tabla de búsqueda más grande (16x16 o 256) para almacenar 0x00 - 0xff, y la compilamos solo una vez, fuera de la función e5 ().

var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e5() {
  var k=['x','x','x','x','-','x','x','-','4','x','-','y','x','-','x','x','x','x','x','x'];
  var u='',i=0,rb=Math.random()*0xffffffff|0;
  while(i++<20) {
    var c=k[i-1],r=rb&0xff,v=c=='x'?r:(c=='y'?(r&0x3f|0x80):(r&0xf|0x40));
    u+=(c=='-')?c:lut[v];rb=i%4==0?Math.random()*0xffffffff|0:rb>>8
  }
  return u
}

Intenté un e6 () que procesa 16 bits a la vez, aún utilizando la LUT de 256 elementos, y mostró los rendimientos decrecientes de la optimización. Aunque tuvo menos iteraciones, la lógica interna se complicó por el aumento del procesamiento, y se realizó de la misma manera en el escritorio, y solo ~ 10% más rápido en el móvil.

La técnica de optimización final para aplicar: desenrollar el ciclo. Como estamos haciendo un bucle en un número fijo de veces, técnicamente podemos escribir todo esto a mano. Intenté esto una vez con una sola variable aleatoria r que seguí reasignando, y el rendimiento se derrumbó. Pero con cuatro variables asignadas datos aleatorios por adelantado, luego usando la tabla de búsqueda y aplicando los bits de RFC apropiados, esta versión los fuma todos:

var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e7()
{
  var d0 = Math.random()*0xffffffff|0;
  var d1 = Math.random()*0xffffffff|0;
  var d2 = Math.random()*0xffffffff|0;
  var d3 = Math.random()*0xffffffff|0;
  return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+
    lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+
    lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
    lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
}

Modificado: http://jcward.com/UUID.js - UUID.generate()

Lo curioso es que generar 16 bytes de datos aleatorios es la parte fácil. Todo el truco es expresarlo en formato de cadena con cumplimiento de RFC, y se lleva a cabo de manera más estricta con 16 bytes de datos aleatorios, un bucle desenrollado y una tabla de búsqueda.

Espero que mi lógica sea correcta, es muy fácil cometer un error en este tipo de trabajo tedioso. Pero los resultados se ven bien para mí. ¡Espero que hayáis disfrutado este enloquecido viaje a través de la optimización de código!

Ser aconsejado: mi objetivo principal era mostrar y enseñar posibles estrategias de optimización. Otras respuestas cubren temas importantes como colisiones y números verdaderamente aleatorios, que son importantes para generar buenos UUID.


305



Aquí hay un código basado en RFC 4122, sección 4.4 (Algoritmos para crear un UUID a partir de un número verdaderamente aleatorio o pseudoaleatorio).

function createUUID() {
    // http://www.ietf.org/rfc/rfc4122.txt
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
    s[8] = s[13] = s[18] = s[23] = "-";

    var uuid = s.join("");
    return uuid;
}

136



El GUID más rápido como el método del generador de cadenas en el formato XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. Esto no genera un GUID estándar.

Diez millones de ejecuciones de esta implementación toman solo 32.5 segundos, que es el más rápido que he visto en un navegador (la única solución sin bucles / iteraciones).

La función es tan simple como:

/**
 * Generates a GUID string.
 * @returns {String} The generated GUID.
 * @example af8a8416-6e18-a307-bd9c-f2c947bbb3aa
 * @author Slavik Meltser ([email protected]).
 * @link http://slavik.meltser.info/?p=142
 */
function guid() {
    function _p8(s) {
        var p = (Math.random().toString(16)+"000000000").substr(2,8);
        return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
    }
    return _p8() + _p8(true) + _p8(true) + _p8();
}

Para probar el rendimiento, puede ejecutar este código:

console.time('t'); 
for (var i = 0; i < 10000000; i++) { 
    guid(); 
};
console.timeEnd('t');

Estoy seguro de que la mayoría de ustedes comprenderá lo que hice allí, pero tal vez haya al menos una persona que necesite una explicación:

El algoritmo:

  • los Math.random() función devuelve un número decimal entre 0 y 1 con 16 dígitos después del punto de fracción decimal (para ejemplo 0.4363923368509859)
  • Luego tomamos este número y convertimos a una cadena con base 16 (del ejemplo anterior, obtendremos 0.6fb7687f)
    Math.random().toString(16).
  • Luego cortamos el 0. prefijo (0.6fb7687f => 6fb7687f) y obtener una cadena con ocho hexadecimales personajes largos.
    (Math.random().toString(16).substr(2,8).
  • A veces el Math.random()la función volverá número más corto (por ejemplo 0.4363), debido a los ceros al final (del ejemplo anterior, en realidad el número es 0.4363000000000000) Es por eso que estoy agregando a esta cadena "000000000" (una cadena con nueve ceros) y luego cortarlo con substr() función para hacer exactamente nueve caracteres (llenando ceros a la derecha).
  • La razón para agregar exactamente nueve ceros se debe al peor escenario posible, que es cuando el Math.random() la función devolverá exactamente 0 o 1 (probabilidad de 1/10 ^ 16 para cada uno de ellos). Es por eso que necesitamos agregarle nueve ceros ("0"+"000000000" o "1"+"000000000"), y luego cortarlo del segundo índice (3er carácter) con una longitud de ocho caracteres. Para el resto de los casos, la adición de ceros no dañará el resultado porque de todos modos lo está cortando.
    Math.random().toString(16)+"000000000").substr(2,8).

La Asamblea:

  • El GUID está en el siguiente formato XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.
  • Dividí el GUID en 4 piezas, cada pieza dividida en 2 tipos (o formatos): XXXXXXXX y -XXXX-XXXX.
  • Ahora estoy construyendo el GUID usando estos 2 tipos para ensamblar el GUID con llamadas 4 piezas, de la siguiente manera: XXXXXXXX  -XXXX-XXXX  -XXXX-XXXX  XXXXXXXX.
  • Para diferir entre estos dos tipos, agregué un parámetro de indicador a una función de creador de pares _p8(s), el s parámetro le dice a la función si agregar guiones o no.
  • Eventualmente construimos el GUID con el siguiente encadenamiento: _p8() + _p8(true) + _p8(true) + _p8()y devolverlo

Enlace a esta publicación en mi blog

¡Disfrutar! :-)


78



var uniqueId = Math.random().toString(36).substring(2) 
               + (new Date()).getTime().toString(36);

Si las ID se generan con más de 1 milisegundo de diferencia, son 100% únicas.

Si se generan dos identificadores en intervalos más cortos, y suponiendo que el método aleatorio es verdaderamente aleatorio, esto generaría identificaciones que son 99.99999999999999% probables de ser globalmente únicas (colisión en 1 de 10 ^ 15)

Puede aumentar este número agregando más dígitos, pero para generar 100% de ID únicos necesitará usar un contador global.

document.getElementById("unique").innerHTML =
  Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
<div id="unique">
</div>


68



Aquí hay una combinación de la respuesta más votado, con una solución para Colisiones de Chrome:

generateGUID = (typeof(window.crypto) != 'undefined' && 
                typeof(window.crypto.getRandomValues) != 'undefined') ?
    function() {
        // If we have a cryptographically secure PRNG, use that
        // https://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript
        var buf = new Uint16Array(8);
        window.crypto.getRandomValues(buf);
        var S4 = function(num) {
            var ret = num.toString(16);
            while(ret.length < 4){
                ret = "0"+ret;
            }
            return ret;
        };
        return (S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));
    }

    :

    function() {
        // Otherwise, just use Math.random
        // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
            return v.toString(16);
        });
    };

En jsbin si quieres probarlo


57



Aquí hay una solución con fecha del 9 de octubre de 2011 de un comentario del usuario jed a https://gist.github.com/982883:

UUIDv4 = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)}

Esto logra el mismo objetivo que la respuesta actual mejor calificada, pero en más de 50 bytes mediante la explotación de coerción, recursión y notación exponencial. Para los curiosos cómo funciona, aquí está la forma anotada de una versión anterior de la función:

UUIDv4 =

function b(
  a // placeholder
){
  return a // if the placeholder was passed, return
    ? ( // a random number from 0 to 15
      a ^ // unless b is 8,
      Math.random() // in which case
      * 16 // a random number from
      >> a/4 // 8 to 11
      ).toString(16) // in hexadecimal
    : ( // or otherwise a concatenated string:
      [1e7] + // 10000000 +
      -1e3 + // -1000 +
      -4e3 + // -4000 +
      -8e3 + // -80000000 +
      -1e11 // -100000000000,
      ).replace( // replacing
        /[018]/g, // zeroes, ones, and eights with
        b // random hex digits
      )
}

52



Aquí hay una implementación totalmente no conforme pero muy completa para generar un identificador único similar a GUID seguro para ASCII.

function generateQuickGuid() {
    return Math.random().toString(36).substring(2, 15) +
        Math.random().toString(36).substring(2, 15);
}

Genera 26 [a-z0-9] caracteres, produciendo un UID que es más corto y más exclusivo que los GUID que cumplen con RFC. Los guiones se pueden agregar trivialmente si importa la legibilidad humana.

Aquí hay ejemplos de uso y tiempos para esta función y varias de las otras respuestas de esta pregunta. La sincronización se realizó con Chrome m25, 10 millones de iteraciones cada una.

>>> generateQuickGuid()
"nvcjf1hs7tf8yyk4lmlijqkuo9"
"yq6gipxqta4kui8z05tgh9qeel"
"36dh5sec7zdj90sk2rx7pjswi2"
runtime: 32.5s

>>> GUID() // John Millikin
"7a342ca2-e79f-528e-6302-8f901b0b6888"
runtime: 57.8s

>>> regexGuid() // broofa
"396e0c46-09e4-4b19-97db-bd423774a4b3"
runtime: 91.2s

>>> createUUID() // Kevin Hakanson
"403aa1ab-9f70-44ec-bc08-5d5ac56bd8a5"
runtime: 65.9s

>>> UUIDv4() // Jed Schmidt
"f4d7d31f-fa83-431a-b30c-3e6cc37cc6ee"
runtime: 282.4s

>>> Math.uuid() // broofa
"5BD52F55-E68F-40FC-93C2-90EE069CE545"
runtime: 225.8s

>>> Math.uuidFast() // broofa
"6CB97A68-23A2-473E-B75B-11263781BBE6"
runtime: 92.0s

>>> Math.uuidCompact() // broofa
"3d7b7a06-0a67-4b67-825c-e5c43ff8c1e8"
runtime: 229.0s

>>> bitwiseGUID() // jablko
"baeaa2f-7587-4ff1-af23-eeab3e92"
runtime: 79.6s

>>>> betterWayGUID() // Andrea Turri
"383585b0-9753-498d-99c3-416582e9662c"
runtime: 60.0s

>>>> UUID() // John Fowler
"855f997b-4369-4cdb-b7c9-7142ceaf39e8"
runtime: 62.2s

Aquí está el código de tiempo.

var r;
console.time('t'); 
for (var i = 0; i < 10000000; i++) { 
    r = FuncToTest(); 
};
console.timeEnd('t');

52



De blog técnico de sagi shkedy:

function generateGuid() {
  var result, i, j;
  result = '';
  for(j=0; j<32; j++) {
    if( j == 8 || j == 12|| j == 16|| j == 20) 
      result = result + '-';
    i = Math.floor(Math.random()*16).toString(16).toUpperCase();
    result = result + i;
  }
  return result;
}

Existen otros métodos que implican el uso de un control ActiveX, ¡pero aléjese de estos!

EDIT: pensé que valía la pena señalar que ningún generador de GUID puede garantizar claves únicas (verifique Artículo de wikipedia) Siempre hay una posibilidad de colisiones. Un GUID simplemente ofrece un universo de claves lo suficientemente grande para reducir el cambio de colisiones a casi cero.


30