Créer un objet dont l'état change lors de l'affectation


31

Je trouve profondément bizarre que cela soit possible dans Ruby (je ne dirai pas immédiatement comment):

obj = #code redacted

print obj.state # Some value.

LValue = obj

print obj.state # Different value!

Votre défi est de créer du code à peu près de cette forme. Créez un objet et affectez-le à une variable. Il doit avoir un attribut défini (ou une méthode déterministe et idempotente) comme stateci-dessus, qui change après que l'objet est affecté à un nouvel identifiant ( LValueci-dessus), même si vous utilisez toujours l'ancien identifiant ( objci-dessus) pour vous y référer.

Modifier pour mettre l'accent : stateou l'équivalent doit être idempotent, donc la création d'un accesseur qui modifie la valeur, ou pour toute autre raison renvoie des résultats différents lorsqu'il est appelé plusieurs fois de suite, n'est pas une solution valide. Ou, plus simplement, ce doit être l'affectation qui change l'état.

Toute langue avec affectation est éligible, bien qu'il y en ait probablement où il n'y a pas de solution entièrement légitime. Je publierai ma réponse Ruby si personne d'autre ne l'obtient après quelques jours et j'accepterai les réponses les plus votées sur une base continue.


La LValue = objligne doit-elle être requise pour statechanger réellement? (Je pourrais simplement créer une propriété en C # qui augmente à chaque fois que vous l'obtenez)
Tim S.

2
Oui, c'est ce que je voulais dire en disant que la méthode devait être idempotente. Je vais modifier pour rendre cela plus clair.
histocrate

OK merci. J'ai dû ignorer cette partie.
Tim S.

4
Est-ce que le simple renvoi du refcount de l'objet fonctionnerait?
Nick T

Serait-ce des altérations destructrices de l'objet lui-même? EmacsLisp: (setq a (list "val")) (setq b (nconc a "val2"))par exemple. afinit par évaluer comme ("val" . "val2")à ce moment-là.
Jonathan Leech-Pepin

Réponses:


30

C ++

C'est trivial en utilisant les bons outils.

#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;
}

Sortie:

3 4
4 5 2 3

12
Au moment où j'ai vu le titre, j'ai su que quelqu'un ferait une surcharge d'opérateur. C'est la voie évidente. Ayez un vote positif.

17

PHP (version de débogage,> = 5.4)

Nous utilisons refcount de l'objet dans un getter. (Donc, par l'affectation, le refcount augmente et la valeur change)

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

C #

Deux options 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();
}

Ou nous pourrions simplement écrire dans la même mémoire:

[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, beaucoup plus court que les autres réponses ici

\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.

En tant que système de composition, TeX a un type "boîte", qui contient du matériel de composition. Étant donné que le cas d'utilisation le plus courant consiste à déplacer ce matériau, à le diviser, etc., plutôt qu'à en faire des copies, les boîtes sont normalement supprimées lorsqu'elles sont utilisées (ou plutôt, les variables "boîte" sont des pointeurs et un seul pointeur à la fois peut pointer à une boîte réelle en mémoire). Pas besoin de magie.


8

C ++ 11 (Donc vous avez oublié 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

Ceci est quelque peu similaire à la réponse D d'Hugo, mais est un peu plus caché (en partie parce que qui # $% ^ connaît Fortran orienté objet)?

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 sortie est

this%state = 1
this%state = 0

Si vous pouvez comprendre ce qui s'est passé, des points bonus vous sont accordés! Si non:

Lors de l'appel de la procédure setNewdans le formulaire call b%setNew(a), best implicitement le premier argument, pas le second.


7

PowerShell

Cela crée un objet dont la statepropriété est le nom des variables qui pointent vers l'objet.

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

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

Sortie

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

Remarque: cela ne fonctionne pas si l'affectation se produit dans une portée enfant.

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

Les sorties

Before: a,this
After: a,this

Get-Variable est intelligent!
mazzy

5

Perl 5

Voici une façon de le faire 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};

Cela produit:

1
2

Explication:

Il s'agit d'une application simple de la surcharge . Plus précisément, je surcharge l'opérateur de conversion de chaîne "", qui est appelé lorsque l'objet surchargé est affecté àsubstr() (qui, oui, est une valeur légale en Perl).

Il y a aussi beaucoup de variables spéciales en Perl qui stringent tout ce qui leur est assigné. Par exemple, ce qui suit fonctionne également:

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

Solution alternative

Voici une autre façon de procéder:

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;

Voici stateune méthode (nous pourrions en faire un attribut avec d'autres manigances de liaison / surcharge, mais cela compliquerait les choses) qui compte littéralement le nombre de références à l'objet. Ainsi, contrairement à la première solution, vous devez en fait assigner $objà une variable normale qui peut contenir une référence d'objet pour effectuer le changement d'état.


5

JavaScript

Ok, j'ai donc fait une version plus courte qui fonctionne comme SSCCE, mais n'essaie plus d'analyser correctement JavaScript, donc le comptage des références peut ne pas fonctionner lorsqu'il est placé dans un script plus complexe.

(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
Voulez-vous expliquer ce que vous faites?
Ryan

5
... qu'est-ce que c'est? O_o
Doorknob

@Doorknob Un getter qui renvoie le résultat de l'appel d'une fonction, qui compte la fréquence à laquelle un identifiant est référencé comme rval, dans une expression d'affectation, dans un texte source donné jusqu'à une ligne donnée, en passant sa source de fonctions englobante et la ligne la getter a été appelé comme arguments. Tout le reste est un tokenizer provisoire désordonné. --- Je ne sais pas comment je devrais l'appeler autrement . En d'autres termes. En d'autres termes: le getter compte le nombre d'affectations de références à x jusqu'à la ligne à partir de laquelle il a été appelé, le reste est un tokenizer non terminé.
C5H8NNaO4

1
Le plus long… et le plus large!
Nicolas Barbulesco

1
@NicolasBarbulesco Je l'ai raccourci
C5H8NNaO4

4

Python

Ça triche un peu, mais que diriez-vous:

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

bien que cela puisse être étendu à d'autres langages qui prennent en charge les destructeurs implicites / explicites

#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;
}

L'opérateur d'affectation par défaut effectue une copie superficielle. Ainsi, l'objet récepteur possède toujours le pointeur et toute modification affecte implicitement l'objet d'origine;


1
Ouais, un newsans un deletedans le programme. Bien que, pour cette tâche, c'est assez bon je pense :)
Ruslan

Quelles sont les sorties?
Nicolas Barbulesco

1
D'après ce que je comprends (C ++ est loin…), ici l' affectation ne change pas l'état . Sinon, déplacez la coutligne vers le haut avant le }et dites si cela fonctionne. :-)
Nicolas Barbulesco

4

Scala

Les conversions implicites vous permettent d'accomplir cela lors de l'affectation à une variable locale normale:

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)

Vous pouvez également accomplir cela avec des types inférés:

var s = ""
s = obj

Vous pouvez également utiliser une méthode de définition personnalisée, bien que cela nécessite que la valeur L soit un champ:

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

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);
}

Sortie:

Obj(0)
Obj(1)

3

Rubis

Comme promis, voici la réponse qui a inspiré la question.

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.newcrée une classe anonyme. L'appel to_sà une classe anonyme donne la représentation sous forme de chaîne par défaut des objets, à quoi ressemble #<Class:0x007fe3b38ed958>. Cependant, une fois que la classe a été affectée à une constante, to_sdevient cette constante. En Ruby, une constante est une variable qui commence par une lettre majuscule, doncobj comme une référence à la classe qui lui permet de rester anonyme.

Mon code encapsule to_sdans une stateméthode, donc la sortie devient

Has not been assigned
Assigned to LValue

Contrairement à la plupart des solutions ici, cela ne fonctionne qu'une seule fois: l'attribution objà une autre constante ne changera pas sa représentation sous forme de chaîne, et aucune autre valeur ne sera affectée à LValue.


3

En Java

Je pensais que c'était impossible à Java. Mais…

Classe principale:

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
  }

}

Chat de classe:

public class Cat {

  static Cat bestCat;

  public Cat() {
    super();
  }

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

}

J'ai été inspiré par @tbodt.


1
Je sais que ce n'est pas du code-golf, mais vous vous rendez compte que vous pouvez simplement supprimer le constructeur et qu'il est toujours le même, non?
David Conrad

2
Ce n'est pas "un objet dont l'état change lors de l'affectation". Il s'agit de manipuler une valeur globale, puis d'imprimer quelque chose en fonction de celle-ci. Ce n'est pas différent de Cat.x = 2puis imprimer Cat.x.
Chris Hayes

@Chris - L'état de l'objet est basé sur une «valeur globale». Ainsi, l'état de l'objet change lors de l'affectation. La question indique ;-) que l' état peut être une méthode déterministe et idempotente. Ma méthode state () est une telle méthode.
Nicolas Barbulesco

Non, l'état de l'objet change sur cette affectation particulière . Si je l'avais fait, Cat otherCat = toml'État n'aurait pas changé du tout. J'ai du mal à croire que cela répond à la lettre ou à l'esprit des règles.
Chris Hayes

@Chris - Bien sûr, l'objet change sur cette affectation! La question demande un objet dont l'état soit modifié par l'affectation. Pas pour un objet dont l'état soit modifié par une affectation.
Nicolas Barbulesco

3

C ++

Ce comportement est en fait spécifié dans la norme (et c'est pourquoi il est déconseillé).

#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';
}

Sortie

some address
0

Le processus qui provoque cela est le même que la réponse d'Abhijit mais sans nécessiter de réponse std::moveidentique à celle de marinus mais en utilisant une classe standard au lieu de la définir moi-même.

Edit: j'ajoute quelques explications. Dans la sortie, "une adresse" sera en fait une valeur hexadécimale pour l'adresse de l'entier alloué. std::auto_ptrlibère son pointeur de magasins lorsqu'il est attribué à un autre auto_ptret définit son pointeur interne sur 0. L'appel get()récupère l'accès au pointeur de magasins.


Je soupçonne que la "sortie" ici n'est pas la vraie sortie.
Nicolas Barbulesco

Pouvez-vous expliquer ce que cela est censé faire? Surtout la méthode get()? Pourquoi retournerait-il 0 à la fin?
Nicolas Barbulesco

@Nicholas yep. Cette sortie n'est pas la vraie sortie, mais une sortie plus générale (je n'avais pas non plus accès à un compilateur donc je n'avais pas d'exemple d'adresse valide à l'époque).
JKor

1
Hm, cela ne parvient pas à compiler sur gcc 4.8.
Michael Hampton

1
J'ai corrigé les erreurs de compilation. Il y a toujours des avertissements si vous compilez pour c ++ 11 car il auto_ptrest obsolète.
JKor

3

Python

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

2

Python 2.x

Je ne pouvais pas trouver un moyen approprié de le faire sans définir une classe supplémentaire.

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

Toutes les autres solutions utilisent la forme de surcharge de l'opérateur de leur langue. Java n'a pas de surcharge d'opérateur, donc je pensais que j'étais coincé. Mais j'ai trouvé quelque chose.

Voici la classe principale:

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());
    }
}

Il y a quelques lignes suspectes, mais elles ne feraient rien si la Thingclasse était complètement normale. Ce n'est pas:

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();
    }
}

Il n'est pas garanti de fonctionner à cause des threads, mais je l'ai testé sur JDK 1.8u5, et cela fonctionne là-bas.



@KyleKanos Débarrassé de tous les caractères Unicode> U + 00FF
tbodt

1

Lisp commun

Je définis l'état comme le nombre de variables spéciales liées à un vecteur. Ainsi, l'affectation à une variable spéciale change l'état.

(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*))

Sortie:

1 
2 
2 
3 

Il ne fonctionne qu'avec des affectations à des variables spéciales, pas à des variables lexicales, ni à des emplacements dans un objet.

Attention, qui do-all-symbolsregarde dans tous les packages, il manque donc des variables qui n'ont pas de package. Il peut compter deux fois les symboles qui existent dans plusieurs packages (lorsqu'un package a importé le symbole d'un autre package).

Rubis

Ruby est presque le même, mais je définis l'état comme le nombre de constantes faisant référence à un tableau.

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

Sortie:

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

Ceci est une généralisation de la réponse histocrate aux objets Ruby qui ne sont pas des classes ou des modules. L'avertissement apparaît car la constante de configuration charge automatiquement le code qui a généré l'avertissement.


0

C ++

Le résultat peut différer sur différentes plateformes. Testé sur 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;
}

Sortie:

1234
13010

0

C #

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);
}

Sortie:

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'est-ce que cela fait? Est-ce que cela surcharge l'opérateur =?
Nicolas Barbulesco

@Nicolas Pas exactement. C'est lors du casting d'un Bà un A, car cela implicit operator A(B b)a des effets secondaires.
ClickRick
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.