"Copie profonde" et "Copie superficielle" à comprendre avec le plus petit exemple

Copie profonde / copie superficielle ...

Un mot qui est souvent source de confusion.

Ici, je voudrais voir comment le comportement du programme diffère en fonction de chaque méthode de copie, en utilisant le plus petit exemple dans lequel la différence entre chaque méthode de copie est apparente.

Avertissement

C'est un avertissement. Si cela ne vous dérange pas, passez au chapitre "Que faites-vous?".

J'utilise le mot "livraison partagée"

Dans cet article, nous utiliserons le «passage partagé» pour unifier les noms des méthodes appelées «partage de passage», «appel par partage», «passage partagé» et «passage de référence par valeur».

Veuillez noter que l'auteur ne recommande pas ce terme. Pour faciliter la comparaison des spécifications multilingues, nous n'avons donné que les noms utilisés dans cet article.

J'utiliserai également le mot «○○ pass» en cas de substitution

Dans cet article, les termes «passer par référence» et «passer par partage» sont également utilisés pour décrire les «méthodes d'attribution de variables».

Normalement, c'est un mot utilisé lors du passage d'un argument à une fonction, mais comme le même concept vaut pour l'affectation, nous utiliserons le mot tel quel.

(Ceci est mon opinion personnelle, mais je ne suis pas sûr de l'intérêt d'utiliser le mot "passer XX" uniquement lors du passage d'arguments à une fonction. Je pense que c'est correct de l'utiliser pour l'affectation, donc je le ferai dans cet article. Je vais.)

Ceux qui ne génèrent pas de nouvelle entité ne seront pas appelés "copies superficielles"

Lorsque je recherche, je reçois beaucoup d'articles disant "La copie superficielle est une méthode qui consiste à copier uniquement une référence ou un pointeur et non à créer une nouvelle entité".

Dans cet article, le cas où «une nouvelle entité est créée, mais quelque part dans le contenu fait référence à la même entité qu'avant la copie» est appelé «copie superficielle».

Version anglaise de Wikipedia et Documentation officielle Python Mais c'est le cas.

(Ceci est mon opinion personnelle, mais au moins ce cas de "création d'une nouvelle entité mais faisant référence à la source de la copie" devrait être appelé "copie superficielle", donc "copie superficielle est nouvelle". Je pense que l'explication selon laquelle "cela ne crée pas d'entité" est incorrecte. Si "un objet qui ne crée pas de nouvelle entité ** est aussi appelé une ** copie superficielle", il n'y a pas de contradiction, mais je pense que c'est également incorrect. )

Que faites-vous?

Remplacez «une copie dans un certain sens» de la variable «a» par la variable «b», jouez avec le «b», puis vérifiez le contenu du «a».

À cette époque, pas seulement deux types de méthodes de copie

Comparons les quatre.

La signification de chaque terme sera expliquée ensemble lorsque le code sera expliqué ci-dessous.

Que faire avec le plus petit exemple

assignment.dart


void main() {
  var a = [
    ["did deep copy"]
  ];

  //Ici, le processus de substitution d'une copie de a dans un certain sens pour b

  b[0][0] = "did shallow copy";
  b[0] = ["did pass-by-sharing"];
  b = [
    ["did pass-by-reference"]
  ];
  print(a[0][0]);
}

Je voulais juste présenter le contenu du traitement, et je ne voulais pas en parler en fonction d'une langue spécifique, alors je l'ai écrit dans Dart, qui semble être utilisé par peu de gens. (Récemment augmenté ...?)

Le contenu du traitement est

  1. Affectez la liste de chaînes à la variable ʻa`.
  2. Remplacez d'une certaine manière la variable «b» par une copie de «a».
  3. Remplacez le 0e élément du 0e élément de «b» par une chaîne de caractères différente.
  4. Remplacez le 0e élément de «b» par une liste de chaînes différentes.
  5. Remplacez «b» par une liste de chaînes différentes.
  6. Sortez le 0e élément du 0e élément de ʻa`.

Voyons pourquoi cela fait une différence.

En passant par référence

Si l'assignation «b = a» est passée par référence, «b» aura une «référence» qui pointe vers «a», et tout traitement ultérieur sur «b» affectera également «a». Je vais.

Il est plus facile de comprendre le mouvement si vous pensez à «a» comme étant donné l'alias «b».

Dans ce cas, le code fonctionne comme ceci.

assignment.dart


void main() {
  var a = [
    ["did deep copy"]
  ];

  //Passez maintenant a par référence à b

  //Tous les traitements suivants sont équivalents à ceux effectués pour un
  b[0][0] = "did shallow copy";
  b[0] = ["did pass-by-sharing"];
  b = [
    ["did pass-by-reference"]
  ];
  print(a[0][0]); // did pass-by-reference
}

Dernier

  b = [
    ["did pass-by-reference"]
  ];

Le contenu de ʻa` est une liste de listes avec cette chaîne.

Par conséquent, la sortie sera "did pass-by-reference".

À propos, cette chaîne de caractères signifie «passé par référence».

En cas de livraison partagée

À proprement parler, la livraison partagée n'est pas seulement un terme qui fait référence à la façon de réussir.

Dans un langage partagé, le contenu d'une variable n'est pas la valeur elle-même, mais une référence à cette valeur. (Ceci est pour de nombreux langages tels que Java, Python, JavaScript.)

Ensuite, lorsque «b = a» est défini, la «référence» stockée dans «a» est copiée et stockée également dans «b».

C'est pourquoi le passage partagé est parfois appelé "passage par référence".

Dans ce cas, le code ci-dessus se comporte comme suit.

assignment.dart


void main() {
  //a fait référence à ce double tableau
  var a = [
    ["did deep copy"]
  ];

  //Ici, partagez de a à b

  //Ce processus affecte a car a et b ont des références qui pointent vers la même entité.
  b[0][0] = "did shallow copy";

  //Ce processus affecte également un
  b[0] = ["did pass-by-sharing"];

  //Ici, b stocke une nouvelle référence pointant vers la nouvelle entité, donc ce processus n'affecte pas a.
  b = [
    ["did pass-by-reference"]
  ];
  print(a[0][0]); // did pass-by-sharing
}

Dernier

  b = [
    ["did pass-by-reference"]
  ];

A assigné "" [["did pass-by-reference"]] à b, une référence qui pointe vers une entité différente de celle d'avant, donc ce processus affecte ʻa. Non.

Par conséquent, la sortie sera "did pass-by-sharing".

À propos, cette chaîne de caractères signifie «partagé et transmis».

Pour copie superficielle

Nous continuons à parler de langages où le contenu d'une variable n'est pas la valeur elle-même, mais une référence à cette valeur. (Java, Python, JavaScript, etc.)

Il n'y a pas de copie superficielle dans les langages où le contenu de la variable est la valeur elle-même (comme C ++). (Je pense qu'il peut être reproduit à l'aide de pointeurs et en passant par référence)

Une copie superficielle, comme son nom l'indique, fait une copie.

Si le contenu est "référence", copiez-le tel quel.

Voyons ce que cela signifie dans le code.

assignment.dart


void main() {
  //a fait référence à ce double tableau
  var a = [
    ["did deep copy"]
  ];

  //Maintenant, faites une copie superficielle de a et attribuez-la à b

  //a et b ont des références qui pointent vers des entités différentes.
  //Cependant, les deux entités ont la même «référence» dans le 0e élément, donc
  //Ce processus affecte un
  b[0][0] = "did shallow copy";

  //Le 0e élément de b est réécrit comme une référence pointant vers quelque chose de différent.
  //Ce processus n'affecte pas a.
  b[0] = ["did pass-by-sharing"];

  //Ici, b stocke une nouvelle référence pointant vers la nouvelle entité, donc ce processus n'affecte pas non plus a.
  b = [
    ["did pass-by-reference"]
  ];
  print(a[0][0]); // did shallow copy
}

«A» et sa copie superficielle sont en fait différents. Si vous affichez ʻid en Python et hashcode dans Dart et que vous le vérifiez, vous pouvez voir que ʻa et b pointent vers des entités différentes.

Cependant, le même contenu est copié.

D'ailleurs, une "référence" pointant vers la même chose a été copiée.

Donc, si vous réécrivez le contenu de la "destination de référence du contenu", ʻaetb` seront affectés.

C'est ça.

  b[0][0] = "did shallow copy";

D'un autre côté, si vous réécrivez le "contenu de b" lui-même, cela n'affectera pas ʻa`.

C'est

  b[0] = ["did pass-by-sharing"];

est. Ce processus n'affecte pas «a». Le résultat est une "copie superficielle".

Pour copie complète

Comme une copie superficielle, une copie profonde fait une copie, mais

Examinez la valeur référencée dans le contenu et copiez-la également.

S'il s'agit également d'une "référence", vérifiez la valeur de la référence et copiez-la également.

Répétez ceci jusqu'à ce que vous ayez copié toutes les références.

ʻA et b` ne partagent plus rien.

Les opérations effectuées sur l'un n'affectent pas du tout l'autre.

assignment.dart


void main() {
  //a fait référence à ce double tableau
  var a = [
    ["did deep copy"]
  ];

  //Passez maintenant une copie complète de a à b. Après cela, l'opération de b n'affecte pas a.

  b[0][0] = "did shallow copy";
  b[0] = ["did pass-by-sharing"];
  b = [
    ["did pass-by-reference"]
  ];
  print(a[0][0]); // did deep copy
}

Puisque rien n'a changé dans ʻa, la valeur initiale did deep copy` est sortie.

Voyons avec un exemple

Voyons comment ils sont réellement attribués dans certaines langues! !! !!

JavaScript

JavaScript passera partagé lorsqu'il est attribué normalement.

assignment.js



a = [["did deep copy"]];

b = a;

b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [["did  pass-by-reference"]];
console.log(a[0][0]); // did pass-by-sharing

Si vous ne voulez pas partager l'entité, si c'est un tableau, vous pouvez utiliser slice pour faire une copie et l'avoir.

assignment.js


a = [["did deep copy"]];

b = a.slice(0, a.length);

b[0][0] = "did shallow copy";
b[0] = ["did pass-by-sharing"];
b = [["did pass-by-reference"]];
console.log(a[0][0]); // did shallow copy

La copie dans ce cas est une copie superficielle. Si vous souhaitez faire une copie complète, vous devez concevoir.

Même s'il s'agit d'un objet au lieu d'un tableau, il est partagé si vous l'affectez normalement. Si vous souhaitez le copier, vous pouvez procéder comme suit.

assignment.js


a = { x: { y: "did deep copy" } };

b = Object.assign({}, a); //Ici b=Si a est défini, il sera partagé

b.x.y = "did shallow copy";
b.x = { y: "did pass-by-sharing" };
b = { x: { y: "did pass-by-reference" } };
console.log(a.x.y); // did shallow copy

Python

Vient ensuite Python.

assignment.py


import copy

a = [['did deep copy']]

b = a

b[0][0] = 'did shallow copy'
b[0] = ['did pass-by-sharing']
b = [['did pass-by-reference']]
print(a[0][0]) # did pass-by-sharing

Même en Python, si vous l'attribuez normalement, il sera partagé.

Python a un module appelé copy qui vous permet de faire explicitement des copies superficielles ou profondes.

Cliquez ici pour la documentation copie --- opérations de copie superficielle et de copie profonde

Vous pouvez faire une copie superficielle avec copy.copy.

assignment.py


import copy

a = [['did deep copy']]

b = copy.copy(a)

b[0][0] = 'did shallow copy'
b[0] = ['did pass-by-sharing']
b = [['did pass-by-reference']]
print(a[0][0])  # did shallow copy

Vous pouvez faire une copie complète avec copy.deepcopy.

assignment.py


import copy

a = [['did deep copy']]

b = copy.deepcopy(a)

b[0][0] = 'did shallow copy'
b[0] = ['did pass-by-sharing']
b = [['did pass-by-reference']]
print(a[0][0])  # did deep copy

Les objets peuvent également être copiés dans ce module. C'est pratique.

Dart

assignment.dart


void main() {
  var a = [
    ["did deep copy"]
  ];

  var b = a;

  b[0][0] = "did shallow copy";
  b[0] = ["did pass-by-sharing"];
  b = [
    ["did pass-by-reference"]
  ];
  print(a[0][0]); // did pass-by-sharing
}

Si Dart est également attribué normalement, il sera partagé.

C++

C ++ se comporte très différemment des langages jusqu'à présent.

C ++ n'est pas partagé lorsqu'il est attribué normalement.

En C ++, le contenu d'une variable n'est pas une "référence" mais une "valeur elle-même".

Puisqu'il est copié tel quel, il n'y a aucune relation avec la source de la copie à ce stade.

Peu importe comment vous le copiez et le créez, cela n'affecte pas la source de la copie.

Il se comporte exactement comme ** copie profonde **.

assignment.cpp


#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<vector<string>> a{vector<string>{"did deep copy"}};

    vector<vector<string>> b = a;

    b[0][0] = "did shallow copy";
    b[0] = vector<string>{"did pass-by-sharing"};
    b = vector<vector<string>>{vector<string>{"did pass-by-reference"}};
    cout << a[0][0] << endl; // did deep copy
}

Vous pouvez également passer par référence en C ++.

En cas de passage par référence, toutes les opérations effectuées par copie et création affecteront la source de la copie.

assignment.cpp


#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<vector<string>> a{vector<string>{"did deep copy"}};

    vector<vector<string>> &b = a;

    b[0][0] = "did shallow copy";
    b[0] = vector<string>{"did pass-by-sharing"};
    b = vector<vector<string>>{vector<string>{"did pass-by-reference"}};
    cout << a[0][0] << endl; // did pass-by-reference
}

fin

Comme nous l'avons vu jusqu'à présent, si vous écrivez ce processus, vous pouvez clarifier ce qui est attribué au moment de l'affectation.

Si vous savez cela, vous devriez être moins susceptible d'être dérangé par un comportement inattendu.

De plus, bien qu'il s'agisse d'un article similaire, il existe un article qui favorise la compréhension en comparant le comportement lors du passage d'un argument à une fonction et le comportement lors de l'assignation, veuillez donc le lire si vous le souhaitez.

Compréhension simple du passage par valeur, passage par partage et passage par référence avec l'exemple minimum (5 lignes)

Si vous faites une erreur dans cet article, je vous serais très reconnaissant de bien vouloir le signaler! Je vous remercie.

Recommended Posts

"Copie profonde" et "Copie superficielle" à comprendre avec le plus petit exemple
Copie superficielle Python et copie profonde
Copie superficielle Python et copie profonde
Intelligence artificielle, machine learning, deep learning pour mettre en œuvre et comprendre
J'ai capturé le projet Toho avec Deep Learning ... je le voulais.
Répétez avec While. Script pour tweeter ou rechercher depuis le terminal
[Statistiques] Visualisez et comprenez la méthode Hamiltonian Monte Carlo avec animation.
[Objet obligatoire DI] Implémenter et comprendre le mécanisme de DI avec Go
La meilleure façon d'utiliser MeCab et CaboCha avec Google Colab
Pour améliorer la réutilisabilité et la maintenabilité des flux de travail créés avec Luigi
Essayer d'implémenter et de comprendre les arborescences de segments étape par étape (python)
Mettez Cabocha 0.68 dans Windows et essayez d'analyser la dépendance avec Python
Spécifiez le navigateur à utiliser avec Jupyter Notebook. Surtout Mac. (Et Vivaldi)
Connectez-vous au VPN avec votre smartphone et éteignez / rallumez le serveur
J'ai essayé d'exprimer de la tristesse et de la joie face au problème du mariage stable.
Comment obtenir la différence de date et d'heure en secondes avec Python
Recherchez des mots-clés Twitter avec tweepy et écrivez les résultats dans Excel
Convertissez la feuille de calcul en CSV et importez-la dans Cloud Storage avec Cloud Functions
J'ai essayé d'apprendre l'angle du péché et du cos avec le chainer
J'ai essayé de contrôler la bande passante et le délai du réseau avec la commande tc
Essayez de séparer l'arrière-plan et l'objet en mouvement de la vidéo avec OpenCV
Enregistrez les étapes pour comprendre l'apprentissage automatique
Introduction au Deep Learning ~ Pliage et mise en commun ~
Fractal pour faire et jouer avec Python
Exemple de script pour afficher BoundingBox avec PIL
Comprendre l'arbre de décision et classer les documents
décalage du consommateur kafka-copy vers un autre avec python
Alignez la barre de couleurs sur la figure avec matplotlib
Je voulais vraiment copier avec du sélénium
La route de la compilation vers Python 3 avec Thrift
Comment démarrer le projet Python en 2020 (Windows WSL et Mac commun)
Comment passer le chemin vers la bibliothèque construite avec pyenv et virtualenv avec PyCharm
Comment obtenir la clé sur Amazon S3 avec Boto 3, exemple de mise en œuvre, notes
Deep Learning from scratch La théorie et la mise en œuvre de l'apprentissage profond appris avec Python Chapitre 3
Utilisez la commande [shell] pour compresser par zip n'importe quel fichier pour créer un fichier et supprimer le fichier d'origine.
Spécifiez les positions de début et de fin des fichiers à inclure avec qiitap
Créez un environnement python pour apprendre la théorie et la mise en œuvre de l'apprentissage profond
[Introduction à AWS] J'ai essayé de porter une application de conversation et de jouer avec text2speech @ AWS ♪