Jak zrobić dokładną kopię tablicy?

100

Jak zrobić dokładną kopię tablicy?

Mam trudności ze znalezieniem informacji o powielaniu tablicy w języku Swift.

Próbowałem użyć .copy()

var originalArray = [1, 2, 3, 4]
var duplicateArray = originalArray.copy()
Patrick
źródło
5
dlaczego nie przypisujesz wartości bezpośrednio w ten sposób:var duplicateArray = originalArray
Dharmesh Kheni
1
To nie działa w moim przypadku. Tworzy to kolejny obiekt, który jest po prostu odwołaniem do tej samej tablicy, a otrzymasz 2 zmienne odwołujące się do tej samej tablicy.
user1060500

Odpowiedzi:

176

Tablice w Swift mają pełnowartościową semantykę, więc nie ma potrzeby robienia niczego wymyślnego.

var duplicateArray = originalArray to wszystko czego potrzebujesz.


Jeśli zawartość twojej tablicy jest typem referencyjnym, to tak, spowoduje to tylko skopiowanie wskaźników do twoich obiektów. Aby wykonać głęboką kopię zawartości, należy zamiast tego użyć mapi wykonać kopię każdej instancji. W przypadku klas Foundation zgodnych z NSCopyingprotokołem można skorzystać z copy()metody:

let x = [NSMutableArray(), NSMutableArray(), NSMutableArray()]
let y = x
let z = x.map { $0.copy() }

x[0] === y[0]   // true
x[0] === z[0]   // false

Zauważ, że istnieją tu pułapki, przed którymi działa semantyka wartości języka Swift - na przykład, ponieważ NSArrayreprezentuje niezmienną tablicę, jej copymetoda po prostu zwraca odwołanie do siebie, więc powyższy test przyniósłby nieoczekiwane wyniki.

Nate Cook
źródło
Wypróbowałem to na placu zabaw z tym prostym kodem var x = [UIView(), UIView(), UIView()] var y = x for i in x { NSLog("%p", i) } println("---") for i in y { NSLog("%p", i) }i otrzymałem następujący wynik: 0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 ---0x7fa82b0009e0 0x7fa82b012660 0x7fa82b012770 Nie wygląda na to, że jest kopiowany, czy wiesz, dlaczego?
Phil Niedertscheider
@PNGamingPower: x zawiera adresy. y zawiera kopie tych adresów. Jeśli zmienisz x [0], y [0] się nie zmieni. (spróbuj x [0] = x [1], y [0] nie zmieni się). Zatem y jest głęboką kopią x, ale skopiowałeś tylko wskaźniki, a nie to, na co wskazują.
ragnarius
@ragnarius, więc w zasadzie musimy zdefiniować, co oznacza „kopia”, kopiując wskaźnik lub wartość. Dlatego jest to rozwiązanie do kopiowania / powielania tablicy wskaźników, ale jak powielić tablicę wartości? Bramka byłaby, x[0] == x[1]ale x[0] === y[0]powinna się nie udać
Phil Niedertscheider
To powinna być akceptowana odpowiedź, ponieważ semantyka wartości Array sprawia, że ​​„kopia” tablicy jest niepotrzebna.
Scott Ahten
To nie działa na mnie. Jeśli zastosuję tę metodę, otrzymam dwie referencje, które w końcu wskazują na tę samą tablicę obiektów. Jeśli usuwam element z listy, jest to odzwierciedlane w obu odwołaniach do obiektów, ponieważ lista nie została skopiowana, a raczej obiekt był tylko przywoływany.
user1060500,
28

Nate ma rację. Jeśli pracujesz z tablicami pierwotnymi, wszystko, co musisz zrobić, to przypisać duplicateArray do oryginalnej tablicy.

Ze względu na kompletność, jeśli pracujesz z obiektem NSArray, wykonaj następujące czynności, aby wykonać pełną kopię NSArray:

var originalArray = [1, 2, 3, 4] as NSArray

var duplicateArray = NSArray(array:originalArray, copyItems: true)
applejack42
źródło
To jest świetne! Dziękuję Ci!
Patrick,
23

Istnieje trzecia opcja odpowiedzi Nate'a:

let z = x.map { $0 }  // different array with same objects

* EDYTOWANA * edycja zaczyna się tutaj

Powyższe jest zasadniczo takie samo jak poniżej i faktycznie użycie operatora równości poniżej będzie działać lepiej, ponieważ tablica nie zostanie skopiowana, chyba że zostanie zmieniona (jest to zgodne z projektem).

let z = x

Przeczytaj więcej tutaj: https://developer.apple.com/swift/blog/?id=10

* EDYTOWANE * edycja kończy się tutaj

dodanie lub usunięcie tej tablicy nie wpłynie na oryginalną tablicę. Jednak zmiana jakichkolwiek właściwości obiektów przechowywanych w tablicy byłaby widoczna w oryginalnej tablicy. Ponieważ obiekty w tablicy nie są kopiami (zakładając, że tablica zawiera obiekty, a nie liczby pierwotne).

oyalhi
źródło
działa, przetestowałem to. są dwie tablice, jeśli zmienisz na 1, druga zostanie wykonana
Filthy Knight
1
Nie, nie jest, chyba że tablica zawiera typy pierwotne zamiast obiektów. Wtedy ma to wpływ, jak podano w odpowiedzi. Prosty przypadek testowy:var array1: [String] = ["john", "alan", "kristen"]; print(array1); var array2 = array1.map { $0 }; print(array2); array2[0] = "james"; print(array1); print(array2);
oyalhi
1
Zapoznaj się z tym streszczeniem, które utworzyłem dla lepszego przykładu przy użyciu niestandardowej klasy: gist.github.com/oyalhi/3b9a415cf20b5b54bb3833817db059ce
oyalhi
Jeśli twoja klasa obsługuje NSCopying, zduplikuj tablicę:let z = x.map { $0.copy as! ClassX }
John Pang,
Jeśli używasz BufferPointers Swift, jest to wersja, której powinieneś używać jako bezpośredniej kopii. Przed zmianą wartości w oryginalnej lub skopiowanej tablicy, Swift skopiuje wartości oryginału do kopii, a następnie będzie kontynuować. Jeśli zamiast tego użyjesz wskaźników, Swift nie będzie teraz działać, jeśli lub kiedy nastąpią zmiany, więc potencjalnie możesz skończyć ze zmianą obu tablic.
Justin Ganzer
16

W przypadku zwykłych obiektów można zaimplementować protokół obsługujący kopiowanie i sprawić, by klasa obiektów implementowała ten protokół w następujący sposób:

protocol Copying {
    init(original: Self)
}

extension Copying {
    func copy() -> Self {
        return Self.init(original: self)
    }
}

A następnie rozszerzenie Array do klonowania:

extension Array where Element: Copying {
    func clone() -> Array {
        var copiedArray = Array<Element>()
        for element in self {
            copiedArray.append(element.copy())
        }
        return copiedArray
    }
}

i to prawie wszystko, aby wyświetlić kod i próbkę, sprawdź tę istotę

Sohayb Hassoun
źródło
To zaoszczędziło dużo czasu, dziękuję.
Abhijit
W przypadku podklas protokół nie może zagwarantować, że wymaganie init zostanie zaimplementowane z typem podklasy. Deklarujesz protokół kopiowania, który implementuje kopiowanie za Ciebie, ale nadal implementujesz clone (), to nie ma sensu.
Binarian,
1
@iGodric copy jest dla elementów w kolekcji, a clone dla całej kolekcji, dostępne po zaimplementowaniu kopiowania dla jego elementów. Ma sens? Ponadto kompilator zapewnia, że ​​podklasy są zgodne z protokołem wymaganym przez ich rodziców.
johnbakers
@johnbakers O tak, teraz to widzę. Dzięki za wyjaśnienie.
Binarian
Bardzo czysta implementacja i pozwala uniknąć niepotrzebnego pośpiechu przekazywania jakichkolwiek parametrów w object'sfunkcji init
Sylvan D Ash
0

Jeśli chcesz skopiować elementy tablicy jakiegoś obiektu klasy. Następnie możesz postępować zgodnie z poniższym kodem bez używania protokołu NSCopying, ale musisz mieć metodę init, która powinna pobierać wszystkie parametry wymagane dla twojego obiektu. Oto kod przykładu do testowania na placu zabaw.

class ABC {
    
    var a = 0
    func myCopy() -> ABC {
        
        return ABC(value: self.a)
    }
    
    init(value: Int) {
        
        self.a = value
    }
}

var arrayA: [ABC] = [ABC(value: 1)]
var arrayB: [ABC] = arrayA.map { $0.myCopy() }

arrayB.first?.a = 2
print(arrayA.first?.a)//Prints 1
print(arrayB.first?.a)//Prints 2
Noman Haroon
źródło