1

Tengo dos objetos, y quiero que me los devuelva permutado en JavaScript, es decir, que si yo ingreso a y b como argumentos, me devuelva [b,a], cambiando el orden.

Pero yo no quiero una función que solo me devuelva el resultado permutado, sino que además me cambie los valores de los objetos (también pueden ser arrays) ingresados.

En el siguiente código, no sirve ninguna de las dos funciones permutar. La primera no, porque no cambia los valores, y solo devuelve los valores permutados. La segunda tampoco, porque aunque devuelve los valores permutados, solamente cambia los valores dentro de la función, pero por fuera siguen igual:

var funciones_permutar=[
  function permutar(a,b)
  {
      return [b,a]
  },
  function permutar(a,b)
  {
    var c=a
    a=b
    b=c
    return [a,b]
  }
]
for(var i=0;i<funciones_permutar.length;i++)
{
  var objeto_1={d:2,e:3}
  var objeto_2={f:4}
  
  console.log("Función permutar "+(i+1))

  //Devuelve correctamente.
  console.log(JSON.stringify([objeto_1,objeto_2]))
  console.log(JSON.stringify(funciones_permutar[i](objeto_1,objeto_2)))

  //Devuelve [{"d":2,"e":3},{"f":4}], pero debería devolver [{"f":4},{"d":2,"e":3}]
  console.log(JSON.stringify([objeto_1,objeto_2]))
}

5 Answers5

1

Explicación

Te explico lo que sucede detrás de escenas

;(function()
{
"use strict"

function intercambiar(a,b)
{
    var c = a // ok
    a = b // mal
    b = c // mal
}   

}())

El hacer a = b equivale a hacer var a = b es decir ahora la la variable LOCAL a apunta a b, lo mismo sucede con la otra linea b = c equivale a hacer var b = c, el anterior código se puede re-escribir como

;(function()
{
"use strict"

function intercambiar(a,b)
{
    var a  // esto es redudante
    var b  // esto es redudante

    var c = a // ok
    a = b  
    b = c
}   

}())

Las primeras dos líneas son redudantes, puesto que se hacen automáticamente al llamar una funcion, no obstante al verlo de esta forma podemos visualizar fácilmente que tanto a como b, son variables LOCALES.

Alternativas

En javascript todo es un objeto, incluso contextos es normal crear un espacio donde podemos colocar variables y permutarlos, por defecto el espacio global en navegadores es window no obstante en nodejs el espacio global es global

;(function()
{
"use strict"

function intercambiar(contexto, var1, var2)
{
 var aux = contexto[var1]
 contexto[var1] = contexto[var2]
 contexto[var2] = aux
 return [contexto[var1], aux]
} 

var global = {} // nuestro contexto
global.a = "foo"
global.b = "bar"

console.log(global) // {a: "foo", b: "bar"}
console.log(intercambiar(global, "a", "b")) // [bar, foo]
console.log(global) // {a: "bar", b: "foo"}
}())

Como puedes ver es algo sumamente sencillo, espero haber aclarado las dudas

Una solución bruta y tosca es la siguiente

function intercambiar(a,b)
{
    var aux = this[a]
    this[a] = this[b]
    this[b] = aux
    return [this[a], aux]
}

var a = {clave : 1234}
var b = ["hola" , "mundo"]
console.log([a,b])
console.log(intercambiar("a","b"))
console.log([a,b])

Una forma ineficiente, bruta y tosca

Object.prototype.encontrar = function(valor)
{
  for (var clave in this)
    {
      if (this[clave] == valor) { return clave }
    }
  return undefined
}

function permutar(a,b)
{
  var a = this.encontrar(a)
  var b = this.encontrar(b)
  if(!a || !b) { return [] }

  var aux = this[a]
  this[a] = this[b]
  this[b] = aux

  return [this[a], aux]
}

var a = {clave: 1123}
var b = ["hola", "mundo"]

console.log([a,b])
console.log(permutar(a,b))
console.log([a,b])

Nota: el anterior código no funciona en la consola embebida de stackoverflow, de probarlo debéis hacerlo directamente en el navegador

Eduen Sarceño
  • 2,072
  • 10
  • 21
  • En mi caso, el contenido de `a` y `b` no son cadenas, más bien son objetos o arrays. –  Jul 08 '17 at 03:45
  • javascript no es tipado, el tipo de variable es irrelevante, todo es un objeto, funciones, cadenas, arrays, mapas, eventos, etc, cambia los valores y verás que no importa – Eduen Sarceño Jul 08 '17 at 03:50
  • He actualizado mi respuesta, revisa la sección #Edit, lo he simplificado lo más que he podido sin obviar los aspectos claves y seguridad del código. – Eduen Sarceño Jul 08 '17 at 03:54
  • Lo mejor sería que los parámetros sean las variables mismas en vez de los strings. De todos modos la solución está bastante bien. –  Jul 08 '17 at 05:17
  • @ArtEze: Lo opuesto a una variable es un literal . "string" es un tipo de dato :) – Rubén Jul 08 '17 at 05:33
  • @Rubén Aunque tu comentario es informativo, yo me refiero a la variable para el objeto, y string para el nombre del objeto. –  Jul 08 '17 at 05:37
  • @ArtEze Me temo que es imposible hacerlo sin strings de manera eficiente, se podría hacer por valor pero se debería buscar en el objeto a qué clave pertenece el valor el cual requeriría de almenos Omega(n lg(n)) así que la complejidad de intercambiar dos valores sería al menos Omega( (n lg(n))^2) mientras que con strings el tiempo es de Theta(1) – Eduen Sarceño Jul 08 '17 at 05:41
  • @EduenSarceño Me serviría bastante la manera no eficiente. ¿Podrías ponerla? –  Jul 08 '17 at 05:43
  • Análogamente en JavaScript un string sirve como puntero a un puntero, toda variable es un puntero a una estructura la única forma de cambiar directamente su valor (al que referencia) es mediante un puntero a puntero, que en este caso sólo podemos hacerlo mediante strings por las restricciones de JavaScript – Eduen Sarceño Jul 08 '17 at 05:49
  • 1
    @ArtEze me temo que no estoy en el ordenador pero básicamente sería recorrer todos los elementos de un objeto, hasta que objeto[clave] == valor, almacenar el string clave y posteriormente hacer objeto[clave] = valor2, el problema de hacer esto es que la complejidad es O(n) y por defecto se guardan las variables en el objeto window el cual tiene al menos unas 500 entradas! Es demasiado ineficiente. – Eduen Sarceño Jul 08 '17 at 05:53
  • 1
    @ArtEze he actualizado mi respuesta, por alguna razón el script no funciona en los snippets, no obstante sí he probado el código en mi navegador, por lo tanto supongo que es asunto de seguridad de iframe - parent – Eduen Sarceño Jul 08 '17 at 20:09
  • 1
    @EduenSarceño: En tu respuesta afirmas que en JavaScript todo es un objeto, pero esto no es así. Más detalles en [Todo en JavaScript es un objeto ¿cierto o falso?](https://es.stackoverflow.com/q/85853/65) – Rubén Jul 10 '17 at 01:01
1
  1. En JavaScript las variables hacen referencia a objetos
  2. Si deseamos cambiar el objeto al cual hacer referencia una variable, debemos usar el nombre de esa variable en la asignación del objeto.

Ejemplo:

var objeto_1 = {
  d: 2,
  e: 3
}
var objeto_2 = {
  f: 4
}

// Cambia el orden de los objetos y los devuelve como una arreglo (array)
function permutar(a, b) {
  var permuta = [b, a];
  // Para asignar un objeto a una variable debemos usar el nombre de la 
  // variable en cuestión
  objeto_1 = permuta[0];
  objeto_2 = permuta[1];
  return permuta;
}

console.log(JSON.stringify([objeto_1, objeto_2]));
var permuta = permutar(objeto_1, objeto_2);
console.log(JSON.stringify(permuta));


console.log(JSON.stringify([objeto_1, objeto_2]));
Rubén
  • 10,857
  • 6
  • 35
  • 79
1

Hay dos escenarios posibles, uno es que se quieran permutar objetos del mismo tipo de dato, es decir, dos arrays, o dos objetos. El otro escenario es que se quiera permutar dos objetos de tipos diferentes, es decir, un objeto y un array, o un array y un objeto.

Si se quiere permutar un objetos con tipos de datos diferentes, no es posible hacerlo completamente bien, pero si se quiere permutar objetos con el mismo tipo de dato, se puede hacer correctamente. Esto es así porque no es posible cambiar el tipo de dato de un array por un objeto, es decir que un array siempre será un array, y no un objeto. En otras palabras, a un array no se le puede eliminar la propiedad length.

La función permutar que propongo, lo que hace es lo siguiente:

  1. Copiar los dos objetos (referenciar no) a permutar.
    • Ya que no es posible copiarlo exactamente, se hace de la mejor manera posible. Es decir, volcar todas las propiedades de un objeto a otro nuevo.
    • Determina si es un array, mediante el mismo objeto, o un booleano auxiliar.
    • Devuelve otro objeto o array dependiendo de lo determinado.
  2. Recibe un array auxiliar que determina si los objetos a permutar son objetos o arrays.
    • Si no lo recibe, lo genera mediante los objetos ingresados.
  3. Para cada uno de los objetos ingresados, se procede de la siguiente manera:
    1. Vaciar el objeto.
      • Esto borra todas las propiedades del objeto.
      • También, no los borra solamente dentro de la función, sino que al salir de la función, el objeto debería estar vacío.
      • Dependiendo de si está determinado como objeto o array, puede poner length en 0, o borrar esa propiedad, ya que los objetos no deberían tenerla.
    2. He creado una función para absorber el tipo de dato. Lo que hace es poner que un objeto tenga el mismo tipo de dato. En caso de que no se pueda, queda en el array auxiliar.
      • El objeto ingresado absorbe el tipo de dato de la copia del otro objeto. El tipo de dato está almacenado en el array auxiliar.
      • En la primer iteración, el primer objeto absorbe a la copia del segundo objeto.
      • En la segunda iteración, el segundo objeto absorbe a la copia del primer objeto.
      • Esto cambia los valores del array auxiliar, de modo que se pueda notar el cambio al salir de la función.
    3. Agrega al objeto, todas las propiedades del otro objeto.
      • En la primera iteración, agrega al primero las del segundo.
      • En la segunda iteración, agrega al segundo las del primero.
      • Al igual que en los pasos anteriores, también cambia los objetos para que se vean cambiados fuera de la función.
  4. Al array auxiliar, que anteriormente tenía 4 elementos, dos para las copias, y dos para los objetos ingresados, se lo modifica para que tenga solo dos. Esto significa que al primer elemento, es true si el segundo objeto ingresado era un array, y el segundo elemento es true si el primer objeto era un array. Si es false, significa que estaban determinados como objetos en vez de arrays.
    • Con esta acción ocurre como en las otras, cambia el array, en vez de reemplazarlo por otro.
  5. Devuelve un array con los objetos permutados, y además con el array auxiliar.
    • En vez de devolver los objetos originales modificados, lo que hace es mostrar los resultados, primero copiando los objetos, teniendo en cuenta el array auxiliar.
    • Esto es así, ya que un array convertido en objeto, no puede mostrarse naturalmente como objeto, y es necesario crear otro objeto con las características adaptadas.

/*Función que genera un array de cada elemento de un array ingresado:
  Ejemplo: es_array([ [], {}, [] ])
  -> [ true, false, true ]
*/
function es_array(objetos,son_arrays)
{
  //Si no existe el array auxiliar ingresado, lo crea.
  if(son_arrays==undefined){son_arrays=[]}
  
  //Determina si es array cada objeto.
  for(var i in objetos){son_arrays.push(Array.isArray(objetos[i]))}
  
  return son_arrays
}

/*Función que borra todas las propiedades del objeto.
 Ejemplos: [0,1,2,3] -> []
           {a:2,b:3} -> {} */
function vaciar(objeto,objeto_es_array)
{
  //Borra las propiedades del objeto ingresado.
  for(var i in objeto){delete objeto[i]}
  
  /*Determina si el objeto necesita la propiedad length dependiendo de
    si es o no un array*/
  if(objeto_es_array){objeto.length=0}else{delete objeto.length}
  
  return objeto
}

/*Absorbe el tipo de dato del segundo objeto al primero.
 Ejemplos: [] {} -> {} {}
           {} [] -> [] [] */
function absorber_tipo_de_dato(uno,actual_son_arrays)
{
  //Asigna a son_arrays el array auxiliar.
  var son_arrays=actual_son_arrays[0]
  
  /*Cambia la propiedad length del primer objeto ingresado dependiendo
    de si el segundo es array. */
  if(son_arrays[actual_son_arrays[2]]){uno.length=0}else{delete uno.length}
  
  /*Cambia al array auxiliar para que los dos valores queden iguales. */
  son_arrays[actual_son_arrays[1]]=son_arrays[actual_son_arrays[2]]

  return son_arrays
}

/*Función que agrega valores al primer objeto mediante una propiedad
  que está en el segundo objeto.
La funcionalidad es: uno[i]=dos[i] */
function agregar(uno,dos,i,uno_es_array)
{
  /*Si el primer objeto es array, la propiedad es un número, y por eso
    aumenta la longitud del array hasta un valor inmediatamente
    mayor a ese número. */
  if(uno_es_array)
  {
    i=+i
    if(uno.length<=i){uno.length=i+1}
  }
  
  /*Asigna la propiedad i al primer objeto, obteniéndola
    desde el segundo objeto. */
  uno[i]=dos[i]
  
  return uno
}

//Copia todas las propiedades de un objeto a otro nuevo.
function copiar(objeto,objeto_es_array)
{
  var copia
  
  //Declara y determina el booleano auxiliar si no existe.
  if(objeto_es_array==undefined){objeto_es_array=es_array([objeto])[0]}
  
  /*Convierte a objeto o array según está determinado
    en el booleano auxiliar. */
  if(objeto_es_array){copia=[]}else{copia={}}
  
  /*Agrega cada propiedad a la copia desde el objeto ingresado. */
  for(var i in objeto){agregar(copia,objeto,i)}

  return copia
}

//Función principal, que permuta los objetos.
function permutar(uno,dos,son_arrays)
{
  //Pone los objetos originales junto con sus copias.
  var objetos=[copiar(uno),copiar(dos),uno,dos]
  
  /* Si no existe el array auxiliar lo declara y lo determina. */
  if(son_arrays==undefined)
  {
    //Genera el array auxiliar.
    son_arrays=es_array(objetos)
  }
  else
  {
    if(son_arrays.length==0)
    {
      //Si el array auxiliar está vacío, lo llena.
      es_array(objetos,son_arrays)
    }
    else
    {
      /* Replica los valores del array auxiliar y los pone al final.
        Ejemplo: [false,true] -> [false,true,false,true]
      */
      for(var i=0;i<2;i++){son_arrays.push(son_arrays[i])}
    }
  }
  
  //Itera 2 veces, una para cada objeto ingresado.
  for(var i=0;i<2;i++)
  {
    /* Declara variables, el original, la copia del objeto no actual y
      un array que contiene al array auxiliar junto con las posiciones
      que se quiere cambiar de ese array auxiliar. */
    var original=objetos[i+2]
    var copia=objetos[1-i]
    var actual_son_arrays=[son_arrays,i,1-i]

    //Vacía el objeto del objeto de la iteración actual.
    vaciar(original,son_arrays[i+2])
    
    //Absorbe el tipo de dato del objeto actual.
    absorber_tipo_de_dato(original,[son_arrays,i+2,1-i])
    
    /*Agrega todas las propiedades al objeto actual desde
      la copia del objeto no actual. */
    for(var j in copia){agregar(original,copia,j,son_arrays[i+2])}
  }
  
  /*Cambia el array auxiliar para mostrar sus dos últimos valores. */
  for(var i=0;i<2;i++){son_arrays.shift()}
  
  return [copiar(uno,son_arrays[0]),copiar(dos,son_arrays[1]),son_arrays]
}

//Array auxiliar.
var son_arrays=[]

//Objetos a permutar.
var objeto_1={d:2,e:3}
var objeto_2=[0,1,2,3,4,5]

console.log("Se muestran 3 elementos. Los dos primeros, son los objetos"+
  " a permutar. El último es un array auxiliar con dos booleanos, que"+
  " determinan si cada uno de los objetos resultantes, es un array "+
  "(si es true)."
)
console.log("Original: ",
  JSON.stringify([objeto_1,objeto_2,son_arrays]))

console.log("Permutado mostrado correctamente: ",
  JSON.stringify(permutar(objeto_1,objeto_2,son_arrays)))

console.log("Permutado como son realmente: ",
  JSON.stringify([objeto_1,objeto_2,son_arrays]))
0

Introducción

Aunque ya había dejado una respuesta, aquí dejo otra usando this y los nombres de las variables como llaves esto con la finalidad de que la solución sea modular, es decir, que si los nombres de las variables cambian, no sea necesario cambiar la función. También se hace uso de funciones flecha.

Nota: Esta solución funciona para variable globales, como estaba declaradas en la revisión 2 de la pregunta.

Se presentan dos escenarios, dos objetos del mismo tipo, y dos objetos de tipos distintos, siendo el primero un objeto {} y el otro una matriz [].

Escenario 1: Dos objetos del mismo tipo, {}

var objeto_1 = {d: 2,e: 3}
var objeto_2 = {f: 4}

/*
 * Intercambia la asignación de objetos a dos variables y devuelve una 
 * arreglo (array) con los objetos permutados
 *
 * @param {String} a nombre de la primer variable
 * @param {String} b nombre de la segunda variable
 *
 * @returns {Array}    
 */

function permutar(a, b) {

  // Intercambiar asignación de objetos entre dos variables
  var swap = x => x;
  this[a] = swap(this[b],this[b]=this[a])

  // Devolver los objetos permutados en una matriz
  return [this[a],this[b]];
}

console.log("--- valores originales ---");
console.log(JSON.stringify([objeto_1, objeto_2]));

/* Llamar la función solución y asignar resultado a variable */
var permuta = permutar("objeto_1", "objeto_2");
console.log("--- resultado de la función solución ---");
console.log(JSON.stringify(permuta));

console.log("--- verificación de intercambio ---")
console.log(JSON.stringify([objeto_1, objeto_2]));

Escenario 2: Dos objetos de tipo distinto, un objeto {} y una matriz []

var objeto_1 = {d: 2,e: 3}
var matriz_1 = [4]

/*
 * Intercambia la asignación de objetos a dos variables y devuelve una 
 * arreglo (array) con los objetos permutados
 *
 * @param {String} a nombre de la primer variable
 * @param {String} b nombre de la segunda variable
 *
 * @returns {Array}    
 */

function permutar(a, b) {

  // Intercambiar asignación de objetos entre dos variables
  var swap = x => x;
  this[a] = swap(this[b],this[b]=this[a])

  // Devolver los objetos permutados en una matriz
  return [this[a],this[b]];
}

console.log("--- valores originales ---");
console.log(JSON.stringify([objeto_1, matriz_1]));

/* Llamar la función solución y asignar resultado a variable */
var permuta = permutar("objeto_1", "matriz_1");
console.log("--- resultado de la función solución ---");
console.log(JSON.stringify(permuta));

console.log("--- verificación de intercambio ---")
console.log(JSON.stringify([objeto_1, matriz_1]));

Explicación

this es una palabra clave de JavaScript que cambia su comportamiento según el contexto y como sea llamado. En este caso, al usar el nombre de las variables como llave, se llama a la variable global que tienen ese nombre.

swap es una variable a la que le ha asignado una función flecha que devuelve el objeto recibido como argumento. Se ha preferido este tipo de función por su brevedad.

Referencia

Respuesta a Swap two objects in JavaScript

Preguntas relacionadas

Rubén
  • 10,857
  • 6
  • 35
  • 79
-2

Eso solo se puede hacer en lenguajes que al llamar una funcion pasan (o pueden pasar) los argumentos por referencia (como es el caso de C++). Javascript (como la gran mayoría de los lenguajes populares) pasa los argumentos por valor. Así que no, no se puede.

Claro que es posible si pasas los argumentos como un array (en es caso simplemente invertes los valores del array), pero supongo que no es eso lo que quieres. Y claro que también es posible si desde dentro de la función puedo "ver" las variables originales, pero, de nuevo, eso es trivial.

Por qué no se puede? Podemos empezar a pensarlo así: supongamos que a y b son enteros. Podemos lograr lo deseado?

  var a= 1;
  var b= 2;
  permutar(a,b);
  // quiero que de aquí en más a y b tengan sus valores permutados

  function permutar(x,y) {
    .... ??
  } 

Un poco de reflexión muestra que esto no es posible. Porque al momento de invocar la función los valores que tienen a y b se copian en las variables locales x y , y cualquier modificación que se haga sobre x e y no impactarán en los valores de a b .

Lo mismo vale si a y b son objetos. Porque en Javascript una variable que referencia a un objeto es, justamente, una referencia - que podemos pensarla como una especie de puntero o (mejor) un "handle": simplemente un numero con el que Javascript identifica internamente el objeto. Al llamar permutar(a,b) lo que se pasan (por copia!) son esos números. Y nada de lo que hagamos con esas copias de kas referencias dentro de la función cambiará las referencias "de afuera".

leonbloy
  • 2,513
  • 7
  • 18
  • Me serviría, pero me gustaría más que los cambie ingresando los argumentos por separado. –  Jul 08 '17 at 02:58
  • Bueno, invertir un array es bastante sencillo, lo haces en un loop (o si son dos, elementos, siemplemente los intercambias). – leonbloy Jul 08 '17 at 03:03
  • Lo que digo, sería cambiar el contenido de tal _puntero_, pero que tenga los valores del otro objeto. –  Jul 08 '17 at 03:15
  • No se puede, en Javascript se pueden pasar punteros (solo se copian referencias). Traté de explicarlo en la respuesta. – leonbloy Jul 08 '17 at 03:20
  • Pero, por ejemplo, si yo hago `var b={c:2};function cambiar(a){return ++a.c};[cambiar(b),cambiar(b)]`, me devuelve `[3,4]`, es decir, cambia los valores aumentando en 1. –  Jul 08 '17 at 03:36
  • 1
    Todos los valores son pasados por referencia (exceptuando números), la diferencia radica en que en javascript al utilizar un parámetro como `l-value` simplemente cambia la posición en memoria a la que apunta el parámetro, no la dirección del objeto al que apunta. – Eduen Sarceño Jul 08 '17 at 04:21
  • Gracias a los votantes por recordarme que no debo perder tiempo en este sitio. – leonbloy Jul 08 '17 at 14:17
  • @leonbloy No era para que te lamentes. Puedes mirar nuevamente mi último comentario, creo que ahí está la clave para solucionarlo. –  Jul 09 '17 at 01:38