Crear un objeto cuyo estado cambie en la asignación


31

Me resulta profundamente extraño que esto sea posible en Ruby (no diré de inmediato cómo):

obj = #code redacted

print obj.state # Some value.

LValue = obj

print obj.state # Different value!

Su desafío es crear código aproximadamente de esta forma. Cree un objeto y asígnelo a una variable. Debe tener algún atributo definido (o método determinista e idempotente) como el stateanterior, que cambia después de que el objeto se asigna a un nuevo identificador ( LValuearriba), incluso si todavía usa el identificador objanterior ( arriba) para referirse a él.

Edite para enfatizar : stateo el equivalente debe ser idempotente, por lo que crear un descriptor de acceso que modifique el valor, o por cualquier otro motivo, arroje resultados diferentes cuando se lo llame varias veces seguidas, no es una solución válida. O, más simplemente, tiene que ser la asignación la que cambia el estado.

Cualquier idioma con asignación es elegible, aunque probablemente haya algunos en los que no haya una solución totalmente legítima. Publicaré mi respuesta de Ruby si nadie más la recibe después de unos días, y aceptaré las respuestas mejor votadas de forma continua.


¿Debe LValue = objser necesaria la línea para statecambiar realmente? (Podría hacer una propiedad en C # que se incremente cada vez que la obtenga)
Tim S.

2
Sí, eso es lo que pretendía al decir que el método debía ser idempotente. Lo editaré para aclararlo.
histocrat

Vale gracias. Debo haber pasado por alto esa parte.
Tim S.

44
¿Simplemente devolver el recuento del objeto funcionaría?
Nick T

¿Tendrían alteraciones destructivas en el objeto mismo? EmacsLisp: (setq a (list "val")) (setq b (nconc a "val2"))por ejemplo. atermina evaluando como ("val" . "val2")en ese punto.
Jonathan Leech-Pepin el

Respuestas:


30

C ++

Esto es trivial usando las herramientas adecuadas.

#include <iostream>

using namespace std;

class Obj {
public:
   int state;

   Obj& operator= (Obj& foo) {
      foo.state++;
      this->state = foo.state - 2;
      return *this;
   }
};

int main() {
   Obj a, b, c, d;
   a.state = 3;
   b.state = 4;

   cout << a.state << " " << b.state << "\n";

   c = a;
   d = b;

   cout << a.state << " " << b.state << " " << c.state << " " << d.state << "\n";

   return 0;
}

Salida:

3 4
4 5 2 3

12
En el momento en que vi el título, supe que alguien haría una sobrecarga del operador. Es la forma obvia. Tener un voto a favor.

17

PHP (compilación de depuración,> = 5.4)

Usamos el recuento del objeto en un captador. (Entonces, por la asignación, el recuento aumenta y los cambios de valor)

class State {
    public function __get($arg) {
        ob_start();
        debug_zval_dump($this); // e.g. "object(State)#1 (0) refcount(6)"
        return ob_get_clean()[29];
    }
}

$obj = new State;
var_dump($obj->state);
$a = $obj;
var_dump($obj->state);

14

DO#

Dos opciones simples:

class Obj
{
    public int state;
    public static implicit operator int(Obj o)
    {
        return o.state++;
    }
}

static int LValueI;
static Obj LValueM { set { value.state++; } }
static void Main()
{
    var obj = new Obj { state = 1 };
    LValueI = obj;
    Console.WriteLine(obj.state); //2, caused by the implicit cast.

    LValueM = obj;
    Console.WriteLine(obj.state); //3, caused by the property setter.
    Console.ReadLine();
}

O simplemente podríamos escribir en la misma memoria:

[StructLayoutAttribute(LayoutKind.Explicit)]
class Program
{
    [FieldOffset(0)]
    int state = 1;
    [FieldOffset(1)]
    int LValue;

    void Test()
    {
        var obj = this;

        Console.WriteLine(state);  //1
        LValue = state;
        Console.WriteLine(state);  //257
        Console.ReadLine();
    }
    static void Main() { new Program().Test(); }
}

12

TeX, mucho más corto que otras respuestas aquí

\setbox0=\hbox{Hello world!} % Put stuff in the box 0.
\message{\the\wd0}           % Print the width of the box => non-zero
\setbox2=\box0               % Put the box instead in box 2.
\message{\the\wd0}           % Now box 0 is void, hence has zero width.

Como sistema de composición tipográfica, TeX tiene un tipo de "caja", que contiene material tipográfico. Dado que el caso de uso más común es mover este material, dividirlo, etc., en lugar de hacer copias de él, los cuadros normalmente se eliminan cuando se usan (o más bien, las variables de "cuadro" son punteros y solo un puntero a la vez puede señalar a una caja real en la memoria). No hay necesidad de magia.


8

C ++ 11 (Entonces ustedes se han olvidado de unique_ptr / shared_ptr :-))

#include <iostream>
#include <memory>
using namespace std;
int main() {
    std::unique_ptr<int> u1(new int(0)), u2;
    std::shared_ptr<int> s1 = std::make_shared<int>(0), s2;
    std::cout<<u1.get()<<" "<<u2.get()<<" "<<std::endl;
    std::cout<<s1.use_count()<<" "<<s2.use_count()<<" "<<std::endl;
    u2 = std::move(u1);
    s2 = s1;
    std::cout<<u1.get()<<" "<<u2.get()<<" "<<std::endl;
    std::cout<<s1.use_count()<<" "<<s2.use_count()<<" "<<std::endl;
   return 0;
}

7

Fortran 03

Esto es algo similar a la respuesta D de Hugo, pero está un poco más escondido (en parte porque ¿quién # $% ^ conoce Fortran orientado a objetos)?

module objects
   implicit none

   type ObjDef
      integer :: state
    contains
      procedure :: initObject
      procedure :: printObject
      procedure :: setNew
   end type
 contains
   subroutine initObject(this)
     class(ObjDef) :: this
     this%state = this%state + 1
   end subroutine initObject

   subroutine printObject(this)
     class(ObjDef) :: this
     print '(a,i0)',"this%state = ",this%state
   end subroutine printObject

   subroutine setNew(this,that)
     class(ObjDef) :: this,that
     that%state = this%state
   end subroutine setNew

end module objects

program objectChange
   use objects
   type(ObjDef) :: a,b

   call initObject(a)
   call printObject(a)
   call b%setNew(a)
   call printObject(a)
end program objectChange

La salida es

this%state = 1
this%state = 0

Si puedes descubrir lo que sucedió, ¡puntos extra para ti! Si no:

Al llamar al procedimiento setNewen la forma call b%setNew(a), bes implícitamente el primer argumento, no el segundo.


7

Potencia Shell

Esto crea un objeto cuya statepropiedad son los nombres de las variables que apuntan al objeto.

$a = @{}| Add-Member -MemberType:16 -PassThru state -Value {
        (gv|?{$this -eq $_.Value}|%{$_.Name}) -join ','} 

'Before: ' + $a.state
$b = $a
'After: ' + $a.state

Salida

Before: a,this
After: a,b,this

Nota: Esto no funciona si la asignación ocurre en un ámbito secundario.

'Before: ' + $a.state
&{$b = $a}
'After: ' + $a.state

Salidas

Before: a,this
After: a,this

¡Get-Variable es inteligente!
mazzy

5

Perl 5

Aquí hay una forma de hacerlo en Perl:

package Magic {
    sub new { bless {state => 1} }
    use overload '""' => sub { $_[0]{state}++ };
}
use feature 'say';

my $obj = new Magic;
say $obj->{state};
substr($_, 0) = $obj;
say $obj->{state};

Esto produce:

1
2

Explicación:

Esta es una aplicación directa de sobrecarga . Específicamente, sobrecargo el operador de conversión de cadenas "", que se llama cuando se asigna el objeto sobrecargado substr()(que, sí, es un valor legal en Perl).

También hay muchas variables especiales en Perl que stringifican cualquier cosa asignada a ellas. Por ejemplo, lo siguiente también funciona:

my $obj = new Magic;
say $obj->{state};
$0 = $obj;
say $obj->{state};

Solución alternativa

Aquí hay otra forma de hacerlo:

package Magic {
    use Devel::Peek 'SvREFCNT';
    sub new { bless \my $foo }
    sub state { SvREFCNT ${$_[0]} }
}
use feature 'say';

my $obj = new Magic;
say $obj->state;
my $other = $obj;
say $obj->state;

Aquí, statehay un método (podríamos convertirlo en un atributo con más travesuras de empate / sobrecarga, pero eso complicaría las cosas) que literalmente cuenta el número de referencias al objeto. Por lo tanto, a diferencia de la primera solución, en realidad tiene que asignar $obja una variable normal que puede contener una referencia de objeto para hacer que el estado cambie.


5

JavaScript

Ok, hice una versión más corta que funciona como SSCCE, pero ya no intenta analizar JavaScript correctamente, por lo que el recuento de referencias podría no funcionar cuando se coloca dentro de un script más complejo.

(function run () {
    var lineOne = getLine (1), a, b, x, y, z;
    var x = {
        get state () {
            var x=/([a-z]+)\s*=\s*([a-z]+)/,c;
            return 1 + Object.keys (c = run.toString ().split ('\n').slice (0,getLine (2)).filter (function (a) {return (x.test (a))}).reduce (function (a,b,c,d) {var r=b.match (x),t=r[2];while (a[t]){t=a[t]};a[r[1]]=t;return a}, {v:0})).reduce (function (a,b) {return (c[b]=="x"?1:0) + a},0)
        }
    };
    console.log (x.state);  //1
    console.log (x.state);  //1
    y = x;
    console.log (x.state);  //2
    z = y;
    console.log (x.state);  //3    
    a = z;
    b = a;
    console.log (x.state);  //5
    a = null;
    console.log (x.state);  //4
    b = null;
    console.log (x.state);  //3
})() //1 1 2 3 5 4 3 

function getLine(n) {
   try {
      to
   } catch (dat) {
      var stack = dat.stack.split('\n');
       for (var i = 0; i < stack.length; i++) {
           if (~stack[i].indexOf ('getLine')) break;          
       }
      return dat.stack.split ('\n')[i + ~~n].match (/:(\d+)/)[1] - ~~window.hasOwnProperty ('__commandLineAPI')
   }
}

2
¿Te importaría explicar qué estás haciendo?
Ryan

55
... ¿Qué demonios es esto? O_o
puerta

@Doorknob Un getter que devuelve el resultado, de llamar a una función, que cuenta la frecuencia con la que se hace referencia a un nombre de identificador como rval, en una expresión de asignación, dentro de un texto fuente dado hasta una línea dada, pasando su fuente fuente de funciones y la línea getter fue llamado como argumento. Todo lo demás es un tokenizador provisorio desordenado. --- No sé cómo más debería llamarlo . En otras palabras. En otras palabras: el captador cuenta el número de asignaciones de referencias a x hasta la línea desde la que se llamó, el resto es un tokenizador no terminado.
C5H8NNaO4

1
¡El más largo ... y el más ancho!
Nicolas Barbulesco

1
@NicolasBarbulesco Lo hice más corto
C5H8NNaO4

4

Pitón

Hace trampa un poco, pero ¿qué tal:

import gc
class A(object):
    @property
    def state(self):
        return len(gc.get_referrers(self))

a = A()
print a.state
b = {"x": a}
print a.state
a.y = a
print a.state
del a
print b["x"].state

4

C ++ 11

aunque esto se puede extender a otros idiomas que admitan destructores implícitos / explícitos

#include <iostream>
using namespace std;

class Foo {
    int *ptr;
public:
    Foo() {
        ptr = new int(0);
    }   
    int state() {
        return *ptr;
    }
    ~Foo() {
        (*ptr)++;
    }
};
int main() {
    Foo a, b;
    cout << a.state() << " " << b.state() << "\n";
    {
        Foo c, d;
        c = a;
        d = b;
    }
   cout << a.state() << " " << b.state()  << "\n";

   return 0;
}

El operador de asignación predeterminado realiza una copia superficial. Por lo tanto, el objeto receptor aún posee el puntero y cualquier cambio afecta implícitamente al objeto original;


1
Sí, un newsin un solo deleteen el programa. Aunque, para esta tarea, es lo suficientemente bueno, creo :)
Ruslan

¿Cuáles son los resultados?
Nicolas Barbulesco

1
Por lo que entiendo (C ++ está muy lejos ...), aquí la asignación no cambia el estado . De lo contrario, mueva la coutlínea hacia arriba antes de }y diga si eso funciona. :-)
Nicolas Barbulesco

4

Scala

Las conversiones implícitas le permiten lograr esto mientras asigna a una variable local normal:

import scala.language.implicitConversions

class Obj {
  var counter = 0
}

implicit def o2s(x: Obj): String = {
  x.counter += 1
  x.toString
}

val obj = new Obj
println(obj.counter)
val s: String = obj
println(obj.counter)

También puede lograr eso con los tipos inferidos:

var s = ""
s = obj

También puede usar un método de establecimiento personalizado, aunque eso requiere que el valor L sea un campo:

object L {
  var _value = new Obj
  def value = _value
  def value_=(x: Obj): Unit = {
    _value = x
    x.counter += 1
  }
}

val obj = new Obj
println(obj.counter)
L.value = obj
println(obj.counter)

3

re

struct Obj {
    int state;

    void opAssign (ref Obj other) {
        ++other.state;
    }
}

void main () {
    import std.stdio;

    Obj obj, lvalue;
    writeln(obj);
    lvalue = obj;
    writeln(obj);
}

Salida:

Obj(0)
Obj(1)

3

Rubí

Según lo prometido, aquí está la respuesta que inspiró la pregunta.

obj = Class.new { def self.state; to_s[/</] ? "Has not been assigned\n" : "Assigned to #{to_s}"; end }

print obj.state

LValue = obj

print obj.state

Class.newcrea una clase anónima Llamar to_sa una clase anónima da la representación de cadena predeterminada de los objetos, que se ve así #<Class:0x007fe3b38ed958>. Sin embargo, una vez que la clase ha sido asignada a una constante, se to_sconvierte en esa constante. En Ruby, una constante es una variable que comienza con una letra mayúscula, entoncesobj es una referencia a la clase que le permite permanecer en el anonimato.

Mi código se ajusta to_sen un statemétodo, por lo que la salida se convierte

Has not been assigned
Assigned to LValue

A diferencia de la mayoría de las soluciones aquí, esto solo funciona una vez: asignar obja otra constante no cambiará su representación de cadena, y tampoco asignará un nuevo valor a LValue.


3

En java

Pensé que esto era imposible en Java. Pero…

Clase principal :

public class MyAppOfCats {

  public static void main(String[] args) {
    Cat tom = new Cat();
    System.out.println(tom.state()); 
    // Output : NOT-BEST-CAT
    Cat.bestCat = tom;
    System.out.println(tom.state());
    // Output : BEST-CAT
  }

}

Clase gato:

public class Cat {

  static Cat bestCat;

  public Cat() {
    super();
  }

  public String state() {
      return ((this == Cat.bestCat) ? "BEST-CAT" : "NOT-BEST-CAT");
  }

}

Me inspiré en @tbodt.


1
Sé que no es código golf, pero te das cuenta de que puedes eliminar el constructor y sigue siendo el mismo, ¿verdad?
David Conrad

2
Esto no es "un objeto cuyo estado cambia en la asignación". Este es usted manipulando un valor global y luego imprimiendo algo basado en él. No es diferente Cat.x = 2y luego imprime Cat.x.
Chris Hayes

@Chris: el estado del objeto se basa en un "valor global". Entonces el estado del objeto cambia en la asignación. La pregunta dice ;-) que el estado puede ser un método determinista e idempotente. El estado de mi método () es tal método.
Nicolas Barbulesco

No, el estado del objeto cambia en esta asignación particular . Si lo hiciera, Cat otherCat = tomel estado no habría cambiado en absoluto. Me cuesta creer que esto cumpla con la letra o el espíritu de las reglas.
Chris Hayes

@ Chris - ¡Por supuesto que el objeto cambia en esta tarea! La pregunta solicita un objeto cuyo estado sea cambiado por la asignación. No para un objeto cuyo estado sea cambiado por cualquier asignación.
Nicolas Barbulesco

3

C ++

Este comportamiento se especifica realmente en el estándar (y es por eso que fue desaprobado).

#include<iostream>
#include<memory>
int main()
{
    std::auto_ptr<int> a(new int(0));
    std::cout<<a.get()<<'\n';
    std::auto_ptr<int> b = a;
    std::cout<<a.get()<<'\n';
}

Salida

some address
0

El proceso que causa esto es el mismo que la respuesta de Abhijit pero sin requerir una std::moverespuesta igual a la de marinus, pero usando una clase estándar en lugar de definirla yo mismo.

Editar: estoy agregando alguna explicación. En la salida, "alguna dirección" en realidad será un valor hexadecimal para la dirección del entero asignado. std::auto_ptrlibera su puntero de tiendas cuando se le asigna a otro auto_ptry establece su puntero interno en 0. Las llamadas get()recuperan el acceso al puntero de tiendas.


Sospecho que la "salida" aquí no es la salida real.
Nicolas Barbulesco

¿Puedes explicar qué se supone que debe hacer? ¿Especialmente el método get()? ¿Por qué devolvería 0 al final?
Nicolas Barbulesco

@Nicholas sí. Esta salida no es la salida real, sino una salida más general (tampoco tenía acceso a un compilador, así que no tenía un ejemplo de una dirección válida en ese momento).
JKor

1
Hm, esto no se compila en gcc 4.8.
Michael Hampton

1
Arregle los errores de compilación. Todavía hay advertencias si está compilando para c ++ 11 porque auto_ptrestá en desuso.
JKor

3

Pitón

import sys
class K:state = property(sys.getrefcount)

2

Python 2.x

No pude encontrar una manera adecuada de hacer esto sin definir una clase adicional.

class State(object):
    def __init__(self):
        self.state = 0
    def __set__(self, obj, other):
        # Keep different references
        other.state += 1
        self.state += 2

class Program(object):
    obj, value = State(), State() # Create two State-objects
    def __init__(self):
        print "Before assignment:", self.obj.state, self.value.state # 0 0
        self.value = self.obj # Set value to obj (supposedly)
        print "After  assignment:", self.obj.state, self.value.state # 1 2
        self.value = self.obj
        print "2nd    assignment:", self.obj.state, self.value.state # 2 4

Program()

2

Java

Todas las demás soluciones utilizan la forma de sobrecarga del operador de su idioma. Java no tiene sobrecarga del operador, así que pensé que estaba atascado. Pero se me ocurrió algo.

Aquí está la clase principal:

public class Program {
    public static void main(String[] args) {
        Thing thing = new Thing(0);
        System.out.println(thing.getState());
        Thing.otherThing = thing;
        Thread.sleep(1);
        System.out.println(thing.getState());
    }
}

Hay algunas líneas sospechosas, pero no harían nada si la Thingclase fuera completamente normal. No es:

public class Thing {
    private int state;

    public Thing(int state) {
        this.state = state;
    }

    public int getState() {
        return state;
    }

    // Please do your best to ignore the rest of this class.
    public static volatile Thing otherThing;
    static {
        Thread t = new Thread() {
            public void run() {
                Thing t = otherThing;
                while (true)
                    if (t != otherThing) {
                        t = otherThing;
                        t.state++;
                    }
            }
        };
        t.setDaemon(true);
        t.start();
    }
}

No se garantiza que funcione debido a los hilos, pero lo probé en JDK 1.8u5, y funciona allí.



@KyleKanos Se deshizo de todos los caracteres unicode> U + 00FF
tbodt

1

Lisp común

Defino estado como el número de variables especiales vinculadas a un vector. Entonces, la asignación a una variable especial cambia el estado.

(defgeneric state (object)
  (:documentation "Get the state of this object."))

(defmethod state ((object vector))
  ;; The state of a vector is the number of symbols bound to it.
  (let ((count 0))
    ;; Iterate each SYM, return COUNT.
    (do-all-symbols (sym count)
      ;; When SYM is bound to this vector, increment COUNT.
      (when (and (boundp sym) (eq (symbol-value sym) object))
    (incf count)))))

(defparameter *a* #(this is a vector))
(defparameter *b* nil)
(defparameter *c* nil)

(print (state *a*))
(setf *b* *a*)
(print (state *a*))
(print (state *a*))
(setf *c* *a*)
(print (state *a*))

Salida:

1 
2 
2 
3 

Solo funciona con asignaciones a variables especiales, no a variables léxicas, ni a ranuras dentro de un objeto.

Cuidado que do-all-symbols ve en todos los paquetes, por lo que pierde variables que no tienen paquete. Puede contar dos símbolos que existen en más de un paquete (cuando un paquete importa el símbolo de otro paquete).

Rubí

Ruby es casi lo mismo, pero defino estado como el número de constantes que se refieren a una matriz.

class Array
  # Get the state of this object.
  def state
    # The state of an array is the number of constants in modules
    # where the constants refer to this array.
    ObjectSpace.each_object(Module).inject(0) {|count, mod|
      count + mod.constants(false).count {|sym|
        begin
          mod.const_get(sym, false).equal?(self)
        rescue NameError
          false
        end
      }
    }
  end
end

A = %i[this is an array]
puts A.state
B = A
puts A.state
puts A.state
C = A
puts A.state

Salida:

state-assign.rb:9:in `const_get': Use RbConfig instead of obsolete and deprecated Config.
1
2
2
3

Esta es una generalización de la respuesta del histocrat a los objetos Ruby que no son clases o módulos. La advertencia aparece porque la constante Config carga automáticamente algún código que hizo la advertencia.


0

C ++

El resultado puede diferir en diferentes plataformas. Probado en ideone .

#include <iostream>
#include <cassert>
// File format: [ciiiiciiii...] a char (1 byte) followed by its state (4 bytes)
// Each group takes 5 bytes
char Buffer[30]; // 5*6, six groups

struct Group {
    char c;
    int state;
};

int main(void) {
    assert(sizeof(char) == 1);
    assert(sizeof(int) == 4);

    Group& first_group = *(Group*)(&Buffer[0]); // Group 1 is at 0
    Group& second_group = *(Group*)(&Buffer[5]); // Group 2 is at 5

    first_group.c = '2';
    first_group.state = 1234;

    std::cout << first_group.state << std::endl;

    second_group = first_group;

    std::cout << first_group.state << std::endl;

    return 0;
}

Salida:

1234
13010

0

DO#

class A
{
    public int N { get; set; }
    public override string ToString() { return N.ToString(); }
}
class B
{
    public int N { get; set; }
    public override string ToString() { return N.ToString(); }
    public static implicit operator A(B b) { b.N = -b.N; return new A { N = b.N }; }
}
public static void Test()
{
    A a = new A { N = 1 };
    B b = new B { N = 2 };
    Console.WriteLine("a is {0}, b is {1}", a, b);
    Console.WriteLine("a is {0}, b is {1}", a, b);
    a = b;
    Console.WriteLine("a is {0}, b is {1}", a, b);
    Console.WriteLine("a is {0}, b is {1}", a, b);
}

Salida:

a is 1, b is 2
a is 1, b is 2
a is -2, b is -2
a is -2, b is -2

¿Qué hace esto? ¿Esto está sobrecargando al operador =?
Nicolas Barbulesco

@Nicolas No exactamente. Es cuando se lanza de un Ba un A, porque implicit operator A(B b)tiene efectos secundarios.
ClickRick
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.