Як працює підрядка String в Swift


354

Я оновлював частину свого старого коду та відповідей за допомогою Swift 3, але коли я потрапив до Swift Strings та Indexing з підрядками, речі заплуталися.

Зокрема, я намагався:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

де другий рядок давав мені таку помилку

Значення типу 'String' не має члена 'substringWithRange'

Я бачу, що Stringзараз є такі методи:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

Спочатку вони нас справді заплутали, тому я почав грати в індекс і діапазон . Це подальше запитання та відповідь для підрядок. Я додаю відповідь нижче, щоб показати, як вони використовуються.


2
Для тих, хто хоче отримати підрядку з рядка stackoverflow.com/q/32305891/468724
Inder Kumar Rathore,

або нижній індекс рядка або підрядка stackoverflow.com/questions/24092884 / ...
Leo Dabus

Відповіді:


831

введіть тут опис зображення

Усі наведені нижче приклади використовують

var str = "Hello, playground"

Швидкий 4

Струни отримали досить великий капітальний ремонт у Swift 4. Коли зараз ви отримаєте деяку підрядку з String, ви отримаєте Substringтип назад, а не a String. Чому це? Рядки - це типові значення в Swift. Це означає, що якщо ви використовуєте одну рядок для створення нової, її потрібно скопіювати. Це добре для стабільності (ніхто більше не збирається змінювати її без вашого відома), але погано для ефективності.

З іншого боку, Підрядка - це посилання на початкову рядок, з якої вона вийшла. Ось зображення з документації, що ілюструє це.

Копіювання не потрібно, тому це набагато ефективніше використовувати. Однак уявіть, що ви отримали десять символів Substring з мільйона символів String. Оскільки Підрядка посилається на Рядок, система повинна буде триматися на всій Строці до тих пір, поки Підрядка буде навколо. Таким чином, щоразу, коли ви маніпулюєте своєю підрядкою, перетворіть її на рядок.

let myString = String(mySubstring)

Це скопіює лише підрядок, і пам'ять, що містить стару струну, може бути відтворена . Підрядки (як тип) мають бути короткочасними.

Ще одне велике вдосконалення Swift 4 полягає в тому, що струни - це колекції (знову ж таки). Це означає, що все, що ви можете зробити для колекції, ви можете зробити для String (використовувати підписки, повторити символи, фільтрувати тощо).

Наступні приклади показують, як отримати підрядку в Swift.

Отримання підрядків

Ви можете отримати підрядок з рядка за допомогою індексів або ряд інших методів (наприклад, prefix, suffix, split). Ви все ще повинні використовувати, String.Indexа не Intіндекс для діапазону. (Дивіться мою іншу відповідь, якщо вам потрібна допомога з цим.)

Початок струни

Ви можете використовувати підписку (зверніть увагу на односторонній діапазон Swift 4):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

або prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

або навіть простіше:

let mySubstring = str.prefix(5) // Hello

Кінець рядка

Використання підписок:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

або suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

або навіть простіше:

let mySubstring = str.suffix(10) // playground

Зауважте, що при використанні suffix(from: index)мені довелося відлік від кінця, використовуючи -10. Це не обов'язково при простому використанні suffix(x), яке бере лише останні xсимволи рядка.

Діапазон в рядку

Тут ми знову просто використовуємо підписки.

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range]  // play

Перетворення SubstringнаString

Не забувайте, коли ви готові зберегти свою підрядку, вам слід перетворити її на Stringтак, щоб можна було очистити стару пам'ять рядка.

let myString = String(mySubstring)

Використовуєте Intрозширення індексу?

Я вагаюся використовувати розроблене Intна основі індексу ознайомлення, прочитавши статтю Strings in Swift 3 від Airspeed Velocity та Оле Бегемана. Хоча у Swift 4, Strings є колекціями, команда Swift спеціально не використовує Intіндекси. Це досі String.Index. Це пов'язано з тим, що символи Swift складаються з різної кількості кодових точок Unicode. Фактичний індекс повинен бути однозначно обчислений для кожного рядка.

Треба сказати, я сподіваюся, що команда Свіфт знайде спосіб абстрагуватися String.Indexв майбутньому. Але до тих пір, поки я не вирішу використовувати їх API. Це допомагає мені пам'ятати, що маніпуляції з рядками - це не просто просте Intпошуку індексів.


9
Thx для дескрипції. Добре заслужене управління. Apple це ускладнила. Підрядка повинна бути такою ж простою, як string.substring [від ... до].
Тедді

Дійсно гарне пояснення. крім однієї дрібниці garbage collected;-) Я сподіваюся, що люди тут знають, що у Свіфті немає сміттєзбору.
Крістіан Анкор Дампф

@ChristianAnchorDampf, Дякую, що знайшли час для коментарів. Я вивіз збирання сміття. Як формулюється нова редакція?
Сурагч

Яка дивовижна відповідь, сер !!
davidev

194

Я дуже розчарований у моделі доступу Swift's String: все повинно бути Index. Все, що я хочу - це отримати доступ до i-го символу рядка за допомогою Int, а не незграбного індексу та просування (що трапляється змінюватися з кожним головним випуском). Тому я зробив розширення до String:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[startIndex..<endIndex])
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play

5
Індекси дуже корисні, оскільки символ може бути більше одного байта. Спробуйтеlet str = "🇨🇭🇩🇪🇺🇸Hello" print(str.substring(to: 2))
vadian

110
Так, я розумію, що символ (тобто розширений кластер графеми ) може приймати кілька байтів. Моє розчарування в тому, що для доступу до символів рядка нам доводиться використовувати метод просування вербового індексу. Чому команда Swift не може просто додати деякі перевантаження до Core Library, щоб відмовитись від неї. Якщо я набираю текст str[5], я хочу отримати доступ до символу в індексі 5, незалежно від того, який символ є, або скільки байтів він займає. Хіба Swift не стосується продуктивності розробника?
Код Різне

6
@RenniePet Я вважаю, що Apple визнає проблему, і зміни відбуваються. Відповідно до сторінки Swift Evolution на GitHub: "Swift 4 прагне зробити рядки більш потужними та легшими у використанні, зберігаючи правильність Unicode за замовчуванням". Це невиразно, але давайте продовжуватимемо свої сподівання
Code Different

3
@CodeDifferent чому apple не додав доступ до підпису символів? Щоб люди зрозуміли, що це погано робити. В основному, якщо ви зробите це для i в 0..string.count, використовуючи підписки, які будуть подвійним циклом, тому що під індексом капота необхідно пройти кожен байт рядка, щоб дізнатися, який наступний символ. Якщо ви циклічно використовуєте індекс, ви повторюєте рядок лише один раз. Btw, ненавиджу це сам, але це міркування, що підписка недоступна в рядку швидко.
Раймундас Сакалаускас

4
@RaimundasSakalauskas цей аргумент не летить у мене. У C # є як правильність Unicode, так і ціла підписка, що дуже зручно. У Swift 1 Apple хотіла, щоб розробники countElement(str)могли знайти довжину. У Swift 3 Apple зробила рядок невідповідною Sequenceі змусила всіх використовувати str.charactersзамість цього. Ці хлопці не бояться вносити зміни. Їх впертість на цілі підписки справді важко зрозуміти
Code Different

102

Розширення Swift 5:

extension String {
    subscript(_ range: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
        let end = index(start, offsetBy: min(self.count - range.lowerBound, 
                                             range.upperBound - range.lowerBound))
        return String(self[start..<end])
    }

    subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
         return String(self[start...])
    }
}

Використання:

let s = "hello"
s[0..<3] // "hel"
s[3...]  // "lo"

Або unicode:

let s = "😎🤣😋"
s[0..<1] // "😎"

2
Набагато краще, дякую за публікацію цього розширення! Я думаю, що приїхати з Python, Свіфт набагато складніше, ніж потрібно звикнути. Здається, для людей, які рухаються в іншому напрямку від Цілі C до Свіфта, є більш позитивне підтвердження.
користувач3064009

1
@Leon Я щойно її видалив. До 4.1 countбув доступний лише вself.characters
Лу Зелл

1
Чи є якісь моменти, щоб слідкувати за цим розширенням? Чому Apple не зробила щось подібне?
Андц

1
@Andz це дуже неефективно. Він починається з початку рядка - двічі - і повинен проаналізувати кожен символ звідти до "діапазону" - двічі.
кареман

3
Вам також потрібно буде додати розширення, яке займає позначку,CountableClosedRange<Int> якщо ви хочете написати, наприклад s[0...2].
Кріс Фредерік

24

Swift 4 і 5:

extension String {
  subscript(_ i: Int) -> String {
    let idx1 = index(startIndex, offsetBy: i)
    let idx2 = index(idx1, offsetBy: 1)
    return String(self[idx1..<idx2])
  }

  subscript (r: Range<Int>) -> String {
    let start = index(startIndex, offsetBy: r.lowerBound)
    let end = index(startIndex, offsetBy: r.upperBound)
    return String(self[start ..< end])
  }

  subscript (r: CountableClosedRange<Int>) -> String {
    let startIndex =  self.index(self.startIndex, offsetBy: r.lowerBound)
    let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
    return String(self[startIndex...endIndex])
  }
}

Як ним користуватися:

"abcde" [0] -> "a"

"abcde" [0 ... 2] -> "abc"

"abcde" [2 .. <4] -> "cd"


20

Швидкий 4

Швидко 4 Stringвідповідає Collection. Замість того substring, тепер ми повинні використовувати , subscript.так що якщо ви хочете , щоб вирізати тільки слово "play"з "Hello, playground", ви могли б зробити це в такий спосіб :

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

Цікаво знати, що робити це дасть вам Substringзамість String. Це швидко та ефективно, оскільки Substringвін зберігає сховище з оригінальним рядком. Однак обмін пам'яттю таким чином також може легко призвести до витоку пам'яті.

Ось чому ви повинні скопіювати результат у нову рядок, як тільки ви хочете очистити початкову рядок. Це можна зробити за допомогою звичайного конструктора:

let newString = String(result)

Додаткову інформацію про новий Substringклас ви можете знайти в [документації Apple]. 1

Отже, якщо ви, наприклад, отримаєте Rangeрезультат a NSRegularExpression, ви можете використовувати таке розширення:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}

Ваш код вийде з ладу, якщо range.upperBound дорівнює> довжина рядка. Також, корисно було б також вибіркове використання, оскільки я не був знайомий з підписками в Swift. Ви можете включити щось на зразок datePartOnly = "2018-01-04-08: 00" [NSMakeRange (0, 10)]. Крім цього, дуже приємна відповідь, +1 :).
dcp

сьогодні це дивна річ: text[Range( nsRange , in: text)!]
Fattie

10

Ось функція, яка повертає підрядку даної підрядки, коли надаються індекси початку та кінця. Для повної довідки ви можете відвідати посилання, наведені нижче.

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

Ось посилання на допис у блозі, який я створив, щоб швидко боротися зі стрижковими маніпуляціями. Рядна маніпуляція швидко (також охоплює стрімкий 4)

Або ви можете побачити цю суть на github


9

У мене була така ж початкова реакція. Я теж був розчарований тим, як синтаксис та об'єкти змінюються настільки різко у кожному великому випуску.

Однак я зрозумів із досвіду, як я врешті-решт зазнаю наслідків спроб боротися зі «зміною», як, наприклад, спілкування з багатобайтовими персонажами, що неминуче, якщо дивитися на глобальну аудиторію.

Тому я вирішив визнати та поважати зусилля, які докладають інженери Apple, і я зі свого боку зрозумів їхній розум, коли вони придумали цей "жахливий" підхід.

Замість того, щоб створювати розширення, які є просто вирішенням, щоб полегшити ваше життя (я не кажу, що вони помиляються чи дорого коштують), чому б не зрозуміти, як тепер створено, щоб струни працювали.

Наприклад, у мене був цей код, який працював на Swift 2.2:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

і після відмови від спроби змусити працювати той самий підхід, наприклад, використовуючи Підрядки, я нарешті зрозумів концепцію трактування струн як двонаправлену колекцію, для якої я закінчив цю версію того ж коду:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

Я сподіваюся, що це сприяє ...


1
Що ж, вирішення складної проблеми не означає, що рішення може бути елегантним. Знову ж таки, я також розумію проблему, але весь клас String і поводження з ним просто жахливі.
незрозумілий

5

Це ж розчарування, це не повинно бути таким важким ...

Я склав цей приклад отримання позицій для підрядків із більшого тексту:

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

повертає ("Чому", 0, 3) ("підрядки", 26, 36) ("Swift3", 40, 46)


3
Це якийсь код, але насправді не пояснює, як в swift3 працюють індексація рядків та підрядів.
Роберт

5

Я новачок у Swift 3, але дивлячись на String(індекс) синтаксис аналогії, я думаю, що індекс - це як "покажчик", обмежений рядком, і Int може допомогти як незалежний об'єкт. Використовуючи синтаксис base + offset, ми можемо отримати i-й символ із рядка з кодом нижче:

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

Для діапазону символів (індексів) з рядка, використовуючи синтаксис String (range), ми можемо отримати від i-го до f-го символів з кодом нижче:

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

Для підрядки (діапазону) з рядка за допомогою String.substring (діапазон) ми можемо отримати підрядку за допомогою наведеного нижче коду:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

Примітки:

  1. I-й та f-й починаються з 0.

  2. Для f-го, я використовую offsetBY: f + 1, оскільки діапазон використання підписки .. <(напіввідкритий оператор), не включає ф-ту позицію.

  3. Звичайно, слід включати перевірку помилок, наприклад недійсний індекс.


5

Швидкий 4+

extension String {
    func take(_ n: Int) -> String {
        guard n >= 0 else {
            fatalError("n should never negative")
        }
        let index = self.index(self.startIndex, offsetBy: min(n, self.count))
        return String(self[..<index])
    }
}

Повертає підпорядкованість перших n символів або весь рядок, якщо рядок коротший. (натхненно: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html )

Приклад:

let text = "Hello, World!"
let substring = text.take(5) //Hello

4

Я досить механічно думаю. Ось основи ...

Швидкий 4 Швидкий 5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

Результатом є підрядка, що означає, що вона є частиною вихідного рядка. Щоб отримати повний роздутий окремий рядок, просто використовуйте напр

    String(t3)
    String(t4)

Це те, що я використовую:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]

3

Швидкий 4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o

2

Я створив для цього просте розширення (Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}

2

Ось більш загальна реалізація:

Цей прийом досі використовується indexу відповідності зі стандартами Свіфта та передбачає повного персонажа.

extension String
{
    func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
    {
        return String(self[range])
    }

    func index(at: Int) -> Index
    {
        return self.index(self.startIndex, offsetBy: at)
    }
}

Для підрядного рядка з 3-го символу:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

Я використовував верблюда, subStringщоб вказати, що він повертає a, Stringа не a Substring.


2

Спираючись на вищезазначене, мені потрібно було розділити рядок на символі, що не надруковуються, скидаючи недрукувальний символ. Я розробив два методи:

var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]

яку я зібрав, використовуючи деякі відповіді вище.

Оскільки String - це колекція, я зробив наступне:

var fString = String()
for (n,c) in str.enumerated(){

*if c == "\u{1A}" {
    print(fString);
    let lString = str.dropFirst(n + 1)
    print(lString)
    break
   }
 fString += String(c)
}*

Що для мене було більш інтуїтивним. Який із них найкращий? Я не можу сказати, що вони працюють зі Swift 5


Дякую за вашу відповідь. Щось відрізняється від Strings у Swift 5? Я ще не встиг пограти з цим.
Сурагч

Вони кажуть так, але я не мав шансу заглянути в це.
Джеремі Ендрюс

1

Швидкий 4

"Підрядка" ( https://developer.apple.com/documentation/swift/substring ):

let greeting = "Hi there! It's nice to meet you! 👋"
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

Приклад рядка розширення:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

Приклад використання рядка розширення:

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"

-1

Swift 5
let desiredIndex: Int = 7 let substring = str[String.Index(encodedOffset: desiredIndex)...]
Ця змінна підрядка дасть результат.
Просто тут Int перетворюється на Index, і тоді ви можете розділити рядки. Якщо ви не отримаєте помилок.


2
Це неправильно. Символ може складатися з одного або декількох байтів. Він працює лише з текстом ascii.
Лев Дабус
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.