Neden iç içe döngüler kötü uygulama olarak kabul edilir?


33

Eğitmenim bugün, iç içe geçmiş döngülerle ilgilenirken bunlara başvurabilmeniz için Java'daki döngüleri "etiketlemenin" mümkün olduğunu belirtti. Bu yüzden, bilmediğim bir özelliğe baktım ve bu özelliğin açıklandığı pek çok yerde onu iç içe döngülerden vazgeçiren bir uyarı izledi.

Nedenini gerçekten anlamıyorum? Kodun okunabilirliğini etkilediği için mi? Yoksa daha "teknik" bir şey mi?


4
CS3 dersimi doğru hatırlıyorsam, bunun nedeni genellikle üssel süreye yol açmasıdır; bu, eğer büyük bir veri kümesi elde ederseniz, uygulamanız kullanılamaz hale gelir.
Travis Pessetto

21
CS eğitmenleri hakkında öğrenmeniz gereken bir şey, söyledikleri her şeyin gerçek dünyada% 100 uygulanmadığıdır. Birkaç derinlemeden daha fazla iç içe geçmiş olan halkaları cesaretlendirmezdim, ancak sorununuzu çözmek için m x n öğelerini işlemeniz gerekirse, bu kadar fazla işlemi yapacaksınız.
Blrfl

8
@TravisPessetto Aslında hala polinom karmaşıklığı - O (n ^ k), k yuvalanmış, üstel değil O (k ^ n), burada k sabit.
m3th0dman

1
@ m3th0dman Beni düzelttiğin için teşekkürler. Öğretmenim bu konuda en iyi değildi. O (n ^ 2) ve O (k ^ n) 'ya aynı şekilde davrandı.
Travis Pessetto

2
İç içe döngüler bazı insanlara göre programın sürdürülmesini azaltan döngüsel karmaşıklığı arttırır ( buraya bakın ).
Marco,

Yanıtlar:


62

İç içe döngüler doğru algoritmayı tanımladıkları sürece iyidir.

İç içe geçmiş döngülerin performans sorunları vardır (bkz. @ Travis-Pesetto'nun yanıtı), ancak bazen tam olarak doğru algoritmadır, örneğin bir matristeki her değere erişmeniz gerektiğinde.

Java'da etiketleme döngüleri, bunu yapmanın başka yolları zahmetli olduğunda, iç içe geçmiş birkaç döngüden erken ayrılmasını sağlar. Örneğin bazı oyunlarda bunun gibi bir kod parçası olabilir:

Player chosen_one = null;
...
outer: // this is a label
for (Player player : party.getPlayers()) {
  for (Cell cell : player.getVisibleMapCells()) {
    for (Item artefact : cell.getItemsOnTheFloor())
      if (artefact == HOLY_GRAIL) {
        chosen_one = player;
        break outer; // everyone stop looking, we found it
      }
  }
}

Yukarıdaki örnek gibi kod bazen belirli bir algoritmayı ifade etmenin en uygun yolu olsa da, bu kodu daha küçük işlevlere bölmek ve muhtemelen returnyerine kullanmak daha iyidir break. Yani bir breaketiket ile hafif bir kod kokusu ; Gördüğünde fazladan dikkat et.


2
Yandaki gibi, grafikler bir matrisin her parçasına erişmesi gereken bir algoritma kullanır. Ancak, GPU bunu zaman açısından verimli bir şekilde ele almak için uzmanlaşmıştır.
Travis Pessetto

Evet, GPU bunu büyük ölçüde paralel bir şekilde yapıyor; Sanırım soru, tek bir yürütme işiyle ilgiliydi.
9000

2
Etiketlerin şüpheli olma nedenlerinden biri, genellikle bir alternatif olduğu içindir. Bu durumda bunun yerine geri dönebilirsiniz.
jgmjgm

22

İç içe döngüler sık ​​sık (ancak her zaman değil) kötü uygulamalardır, çünkü yapmaya çalıştığınız şey için sık sık (ancak her zaman değil) fazladırlar. Çoğu durumda, ulaşmaya çalıştığınız hedefi gerçekleştirmenin daha hızlı ve daha az israflı bir yolu vardır.

Örneğin, A listesinde 100 öğeniz ve B listesinde 100 öğeniz varsa ve A listesindeki her öğe için B listesinde eşleşen bir öğenin olduğunu biliyorsunuzdur (kasıtlı olarak bırakılan "eşleşme" tanımıyla). Burada), ve bir çift listesi üretmek istiyorsanız, bunu yapmanın basit yolu şudur:

for each item X in list A:
  for each item Y in list B:
    if X matches Y then
      add (X, Y) to results
      break

Her listedeki 100 öğe ile bu ortalama 100 * 100/2 (5.000) matchesişlem alacaktır . Daha fazla öğe ile veya 1: 1 korelasyonu garanti edilmezse, daha da pahalı hale gelir.

Öte yandan, böyle bir işlemi gerçekleştirmek için çok daha hızlı bir yol var:

sort list A
sort list B (according to the same sort order)
I = 0
J = 0
repeat
  X = A[I]
  Y = B[J]
  if X matches Y then
    add (X, Y) to results
    increment I
    increment J
  else if X < Y then
    increment I
  else increment J
until either index reaches the end of its list

Bu şekilde yaparsanız, dayanmak istediğiniz matchesişlemlerin sayısı yerine, length(A) * length(B)şimdi de dayanır length(A) + length(B), bu da kodunuzun çok daha hızlı çalışacağı anlamına gelir.


10
O(n log n)Quicksort kullanılıyorsa , sıralama düzeninin önemsiz olmayan bir süre alması iki kez sürülür.
Robert Harvey

@RobertHarvey: Elbette. Ama bu hala O(n^2)N'nin küçük olmayan değerlerinden çok daha az
Mason Wheeler

10
Algoritmanın ikinci versiyonu genellikle yanlıştır. İlk olarak, X ve Y'nin <genellikle matchesoperatörden elde edilemeyen operatör ile karşılaştırılabilir olduğu varsayılmaktadır . İkincisi, hem X hem de Y sayısal olsalar da, 2. algoritma, örneğin ne zaman X matches Yolduğu gibi yine de yanlış sonuçlar üretebilir X + Y == 100.
Paşa

9
@ user958624: Açıkçası bu genel bir algoritmaya çok üst düzey bir genel bakış. "Eşleşmeler" işlecinde olduğu gibi, "<" karşılaştırılan veriler bağlamında doğru olacak şekilde tanımlanmalıdır. Bu doğru yapılırsa, sonuçlar doğru olacaktır.
Mason Wheeler

PHP, bence benzersiz olan dizileri ile sonuncusu gibi bir şey yaptı ve / veya tam tersi. İnsanlar bunun yerine sadece PHP'nin karma tabanlı dizilerini kullanırlar ve çok daha hızlı olurlardı.
jgmjgm

11

İç içe geçme döngülerinden kaçınmanın bir nedeni, döngüsel yapı olup olmadıklarına bakılmaksızın blok yapılarını çok derinlemesine yuvalamanın kötü bir fikir olmasıdır.

Her bir işlev veya yöntemin anlaşılması kolay olmalı, hem amacı (adı ne yaptığını ifade etmelidir) hem de bakım verenler için (dahilileri anlamak kolay olmalıdır). Bir fonksiyon kolayca anlaşılamayacak kadar karmaşıksa, bu genellikle iç kısımların bazılarının ayrı fonksiyonlara ayrılması gerektiği anlamına gelir, bu nedenle (şimdi daha küçük) ana fonksiyonda ismine göre adlandırılabilirler.

İç içe geçen döngülerin nispeten hızlı bir şekilde anlaşılması zor olabilir, ancak bazı döngülerin iç içe geçmesi iyi olur, diğerleri belirttiği gibi, aşırı (ve gereksiz yere) yavaş bir algoritma kullanarak performans sorunu oluşturduğunuz anlamına gelmez.

Aslında, aşırı yavaş performans sınırlamaları elde etmek için iç içe döngüler gerekmez. Örneğin, her yinelemede bir öğeyi kuyruktan alan tek bir döngü düşünün, ardından birkaç geri koyar - örneğin bir labirentin genişliğini ilk kez araştırır. Performansa, döngünün iç içe geçme derinliği (yalnızca 1 olan) değil, sonunda bitmeden önce bu sıraya konan öğe sayısı (henüz tükenirse) tarafından karar verilebilir - labirent olduğunu.


1
Sık sık iç içe geçmiş bir döngüyü düzleştirebilirsiniz ve yine de aynı zaman alır. 0 için genişlik, 0 için yükseklik; Bunun yerine sadece 0'a genişlik kez yükseklik koyabilirsiniz.
jgmjgm

jgmjgm - evet, döngü içindeki kodu karmaşıklaştırma riski altında. Düzleştirme bunu bazen de basitleştirebilir, ancak daha sık olarak, en azından gerçekten istediğiniz endeksleri kurtarma karmaşıklığını ekliyorsunuzdur. Bunun bir püf noktası, özel bir bileşik indeksi arttırmak için tüm döngüyü mantığa yerleştiren tüm döngüyü etkileyen bir indeks tipi kullanmaktır - bunu sadece bir döngü için yapma ihtimaliniz yoktur, ama belki benzer yapılara sahip birden fazla döngüye sahip olabilirsiniz veya belki daha esnek bir genel sürüm yazabilirsiniz. Bu tür (eğer varsa) kullanmak için genel giderler netlik için buna değer olabilir.
Steve314,

Bunu yapmak için iyi bir şey olarak önermiyorum, ancak iki döngüyü bir haline getirmek ne kadar şaşırtıcı derecede basit olabilir, ancak zaman karmaşıklığı üzerinde hala bir etkisi olmayabilir.
jgmjgm

7

Çok sayıda iç içe döngü olması durumunda, polinom zamanı ile bitirdiniz. Örneğin bu sözde kodu verilen:

set i equal to 1
while i is not equal to 100
  increment i
  set j equal to 1
  while j is not equal to i
    increment j
  end
 end

Bu, şuna benzer bir grafik olacak olan O (n ^ 2) süre olarak kabul edilir: görüntü tanımını buraya girin

Y ekseninin programınızın sonlandırılması için gereken süre ve x ekseni veri miktarı olduğu yerlerde.

Çok fazla veri alırsanız, programınız çok yavaş olacaktır. Hiç kimse onu bekleyemez. ve Binlerce veri girişi yapmayı çok uzun sürmeyeceğine inanıyorum.


10
grafiğinizi yeniden seçmek isteyebilirsiniz: bu, ikinci dereceden bir eğri değil üstel bir eğridir, özyineleme O (n log n) 'i O (n ^ 2)' den O (n log n) 'ye kadar yapmaz
cırcır

5
Özyineleme için iki kelime "yığın" ve "taşma" var
Mateusz

10
Özyinelemenin bir O (n ^ 2) işlemini bir O (n log n) seviyesine kadar azaltabildiğini ifade etmeniz büyük oranda yanlış. Yinelemeli olarak yinelemeli olarak uygulanan aynı algoritma, aynı büyük O zaman karmaşıklığına sahip olmalıdır. Ayrıca, tekrarlama genellikle daha yavaş olabilir (dilin uygulanmasına bağlı olarak), çünkü her bir özyinelemeli çağrı yeni bir yığın çerçevesi oluşturulmasını gerektirirken yineleme sadece bir dal / karşılaştırmayı gerektirir. İşlev çağrıları genellikle ucuzdur, ancak ücretsiz değildir.
dckrooney

2
@TravisPessetto Son 6 ayda 3 tane yığın taşması gördüm, özyineleme veya döngüsel nesne referansları nedeniyle C # uygulamaları geliştirdim. Komik olan şey çökecek ve size neyin çarptığını bilmiyorsunuz. İç içe döngüler gördüğünüzde, kötü bir şeyin olabileceğini bilirsiniz ve bir şey için yanlış indeks ile ilgili istisna kolayca görülebilir.
Mateusz

2
@Mateusz ayrıca Java gibi diller bir yığın taşması hatası yakalamanıza izin verir. Bu bir yığın izlemeyle ne olduğunu görmenize izin vermelidir. Çok fazla tecrübem yok, ancak yığın taşması hatası gördüğüm tek zaman, sonsuz bir özyinelemeye neden olan bir PHP'de ve PHP kendisine atanmış olan 512 MB bellekten bitiyor. Özyinelemenin son sonlandırma değerine sahip olması gerekir, sonsuz döngüler için iyi değildir. CS'deki her şey gibi, her şey için bir zaman ve yer vardır.
Travis Pessetto

0

Küçük bir binek araç yerine otuz tonluk bir kamyon kullanmak kötü bir uygulamadır. 20 ya da 30 ton eşya taşıması gerektiğinde.

Yuvalanmış bir döngü kullandığınızda, bu kötü bir uygulama değildir. Ya tamamen aptalca, ya da tam olarak ihtiyaç duyulan şey. Sen karar ver.

Ancak, birileri döngü etiketleme konusunda şikayet etti . Bunun cevabı: soruyu sormak zorundaysanız, etiketleme kullanmayın. Kendine karar verecek kadar bilgin varsa, kendine karar ver.


Kendinize karar verecek kadar bilginiz varsa, etiketli halkaları kullanmamanız için yeterli bilginiz vardır. :-)
user949300,

Hayır. Yeterince öğretildiğinde, etiketli kullanımı kullanmamalısın. Yeterince bildiğiniz zaman dogmanın ötesine geçiyor ve doğru olanı yapıyorsunuz.
gnasher729

1
Etiketteki sorun, nadiren ihtiyaç duyulması ve birçok insanın akış kontrol hataları üzerinde çalışmak için erken kullanmasıdır. Akış kontrolünü yanlış aldıklarında, insanların her yerden her yere çıkma şekli. İşlevler ayrıca büyük ölçüde gereksiz hale getirir.
jgmjgm

-1

İç içe geçmiş döngüler için doğal olarak yanlış veya zorunlu olarak bile kötü bir şey yoktur. Ancak, bazı önemli hususları ve tuzakları var.

Öncelikle kısaltılmış olduğunuz ya da yanık olarak bilinen psikolojik bir süreç yüzünden yönlendirdiğiniz makaleler, ayrıntıların üzerinden geçiyor.

Yanmış olmak, ima eden varlıkla ilgili olumsuz bir deneyim yaşadığınızda ve bundan kaçınmanız demektir. Mesela sebzeleri keskin bir bıçakla kesip kendimi kesebilirim. Daha sonra keskin bıçakların kötü olduğunu söyleyebilirim, bu kötü deneyimin bir daha yaşanmasını imkansız kılmak için onları sebze kesmek için kullanmadıklarını söyleyebilirim. Bu kesinlikle çok pratik değil. Gerçekte, sadece dikkatli olmanız gerekir. Başka birine sebze kesmesini söylüyorsanız, o zaman daha da güçlü bir şekilde hissedersiniz. Çocuklara sebze kesmelerini söyleseydim, özellikle onları yakından denetleyemezsem, keskin bir bıçak kullanmamalarını söylerken çok kuvvetli hissederdim.

Sorun programlamada, her zaman önce güvenliği tercih ediyorsanız en yüksek verimlilikle karşılaşmamanızdır. Bu durumda çocuklar sadece yumuşak sebzeleri kesebilir. Başka bir şeyle karşı karşıya kaldıklarında sadece keskin bir bıçak kullanarak karışıklık çıkaracaklar. Yuvalanmış döngüler dahil olmak üzere döngülerin uygun kullanımını öğrenmek önemlidir ve kötü sayılırlarsa ve bunları kullanmaya çalışmazsanız bunu yapamazsınız.

Buradaki birçok cevap, iç içe geçme için bir yuvaya işaret ettiğinden, programınızın performans artış özelliklerinin bir göstergesidir; bu da her bir yuvaya katlanarak kötüleşebilir. Bu, O (n), O (n ^ 2), O (n ^ 3) ve benzerlerini içerir; burada derinlik iç içe geçirdiğiniz kaç döngüyü temsil eder, O (n ^ derinlik). Yuvalama işleminiz büyüdükçe, gereken zaman katlanarak büyür. Bununla ilgili sorun şu ki, zamanınız veya mekan karmaşıklığınızın (oldukça sık sık bir * b * c ama tüm yuva döngülerinin her zaman çalışabileceği) olacağının kesinliği değil, ne de sizin öyle olsa bile bir performans sorunu var.

Pek çok insan için, özellikle de öğrenciler, dürüst olmak gerekirse, döngüler için günlük yaşamda nadiren program yapan yazarlar ve öğretim görevlileri, alışkın olmadıkları ve erken karşılaşmalarda çok fazla bilişsel yüke neden olan bir şey olabilir. Bu sorunlu bir durumdur çünkü daima bir öğrenme eğrisi vardır ve onlardan kaçınmak öğrencileri programcılara dönüştürmede etkili olmayacaktır.

İç içe döngüler çılgınca gidebilir, yani çok derinlere yuvalanmış olarak sona erebilirler. Her kıtaya, daha sonra her bir ülkeye, sonra her bir şehre, sonra her bir mağazaya, daha sonra her rafa, sonra her bir ürüne bakacak olursam, her bir fasulyeden bir fasulye kovanı alıp ortalamayı elde etmek için boyutunu ölçmek Bunun çok derine yuvalanacağını görebiliyorum. Sol kenar boşluğundan uzakta bir piramit ve boşa harcanacak bir yeriniz olacak. Sayfadan çıkmayı bile başarabilirsin.

Bu, ekranların küçük ve düşük çözünürlükte olması tarihsel olarak daha önemli bir sorundur. Bu durumlarda, birkaç iç içe geçme seviyesi bile gerçekten çok fazla alan kaplayabilir. Bu, günümüzde eşiğin daha yüksek olduğu yerlerde daha az bir endişe kaynağı olmasına rağmen, yeterli yuvalama varsa hala sorun yaratabilir.

İlgili estetik argümandır. Birçok insan, daha tutarlı bir hizalamaya sahip yerleşim düzenlerinin aksine, insanların alışkın olduğu, göz takibi ve diğer kaygılarla bağlantılı olabileceği veya bununla bağlantılı olamayacak şekilde estetik olarak hoş görünen döngüler iç içe bulamazlar. Bununla birlikte, kendi kendini pekiştirme eğiliminde olması ve sonuçta kodun bir blok bloğunun parçalanması ve soyutlamaların arkasına sarılması gibi fonksiyonların soyutlanması arkasındaki kodların yürütme akışına eşleştirilmesi riskini almak için okumayı zorlaştırması problemlidir.

İnsanların alıştığı şeylere karşı doğal bir eğilim var. Eğer bir şeyi en basit şekilde programlıyorsanız, yuvalamaya ihtiyaç duymama olasılığı en yüksek, bir seviyeye ihtiyaç duyma olasılığı büyüklük sırasına göre düşer, başka bir seviye için olasılık tekrar düşer. Frekans düşüyor ve esasen iç içe geçme derinliği, insan duyularının ne kadar az eğitimli olduğunu önceden tahmin ediyor.

Bununla ilişkili olarak, iç içe bir döngünün düşünülebileceği herhangi bir karmaşık yapıda, o zaman her zaman sormanız gereken, daha az döngüye ihtiyaç duyan cevapsız bir çözüm potansiyeli olduğu için mümkün olan en basit çözümün olmasıdır. Buradaki ironi, iç içe geçmiş bir çözümün, asgari çaba, karmaşıklık ve bilişsel yük ile çalışan bir şey üretmenin en basit yoludur. Döngüler için yuva yapmak genellikle doğaldır. Örneğin, döngü için iç içe geçmekten çok daha hızlı bir yolun da çok daha karmaşık olduğu ve önemli ölçüde daha fazla koddan oluştuğu yukarıdaki yanıtlardan birini düşünüyorsanız.

Döngüleri soyutlamak ya da düzleştirmek için çoğu zaman mümkün olduğu için son derece özen gösterilmesi gerekir, ancak sonuçta hastalıktan daha kötü bir tedavi olabilir, özellikle de örneğin çabadan ölçülebilir ve anlamlı bir performans artışı elde edemiyorsanız.

İnsanların sık sık performans sorunları yaşamaları çok yaygındır; bu işlem, bilgisayara bir eylemi birçok kez tekrar etmesini söyleyen ve doğal olarak genellikle performans darboğazlarına karışacak olan döngülerle ilgilidir. Maalesef buna verilen cevaplar çok yüzeysel olabilir. İnsanların bir döngü görmesi ve hiçbir şeyin olmadığı bir performans problemi görmesi ve daha sonra döngüyü görünmeden gerçek bir etkiye kadar gizlememesi yaygınlaşır. Kod "hızlı" görünüyor, ancak yola koydu, kontağı açın, gaz pedalını döşeyin ve hız ölçere bir bakın ve bunun hala onun zimmer karesinde yürüyen yaşlı bir kadın kadar hızlı olduğunu görebilirsiniz.

Bu tür bir gizleme, rotanız üzerinde on kupa sahibi olmanıza benzer. Gitmek istediğiniz yere giden düz bir rotaya sahip olmak yerine, onu her köşenin arkasında bir kupa olacak şekilde düzenlersiniz, o zaman yolculuğunuza başlamazken yanıltıcı olmaz. Gözden ırak olan gönülden de ırak olur. hala on kez tecavüze uğrayacaksın ama şimdi bunun geldiğini görmeyeceksin.

Sorunuzun cevabı her ikisinin de olduğu, ancak endişelerin hiçbiri kesin değil. Ya tamamen özneldirler ya da yalnızca bağlamsal olarak nesneldirler. Maalesef bazen tamamen öznel veya daha doğrusu görüş emsal alır ve hâkimdir.

Temel bir kural olarak, iç içe geçmiş bir döngüye ihtiyaç duyuyorsa veya bir sonraki açık adıma benziyorsa, bunu kasıtlı yapmamak ve basitçe yapmak en iyisidir. Ancak herhangi bir şüphe varsa, o zaman daha sonra gözden geçirilmelidir.

Diğer bir kural, kardinaliteyi her zaman kontrol etmeniz ve kendinize sormanızın, bu döngünün bir problem olacağıdır. Önceki örneğimde şehirlerden geçtim. Testler için sadece on şehirden geçebilirim ama gerçek dünya kullanımında beklenebilecek makul maksimum şehir sayısı nedir? O zaman bunu kıtalar için aynı şekilde çarpabilirim. Her zaman döngülerle birlikte düşünmek bir kuraldır, özellikle de çizgide aşağıya çevrilebilecek dinamik (değişken) bir süreyi tekrarlar.

Ne olursa olsun, her zaman önce işe yarayanı yapın. Optimizasyon için bir fırsat gördüğünüzde, optimize edilmiş çözümünüzü çalışmak için en kolay olanla karşılaştırabilir ve beklenen faydaları sağladığını doğrulayabilirsiniz. Ölçümler gelmeden ve YAGNI'ye veya çok fazla boşa harcanan zaman ve cevapsız son teslim tarihine neden olmadan önce erken optimizasyon yapmak için çok uzun zaman harcayabilirsiniz.


Keskin <-> künt bıçak örneği mükemmel değildir, çünkü künt bıçaklar kesim için genellikle daha tehlikelidir. Ve O (n) -> O (n ^ 2) -> O (n ^ 3) değil , üstel olarak n, bu kadar geometrik veya polinom .
Caleth

Donuk bıçakların daha kötü olması, biraz şehir efsanesidir. Uygulamada, donuk bıçakların genellikle çok fazla güç gerektiren ve çok fazla kayma gerektiren özellikle uygun olmadığı durumlara göre değişir ve genellikle spesifiktir. Haklısın, orada gizli ama doğal bir sınırlama var, sadece yumuşak sebzeleri kesebilirsin. N ^ 'in üstel olduğunu düşünürdüm ama haklısın, bu örnekleri kendi başlarına değil.
jgmjgm

@jgmjgm: n ^ derinlik polinom, derinlik ^ n üstel. Bu gerçekten bir yorum meselesi değil.
Roel Schroeven

x ^ y, y ^ x'den farklı mı? Yaptığın hata, cevabı hiç okumadığın. Üstellik için yalvardınız ve kendi başlarına üstel olmayan denklemler için yalvardınız. Eğer okursanız her iç içe geçme katmanı için katlanarak arttığını göreceksiniz ve bunu sınava koyarsanız, (a = 0; a <n; a ++); için (b = 0; b <n) b ++); (c = 0, c <n c ++); döngüler eklerken veya çıkarırken, bunun gerçekten üstel olduğunu göreceksiniz. Bir döngü n ^ 1'de gerçekleştirilir, iki n ^ 2'de gerçekleştirilir ve üç n ^ 3'te gerçekleştirilir. İç içe anlamayı başaramazsın: D. Gemoetric altkümeleri üstel.
jgmjgm

Sanırım bu, insanların iç içe geçmiş yapılarla gerçekten mücadele ettikleri bir noktayı kanıtlıyor.
jgmjgm
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.