Голанг, чому ми не маємо набір структур даних [закрито]


129

Я намагаюся розв’язати вправу "Програмування в ході програмування" № 1.4, яка вимагає від мене набору. Я можу створити тип набору, але чому мова не входить разом із ним? Поїхавши з google, звідки також походить гуава, чому мовні дизайнери не вирішили додати підтримку фундаментальних структур даних? навіщо змушувати своїх користувачів створювати власні реалізації для чогось такого базового, як набір?


11
хммм. цікаво, чому негативні голоси? Я родом із світу java, де у нас був набір майже з самого початку, навіть без дженериків. Отже, я вважаю таку поведінку химерною
lijan

Відповіді:


83

Частково тому, що в Go немає загальних даних (тому вам знадобиться один тип набору для кожного типу або відкинутись на роздуми, що є досить неефективним).

Почасти, тому що якщо все, що вам потрібно, це "додавання / видалення окремих елементів до набору" та "відносно економічне місце", ви можете отримати справедливий біт цього просто за допомогою map[yourtype]bool(і встановіть значення trueдля будь-якого елемента в наборі ) або, для більшої ефективності простору, ви можете використовувати порожню структуру як значення та використовувати _, present = the_setoid[key]для перевірки наявності.


1
Крім того, здається, що в дусі Go просто виписати його у власному коді, або "вставивши" набір в інший код, або визначивши свій власний тип набору, коли це необхідно. У будь-якому разі, наприклад, використання std :: set <T> у C ++ завжди відбувається як частина реалізації функції або реалізація якоїсь іншої структури даних. Тому просто реалізуйте цю іншу структуру даних безпосередньо, використовуючи карти та фрагменти та будь-які інші будівельні блоки, які вам потрібні, але без будь-якого вбудованого набору. Кожне використання набору все одно використовуватиме його дещо інакше :)
Bjarke Ebert

1
Але зазвичай вам не потрібен набір <Foo> сам по собі, ви використовуєте набір <Foo> як частину реалізації чогось більшого. Я бачив тонни коду, де те, що вам потрібно зробити, щоб включити "багаторазовий" компонент, набагато гірше, ніж просто уникнути потреби. Отже, у нас є набір <Foo>, для цієї функції потрібен набір <Bar>, ой, чи є у нас ще коваріація або як зробити WrapperFactory, щоб ця річ виглядала як ця річ тощо. Можливо, та інша функція дійсно просто потрібен інтерфейс, який може перевірити на членство, тому не надсилайте йому набір <Foo>.
Bjarke Ebert

42
Тож якщо в ній немає дженериків, то як взагалі реалізована «загальна» карта?
Фермін Сільва

26
Зауважте, що якщо ви хочете зберегти байти, ви можете використовувати map[T]struct{}замість них map[T]bool.
емерсія

4
Подивіться на emersion.fr/blog/2017/sets-in-go
emersion

69

Однією з причин є те, що створити набір з карти легко:

s := map[int]bool{5: true, 2: true}
_, ok := s[6] // check for existence
s[8] = true // add element 
delete(s, 2) // remove element

Союз

s_union := map[int]bool{}
for k, _ := range s1{
    s_union[k] = true
}
for k, _ := range s2{
    s_union[k] = true
}

Перехрестя

s_intersection := map[int]bool{}
for k,_ := range s1 { 
  if s2[k] {
    s_intersection[k] = true
  }
}

Реально реалізувати всі інші задані операції не так вже й важко.


10
Перевірка наявності - це просто індексація карти. Оскільки, якщо його немає в ньому, нульове значення (яке є false) правильно скаже це. Не потрібно ідіоми з комою-ок для тестування.
icza

2
Реалізація для перехрестя виглядає так для різниці.
musiphil

1
@Kittsil дякую Оновлено.
Сальвадор Далі

34
Оптимальніше використовувати map[int]struct{}замість цього bool, оскільки порожня структура займає 0 байт у пам'яті. Нещодавно я написав суть для цього gist.github.com/bgadrian/cb8b9344d9c66571ef331a14eb7a2e80
BG Adrian

13
Це не так просто. Просто потреба писати цей код скрізь, де потрібно використовувати набір, здається мені смішною в ці дні. Підтримка колекцій повинна надаватися будь-якою мовою. Думаю, що краща відповідь полягає в тому, що Go ще не настільки зрілий. Я впевнений, що досить скоро будуть бібліотеки, які охоплюватимуть це.
Стеф

5

Як писав Ватін: Так як у дефіциті бракує дженерики, це повинно бути частиною мови, а не стандартною бібліотекою. Для цього вам доведеться забруднити мову ключовими словами, об'єднанням, перетином, різницею, підмножиною ...

Інша причина полягає в тому, що зовсім не зрозуміло, що таке "правильна" реалізація набору:

  1. Існує функціональний підхід:

    func IsInEvenNumbers(n int) bool {
        if n % 2 == 0 {
            return true
        }
       return false
    }
    

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

  1. Або ви маєте такий підхід, як показав Далі.

Карта не має такої проблеми, оскільки ви зберігаєте щось, пов’язане зі значенням.


2
Для обробки наборів вбудованих програм Pascal перевантажує купу двійкових (двох аргументованих) операторів: +для об'єднання, -різниці, *перетину, <=підмножини, >=суперсети, =рівності, <>нерівності та inчленства. Тож у Go, це було б лише одне нове ключове слово - in. З іншого боку, вбудовані набори Паскаля працюють лише над "порядками" - тобто будь-яким типом, який має в основі представлення цілого значення деякого розміру.
kostix

9
Той факт, що існує кілька способів реалізації набору, не зупиняв їх надання багатьом іншим мовам.
серпень

@kostix: Go навіть міг використовувати синтаксис s[key](як би sа map[T]bool) замість key in s.
musiphil

2
Будь-яка причина не просто повернутися n % 2 == 0?
Жоао Андраде

4

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


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