Swift - Nesne dizisini birden çok ölçüte göre sıralayın


92

Bir dizi Contactnesnem var:

var contacts:[Contact] = [Contact]()

İletişim sınıfı:

Class Contact:NSOBject {
    var firstName:String!
    var lastName:String!
}

Ve tarafından bu diziyi sıralamak istiyoruz lastNameve o zamana firstNamedurumda bazı kişiler aynı var lastName.

Bu kriterlerden birine göre sıralama yapabiliyorum, ancak ikisine birden değil.

contacts.sortInPlace({$0.lastName < $1.lastName})

Bu diziyi sıralamak için nasıl daha fazla ölçüt ekleyebilirim?


2
Az önce söylediğin gibi yap! Küme parantezlerinin içindeki kodunuz şunu söylemelidir: "Soyadlar aynıysa, ada göre sıralayın; aksi takdirde soyadına göre sıralayın".
mat

4
Burada birkaç kod kokusu görüyorum: 1) Contactmuhtemelen miras almamalı NSObject, 2) Contactmuhtemelen bir yapı olmalı ve 3) firstNameve lastNamemuhtemelen örtük olarak sarmalanmamış seçenekler olmamalı.
Alexander - Monica'yı

3
@AMomchilov Contact'ın bir yapı olmasını önermek için bir neden yok çünkü kodunun geri kalanının örneklerini kullanırken zaten referans semantiğine dayanıp dayanmadığını bilmiyorsunuz.
Patrick Goley

3
@AMomchilov "Muhtemelen" yanıltıcıdır çünkü kod tabanının geri kalanı hakkında tam olarak hiçbir şey bilmiyorsunuz. Bir yapıya dönüştürülürse, eldeki örneği değiştirmek yerine, değişkenler değiştirilirken ani kopyaların tümü oluşturulur. Bu davranış önemli bir değişiklik olduğunu ve olası her şey hem referans için düzgün kod olmuştur çünkü bunu yaparken "muhtemelen" hataların ortaya çıkmasına neden olur ve değer semantik.
Patrick Goley

1
@AMomchilov Muhtemelen bir yapı olması gerektiğinin bir nedenini henüz duymadım. OP'nin programının geri kalanının anlamını değiştiren önerileri takdir edeceğini sanmıyorum, özellikle de problemi çözmek için bile gerekli olmadığında. Derleyici kurallarının bazıları için yasal olduğunu fark etmemiştim ... belki yanlış web sitesindeyim
Patrick Goley

Yanıtlar:


120

"Birden çok kritere göre sıralama" nın ne anlama geldiğini bir düşünün. Bu, iki nesnenin önce bir kriterle karşılaştırıldığı anlamına gelir. Daha sonra, bu kriterler aynıysa, bağlar sonraki kriterlere göre bozulur ve istediğiniz sıralamayı alana kadar bu şekilde devam eder.

let sortedContacts = contacts.sort {
    if $0.lastName != $1.lastName { // first, compare by last names
        return $0.lastName < $1.lastName
    }
    /*  last names are the same, break ties by foo
    else if $0.foo != $1.foo {
        return $0.foo < $1.foo
    }
    ... repeat for all other fields in the sorting
    */
    else { // All other fields are tied, break ties by last name
        return $0.firstName < $1.firstName
    }
}

Burada gördüğünüz , öğelerin nasıl karşılaştırıldığını belirlemek için sağlanan kapanışa başvuran Sequence.sorted(by:)yöntemdir .

Sıralamanız birçok yerde kullanılacaksa, türünüzü Comparable protokole uygun hale getirmek daha iyi olabilir . Bu şekilde, öğelerin nasıl karşılaştırıldığını belirlemek için operatör uygulamanıza başvuran Sequence.sorted()yöntemi kullanabilirsiniz . Bu şekilde, herhangi sıralayabilir arasında şimdiye sıralama kodu çoğaltmak gerek kalmadan s.Comparable.<(_:_:)SequenceContact


2
elseVücut arasında olmalıdır { ... }aksi kod derleme yapmaz.
Luca Angeletti

Anladım. Onu uygulamaya çalıştım ama sözdizimini doğru yapamadım. Çok teşekkürler.
sbkl

için sortvs. sortInPlacebakınız burada . Aslo bkz bu çok daha modüler var, aşağıda
Honey

sortInPlaceSwift 3'te artık YOK, bunun yerine kullanmanız gerekiyor sort(). sort()dizinin kendisini değiştirecektir. Ayrıca sıralı bir dizi sorted()döndürecek adında yeni bir işlev var
Honey

2
@AthanasiusOfAlex kullanmak ==iyi bir fikir değil. Yalnızca 2 mülk için çalışır. Bundan daha fazlasını yaparsanız, birçok bileşik mantıksal ifade ile kendinizi tekrar etmeye başlıyorsunuz
Alexander - Reinstate Monica

123

Birden çok kriterin karşılaştırmasını yapmak için tuple kullanma

Birden çok ölçüte göre sıralama gerçekleştirmenin gerçekten basit bir yolu (yani, bir karşılaştırmaya göre sıralama ve eşdeğer ise, sonra başka bir karşılaştırma) tuple kullanmaktır , çünkü <ve >işleçleri sözlükbilimsel karşılaştırmalar yapan aşırı yüklemelere sahiptir.

/// Returns a Boolean value indicating whether the first tuple is ordered
/// before the second in a lexicographical ordering.
///
/// Given two tuples `(a1, a2, ..., aN)` and `(b1, b2, ..., bN)`, the first
/// tuple is before the second tuple if and only if
/// `a1 < b1` or (`a1 == b1` and
/// `(a2, ..., aN) < (b2, ..., bN)`).
public func < <A : Comparable, B : Comparable>(lhs: (A, B), rhs: (A, B)) -> Bool

Örneğin:

struct Contact {
  var firstName: String
  var lastName: String
}

var contacts = [
  Contact(firstName: "Leonard", lastName: "Charleson"),
  Contact(firstName: "Michael", lastName: "Webb"),
  Contact(firstName: "Charles", lastName: "Alexson"),
  Contact(firstName: "Michael", lastName: "Elexson"),
  Contact(firstName: "Alex", lastName: "Elexson"),
]

contacts.sort {
  ($0.lastName, $0.firstName) <
    ($1.lastName, $1.firstName)
}

print(contacts)

// [
//   Contact(firstName: "Charles", lastName: "Alexson"),
//   Contact(firstName: "Leonard", lastName: "Charleson"),
//   Contact(firstName: "Alex", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Webb")
// ]

Bu, önce elemanların lastNameözelliklerini karşılaştıracaktır . Eşit değillerse, sıralama düzeni <onlarla karşılaştırmaya dayalı olacaktır . Bunlar ise olan eşit, daha sonra, başlığın elemanların bir sonraki çift üzerine hareket karşılaştırarak yani edecek firstNameözellikleri.

Standart kütüphane, 2 ila 6 elemanlı tuplelar sağlar <ve >aşırı yükler.

Farklı özellikler için farklı sıralama düzenleri istiyorsanız, demetlerdeki öğeleri kolayca değiştirebilirsiniz:

contacts.sort {
  ($1.lastName, $0.firstName) <
    ($0.lastName, $1.firstName)
}

// [
//   Contact(firstName: "Michael", lastName: "Webb")
//   Contact(firstName: "Alex", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Elexson"),
//   Contact(firstName: "Leonard", lastName: "Charleson"),
//   Contact(firstName: "Charles", lastName: "Alexson"),
// ]

Bu şimdi lastNameazalan, sonra firstNameartan şekilde sıralanacaktır.


sort(by:)Birden çok yüklem alan bir aşırı yük tanımlama

Koleksiyonları mapkapatma ve SortDescriptors ile Sıralama konusundaki tartışmadan esinlenilen başka bir seçenek, özel bir aşırı yüklemeyi tanımlamak sort(by:)ve sorted(by:)birden çok yüklemle ilgilenmek olabilir - burada her bir yüklem, öğelerin sırasına karar vermek için dikkate alınır.

extension MutableCollection where Self : RandomAccessCollection {
  mutating func sort(
    by firstPredicate: (Element, Element) -> Bool,
    _ secondPredicate: (Element, Element) -> Bool,
    _ otherPredicates: ((Element, Element) -> Bool)...
  ) {
    sort(by:) { lhs, rhs in
      if firstPredicate(lhs, rhs) { return true }
      if firstPredicate(rhs, lhs) { return false }
      if secondPredicate(lhs, rhs) { return true }
      if secondPredicate(rhs, lhs) { return false }
      for predicate in otherPredicates {
        if predicate(lhs, rhs) { return true }
        if predicate(rhs, lhs) { return false }
      }
      return false
    }
  }
}

extension Sequence {
  mutating func sorted(
    by firstPredicate: (Element, Element) -> Bool,
    _ secondPredicate: (Element, Element) -> Bool,
    _ otherPredicates: ((Element, Element) -> Bool)...
  ) -> [Element] {
    return sorted(by:) { lhs, rhs in
      if firstPredicate(lhs, rhs) { return true }
      if firstPredicate(rhs, lhs) { return false }
      if secondPredicate(lhs, rhs) { return true }
      if secondPredicate(rhs, lhs) { return false }
      for predicate in otherPredicates {
        if predicate(lhs, rhs) { return true }
        if predicate(rhs, lhs) { return false }
      }
      return false
    }
  }
}

( secondPredicate:Parametre talihsizdir, ancak mevcut sort(by:)aşırı yük ile belirsizlikler yaratmamak için gereklidir )

Bu daha sonra şunu söylememizi sağlar ( contactsönceki diziyi kullanarak ):

contacts.sort(by:
  { $0.lastName > $1.lastName },  // first sort by lastName descending
  { $0.firstName < $1.firstName } // ... then firstName ascending
  // ...
)

print(contacts)

// [
//   Contact(firstName: "Michael", lastName: "Webb")
//   Contact(firstName: "Alex", lastName: "Elexson"),
//   Contact(firstName: "Michael", lastName: "Elexson"),
//   Contact(firstName: "Leonard", lastName: "Charleson"),
//   Contact(firstName: "Charles", lastName: "Alexson"),
// ]

// or with sorted(by:)...
let sortedContacts = contacts.sorted(by:
  { $0.lastName > $1.lastName },  // first sort by lastName descending
  { $0.firstName < $1.firstName } // ... then firstName ascending
  // ...
)

Çağrı sitesi, tuple varyantı kadar özlü olmasa da, neyin karşılaştırıldığı ve hangi sırayla yapıldığına dair ek netlik kazanırsınız.


Uygun Comparable

Eğer gibi düzenli sonra bu türde karşılaştırmaları yapıyor gibi gidiyoruz @AMomchilov & @appzYourLife önermek, sen uyabilir Contactiçin Comparable:

extension Contact : Comparable {
  static func == (lhs: Contact, rhs: Contact) -> Bool {
    return (lhs.firstName, lhs.lastName) ==
             (rhs.firstName, rhs.lastName)
  }

  static func < (lhs: Contact, rhs: Contact) -> Bool {
    return (lhs.lastName, lhs.firstName) <
             (rhs.lastName, rhs.firstName)
  }
}

Ve şimdi sadece sort()artan bir düzen için arayın :

contacts.sort()

veya sort(by: >)azalan bir sıra için:

contacts.sort(by: >)

İç içe bir türde özel sıralama siparişleri tanımlama

Kullanmak istediğiniz başka sıralama emirleriniz varsa, bunları iç içe bir türde tanımlayabilirsiniz:

extension Contact {
  enum Comparison {
    static let firstLastAscending: (Contact, Contact) -> Bool = {
      return ($0.firstName, $0.lastName) <
               ($1.firstName, $1.lastName)
    }
  }
}

ve sonra şu şekilde arayın:

contacts.sort(by: Contact.Comparison.firstLastAscending)

contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) } Yardım etti. Teşekkürler
Prabhakar Kasi

Benim gibi ayırıma tabi tutulacak olan özellikler opsiyoneldir, o zaman böyle bir şey yapabileceğini: contacts.sort { ($0.lastName ?? "", $0.firstName ?? "") < ($1.lastName ?? "", $1.firstName ?? "") }.
BobCowe

Holly molly! Çok basit ama çok verimli ... neden bunu hiç duymadım ?! Çok teşekkürler!
Ethenyl

@BobCowe Bu sizi ""diğer dizelerle nasıl karşılaştırılacağının insafına bırakıyor (boş olmayan dizelerden önce gelir). Bunun nilyerine s'lerin listenin sonuna gelmesini istiyorsanız, bu biraz üstü kapalı, biraz sihir ve esnek değil. Benim bakmak tavsiye nilComparatorfonksiyon stackoverflow.com/a/44808567/3141234
eski durumuna Monica - İskender

19

2 kriter ile sıralama için başka bir basit yaklaşım aşağıda gösterilmiştir.

Öyle bu durumda, birinci alanı için kontrol edin lastNameonlar tarafından eşit sıralama değilse, lastNameeğer lastNames eşittir' bu durumda, daha sonra sıralama ikinci alana göre, firstName.

contacts.sort { $0.lastName == $1.lastName ? $0.firstName < $1.firstName : $0.lastName < $1.lastName  }

Bu, tuple'lerden daha fazla esneklik sağlar.
Babac

5

@Hamish tarafından açıklandığı gibi sözlükbilimsel sıralamaların yapamayacağı tek şey, farklı sıralama yönlerini ele almaktır, örneğin azalan ilk alana göre sıralama, bir sonraki alan artan, vb.

Swift 3'te bunun nasıl yapılacağına dair bir blog yazısı oluşturdum ve kodu basit ve okunaklı tuttum.

Burada bulabilirsiniz:

http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/

Burada koda sahip bir GitHub deposu da bulabilirsiniz:

https://github.com/jallauca/SortByMultipleFieldsSwift.playground

Hepsinin özü, diyelim ki, konum listeniz varsa, bunu yapabileceksiniz:

struct Location {
    var city: String
    var county: String
    var state: String
}

var locations: [Location] {
    return [
        Location(city: "Dania Beach", county: "Broward", state: "Florida"),
        Location(city: "Fort Lauderdale", county: "Broward", state: "Florida"),
        Location(city: "Hallandale Beach", county: "Broward", state: "Florida"),
        Location(city: "Delray Beach", county: "Palm Beach", state: "Florida"),
        Location(city: "West Palm Beach", county: "Palm Beach", state: "Florida"),
        Location(city: "Savannah", county: "Chatham", state: "Georgia"),
        Location(city: "Richmond Hill", county: "Bryan", state: "Georgia"),
        Location(city: "St. Marys", county: "Camden", state: "Georgia"),
        Location(city: "Kingsland", county: "Camden", state: "Georgia"),
    ]
}

let sortedLocations =
    locations
        .sorted(by:
            ComparisonResult.flip <<< Location.stateCompare,
            Location.countyCompare,
            Location.cityCompare
        )

1
"Sözlük türlerinin @Hamish tarafından açıklandığı gibi yapamayacağı tek şey, farklı sıralama yönlerini ele almaktır" - evet, demetlerdeki öğeleri değiştirebilirler;)
Hamish

Bunu ilginç bir teorik alıştırma buluyorum ama @ Hamish'in cevabından çok daha karmaşık. Bana göre daha az kod daha iyi koddur.
Manuel

5

Bu sorunun zaten birçok harika cevabı var, ancak bir makaleye işaret etmek istiyorum - Swift'de Açıklayıcıları Sırala . Çoklu ölçüt sıralaması yapmanın birkaç yolu var.

  1. NSSortDescriptor kullanıldığında, bu şekilde bazı sınırlamalar vardır, nesne bir sınıf olmalı ve NSObject'ten miras almalıdır.

    class Person: NSObject {
        var first: String
        var last: String
        var yearOfBirth: Int
        init(first: String, last: String, yearOfBirth: Int) {
            self.first = first
            self.last = last
            self.yearOfBirth = yearOfBirth
        }
    
        override var description: String {
            get {
                return "\(self.last) \(self.first) (\(self.yearOfBirth))"
            }
        }
    }
    
    let people = [
        Person(first: "Jo", last: "Smith", yearOfBirth: 1970),
        Person(first: "Joe", last: "Smith", yearOfBirth: 1970),
        Person(first: "Joe", last: "Smyth", yearOfBirth: 1970),
        Person(first: "Joanne", last: "smith", yearOfBirth: 1985),
        Person(first: "Joanne", last: "smith", yearOfBirth: 1970),
        Person(first: "Robert", last: "Jones", yearOfBirth: 1970),
    ]
    

    Burada, örneğin, soyadına, ardından ada ve son olarak doğum yılına göre sıralamak istiyoruz. Ve bunu duyarsız bir şekilde ve kullanıcının yerel ayarını kullanarak yapmak istiyoruz.

    let lastDescriptor = NSSortDescriptor(key: "last", ascending: true,
      selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
    let firstDescriptor = NSSortDescriptor(key: "first", ascending: true, 
      selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
    let yearDescriptor = NSSortDescriptor(key: "yearOfBirth", ascending: true)
    
    
    
    (people as NSArray).sortedArray(using: [lastDescriptor, firstDescriptor, yearDescriptor]) 
    // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
    
  2. Soyadı / adı ile Swift sıralama yöntemini kullanma. Bu yol hem sınıf / yapı ile çalışmalıdır. Ancak, burada doğum yılına göre sıralama yapmıyoruz.

    let sortedPeople = people.sorted { p0, p1 in
        let left =  [p0.last, p0.first]
        let right = [p1.last, p1.first]
    
        return left.lexicographicallyPrecedes(right) {
            $0.localizedCaseInsensitiveCompare($1) == .orderedAscending
        }
    }
    sortedPeople // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1985), Joanne smith (1970), Joe Smith (1970), Joe Smyth (1970)]
    
  3. NSSortDescriptor'u devreye sokmanın hızlı yolu. Bu, 'işlevler birinci sınıf türdür' kavramını kullanır. SortDescriptor bir işlev türüdür, iki değer alır ve bir bool döndürür. SortByFirstName deyin, iki parametre ($ 0, $ 1) alıp adlarını karşılaştırıyoruz. Birleştirme işlevleri, bir grup SortDescriptor alır, hepsini karşılaştırır ve sipariş verir.

    typealias SortDescriptor<Value> = (Value, Value) -> Bool
    
    let sortByFirstName: SortDescriptor<Person> = {
        $0.first.localizedCaseInsensitiveCompare($1.first) == .orderedAscending
    }
    let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth }
    let sortByLastName: SortDescriptor<Person> = {
        $0.last.localizedCaseInsensitiveCompare($1.last) == .orderedAscending
    }
    
    func combine<Value>
        (sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> {
        return { lhs, rhs in
            for isOrderedBefore in sortDescriptors {
                if isOrderedBefore(lhs,rhs) { return true }
                if isOrderedBefore(rhs,lhs) { return false }
            }
            return false
        }
    }
    
    let combined: SortDescriptor<Person> = combine(
        sortDescriptors: [sortByLastName,sortByFirstName,sortByYear]
    )
    people.sorted(by: combined)
    // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
    

    Bu iyidir çünkü onu hem struct hem de class ile kullanabilirsin, hatta sıfırlarla karşılaştırmak için genişletebilirsin.

Yine de orijinal makaleyi okumanız şiddetle tavsiye edilir. Çok daha fazla detayı var ve iyi açıklanmış.


2

Ekstra kod gerektirmediği için Hamish'in tuple çözümünü kullanmanızı tavsiye ederim .


ifİfadeler gibi davranan ancak dallanma mantığını basitleştiren bir şey istiyorsanız , aşağıdakileri yapmanıza izin veren bu çözümü kullanabilirsiniz:

animals.sort {
  return comparisons(
    compare($0.family, $1.family, ascending: false),
    compare($0.name, $1.name))
}

İşte bunu yapmanıza izin veren işlevler:

func compare<C: Comparable>(_ value1Closure: @autoclosure @escaping () -> C, _ value2Closure: @autoclosure @escaping () -> C, ascending: Bool = true) -> () -> ComparisonResult {
  return {
    let value1 = value1Closure()
    let value2 = value2Closure()
    if value1 == value2 {
      return .orderedSame
    } else if ascending {
      return value1 < value2 ? .orderedAscending : .orderedDescending
    } else {
      return value1 > value2 ? .orderedAscending : .orderedDescending
    }
  }
}

func comparisons(_ comparisons: (() -> ComparisonResult)...) -> Bool {
  for comparison in comparisons {
    switch comparison() {
    case .orderedSame:
      continue // go on to the next property
    case .orderedAscending:
      return true
    case .orderedDescending:
      return false
    }
  }
  return false // all of them were equal
}

Test etmek istiyorsanız, şu ekstra kodu kullanabilirsiniz:

enum Family: Int, Comparable {
  case bird
  case cat
  case dog

  var short: String {
    switch self {
    case .bird: return "B"
    case .cat: return "C"
    case .dog: return "D"
    }
  }

  public static func <(lhs: Family, rhs: Family) -> Bool {
    return lhs.rawValue < rhs.rawValue
  }
}

struct Animal: CustomDebugStringConvertible {
  let name: String
  let family: Family

  public var debugDescription: String {
    return "\(name) (\(family.short))"
  }
}

let animals = [
  Animal(name: "Leopard", family: .cat),
  Animal(name: "Wolf", family: .dog),
  Animal(name: "Tiger", family: .cat),
  Animal(name: "Eagle", family: .bird),
  Animal(name: "Cheetah", family: .cat),
  Animal(name: "Hawk", family: .bird),
  Animal(name: "Puma", family: .cat),
  Animal(name: "Dalmatian", family: .dog),
  Animal(name: "Lion", family: .cat),
]

Jamie'nin çözümünden temel farkları , özelliklere erişimin sınıfta statik / örnek yöntemleri yerine satır içi olarak tanımlanmasıdır. Örneğin $0.familyyerine Animal.familyCompare. Ve artan / azalan, aşırı yüklenmiş bir operatör yerine bir parametre tarafından kontrol edilir. Jamie'nin çözümü, Array üzerine bir uzantı ekler, benim çözümüm yerleşik sort/ sortedyöntemi kullanır, ancak iki tane daha tanımlanmasını gerektirir: compareve comparisons.

Bütünlük uğruna, benim çözümümün Hamish'in tuple çözümüyle karşılaştırması burada . Göstermek için, (name, address, profileViews)Hamish'in çözümüne göre insanları sıralamak istediğimiz vahşi bir örnek kullanacağım , karşılaştırma başlamadan önce 6 özellik değerinin her birini tam olarak bir kez değerlendirecek. Bu istenmeyebilir veya istenmeyebilir. Örneğin, profileViewspahalı bir şebeke araması olduğunu varsayarsak , profileViewskesinlikle gerekli olmadıkça aramadan kaçınmak isteyebiliriz . Benim çözümüm ve profileViewstarihine kadar değerlendirmekten kaçınacak . Bununla birlikte, değerlendirdiğinde , muhtemelen birden çok kez değerlendirilecektir.$0.name == $1.name$0.address == $1.addressprofileViews


1

Peki ya:

contacts.sort() { [$0.last, $0.first].lexicographicalCompare([$1.last, $1.first]) }

lexicographicallyPrecedesdizideki tüm türlerin aynı olmasını gerektirir. Örneğin [String, String]. OP'nin muhtemelen istediği şey türleri karıştırıp eşleştirmektir: [String, Int, Bool]böylece yapabilirlerdi [$0.first, $0.age, $0.isActive].
Senseful

-1

Swift 3'teki [String] dizim için çalıştı ve Swift 4'te sorun yok gibi görünüyor

array = array.sorted{$0.compare($1, options: .numeric) == .orderedAscending}

Cevap vermeden önce soruyu okudunuz mu? Sunduklarınıza göre değil, birden çok parametreye göre sıralayın.
Vive
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.