Julia: должен ли `in` использовать` isequal` для проверки на сдерживание?

Созданный на 16 дек. 2014  ·  138Комментарии  ·  Источник: JuliaLang/julia

Вот как работает Dicts, так что мне есть чем заняться. Поднялся в этом обсуждении:

https://groups.google.com/forum/#!topic/julia -users / XZDm57yHc5M

Мотивирующие примеры:

julia> NaN in [1,1.5,2,NaN]
false

julia> NaN in Set([1,1.5,2,NaN])
true

julia> NaN in keys(Dict(1=>"one", 1.5=>"one and a half", 2=>"two", NaN=>"not a number"))
true

Похоже, что in for array - это тот, который здесь не в порядке.

breaking help wanted

Самый полезный комментарий

Возвращаясь к этому, я склонен думать, что мы должны оставить все как есть. В настоящее время in имеет два типа поведения:

  1. Для контейнеров с явными представлениями о том, что они содержат (например, dicts и sets), контейнер определяет используемый тест на равенство.
  2. Все остальное использует == .

Это красиво и просто. Если мы не хотим изменять правило (2), чтобы использовать isequal вместо == , но мы хотим что-то изменить, тогда мы должны перейти к 3 видам поведения вместо 2. Я думаю, это было бы плохо; например, x in (f(y) for y in itr) должен дать тот же ответ, что и x in [f(y) for y in itr] .

Все 138 Комментарий

Разве для этого не потребуются две версии Dicts / Sets, одна с использованием isequal а другая с использованием egal для сдерживания? Я бы нашел такое поведение очень запутанным. Например, Set([1,1.0]) - это набор из одного элемента?

Редактировать. Я вижу, у нас уже есть такое поведение.

Это еще один случай, который можно обосновать тем, что стандарт IEEE определяет поведение == , но не in . Другими словами, стандарт не говорит, что функция in должна использовать IEEE == для сравнения элементов. Возможно, поведение IEEE NaN следует использовать как можно реже. Случай -0.0 сложнее; 0 in A может быть проблемой не найти -0.0.

@jakebolewski , Set([1,1.0]) _is_ уже одноэлементный набор:

julia> Set([1,1.0])
Set([1.0])

Это довольно простое следствие использования Dict в качестве основы для Set. Если мы хотим эгального поведения, нам понадобится тип ObjectIdSet который соответствует ObjectIdDict .

Случай -0.0 сложнее; 0 в A, не обнаружив -0,0, может быть проблемой.

Это отстой, но я думаю, что мы должны с этим жить - либо это, либо ввести еще один вид равенства, которое мы можем сделать с моим холодным мертвым телом.

Нам определенно не нужно другое равенство. Думаю, вместо этого мы предпочтем сохранить тебе жизнь.

Я рад.

Другой вариант - сделать -0.0 и 0.0 одинаковыми для isequal и hashing , но это тоже плохо.

О, мы не можем сделать isequal(-0.0, 0.0) поскольку это будет означать, что -0.0 не будет сортировать до 0.0 .

Действительно ли требуется, чтобы isless соответствовало isequal ? В любом случае, имея несколько концепций равенства, вам уже нужно посмотреть документацию, чтобы проверить, каково это поведение.

В качестве точки данных MATLAB isequal и ismember рассматривают -0.0 и 0.0 как равные. R не имеет isequal , но его функция identical допускает любое поведение через аргумент, а его функция %in% считает их равными. in Python делает то же самое. Поэтому не похоже, что различение отрицательного и положительного нуля является убедительным вариантом использования: это нормально, если оно поддерживается только в специальных типах dict / set. Напротив, их различение по умолчанию может привести к появлению очень тонких ошибок для 99% пользователей (источник: секретный опрос), которые даже не думали, что ноль не всегда равен нулю.

Действительно ли требуется, чтобы isequal не согласовывалось с isequal?

В этом нет строгой необходимости, но я думаю, что все остальное было бы катастрофой, имо.

Интересные данные:

In [19]: np.nan == np.nan
Out[19]: False

In [20]: np.nan in [np.nan]
Out[20]: True

In [21]: -0.0
Out[21]: -0.0

In [22]: 0.0 in [-0.0]
Out[22]: True

Я не уверен, как Python in обрабатывает здесь равенство.

В этом нет строгой необходимости, но я думаю, что все остальное было бы катастрофой, имо.

Я не совсем уверен. -0.0 и 0.0 сортируются рядом друг с другом, поэтому рассмотрение их как равных не противоречит порядку, просто теряется небольшая часть информации. (Я полагаю, что существует теория, которая выражает это в более общем плане.) В каких случаях это может стать проблемой?

Стоит подумать о том, чтобы isless соответствовало предикату IEEE totalOrder : основное отличие состоит в том, что NaN s с разными битовыми шаблонами различны, а NaN с отрицательным знаком предшествуют -Inf : в определенном смысле это порядок, соответствующий === .

Это можно сделать через

function totalorder(x::Float64,y::Float64)
    xi = reinterpret(Int64,x)
    yi = reinterpret(Int64,y)
    (xi < yi) $ ((xi < 0) & (yi < 0))
end

(который также кажется быстрее, чем наш текущий isless ).

Такой порядок сортировки всегда казался мне странным и бесполезным - зачем мне нужно, чтобы некоторые NaN располагались в начале, а некоторые в конце? Если бы мы начали это делать, мне казалось, что нам почти нужно было бы печатать NaN со знаком, что тоже было бы странно. Единственное преимущество порядка IEEE, которое я вижу, состоит в том, что вы можете эффективно реализовать его с помощью целочисленных сравнений.

вы можете эффективно реализовать это с помощью целочисленных сравнений.

Ага, так можно было думать, как компьютерный ученый 80-х :)

Хорошо, я бы согласился сделать isless более детализированным порядком, чем isequal , а именно:

  1. isequal(-0.0, 0.0) но isless(-0.0, 0.0) .
  2. isequal(nan1, nan2) для любых двух значений NaN, но имеет isless порядок NaN по битовому шаблону.
  3. все остальные классы эквивалентности одинаковы для isequal и isless .

Я не склонен к тому, чтобы одни NaN были отрицательными, а другие - положительными. Это просто глупо и бесполезно.

Да, это может быть нормально. Надо подумать об этом.

Кстати, если есть какие-то эксперты по питону, я действительно хотел бы знать, что происходит в примере, который я опубликовал выше. Похоже, in не вызывает __eq__ для np.nan . Однако он также не использует is , как показывает этот пример:

In [1]: 1 is 1.0
Out[1]: False

In [2]: 1 == 1.0
Out[2]: True

In [3]: 1 in [1.0]
Out[3]: True

РЕДАКТИРОВАТЬ: Хорошо, я предполагаю, что in сначала использует is и возвращает true, если это так, перед вызовом __eq__ .

О, Python, ты такой сумасшедший.

in Python реализован PySequence_Contains , который по умолчанию использует PyObject_RichCompareBool (obj, item, Py_EQ) , который вызывает слот tp_richcompare объекта типа, который для типов с плавающей запятой превращается в float_richcompare (чьи комментарии начинаются с Comparison is pretty much a nightmare ), который использует C == для двух переменных с плавающей запятой. _ Однако_, PyObject_RichCompareBool _first_ проверяет идентичность объекта, и в этом случае сравнение просто пропускается. (См. Также документацию для PyObject_RichCompareBool , где это действительно упоминается.)

Итак, предположение Джеффа верное.

(Да, я потратил слишком много времени на изучение кодовой базы CPython для тех, кто никогда не программирует на Python.)

Ясно, что я слишком рано использовал свое «о, Python, ты такой сумасшедший».

Это отрывок из документации Python для операторов сравнения:

>>> help('in')

...

Comparison of objects of the same type depends on the type:

...

* The values "float('NaN')" and "Decimal('NaN')" are special. The
are identical to themselves, "x is x" but are not equal to
themselves, "x != x".  Additionally, comparing any value to a
not-a-number value will return "False".  For example, both "3 <
float('NaN')" and "float('NaN') < 3" will return "False".

...

The operators "in" and "not in" test for membership.  "x in s"
evaluates to true if *x* is a member of *s*, and false otherwise.  "x
not in s" returns the negation of "x in s".  All built-in sequences
and set types support this as well as dictionary, for which "in" tests
whether the dictionary has a given key. For container types such as
list, tuple, set, frozenset, dict, or collections.deque, the
expression "x in y" is equivalent to "any(x is e or x == e for e in
y)".

For the string and bytes types, "x in y" is true if and only if *x* is
a substring of *y*.  An equivalent test is "y.find(x) != -1".  Empty
strings are always considered to be a substring of any other string,
so """ in "abc"" will return "True".

For user-defined classes which define the "__contains__()" method, "x
in y" is true if and only if "y.__contains__(x)" is true.

For user-defined classes which do not define "__contains__()" but do
define "__iter__()", "x in y" is true if some value "z" with "x == z"
is produced while iterating over "y".  If an exception is raised
during the iteration, it is as if "in" raised that exception.

Lastly, the old-style iteration protocol is tried: if a class defines
"__getitem__()", "x in y" is true if and only if there is a non-
negative integer index *i* such that "x == y[i]", and all lower
integer indices do not raise "IndexError" exception.  (If any other
exception is raised, it is as if "in" raised that exception).

The operator "not in" is defined to have the inverse true value of
"in".

The operators "is" and "is not" test for object identity: "x is y" is
true if and only if *x* and *y* are the same object.  "x is not y"
yields the inverse truth value.

Таким образом, даже если 1 и 1.0 равны, они не являются одним и тем же объектом в памяти:

In [1]: 1 is 1.0
Out[1]: False

In [2]: id(1) == id(1.0)
Out[2]: False

@ Ismael-VC, поэтому их документация неверна, потому что эффективно использует x is z or x == z , а не x == z .

Фактически, функция сортировки по основанию в SortingAlgorithms.jl действительно использует целые числа
сравнения (и некоторые махинации) для сортировки чисел с плавающей запятой.

В среду, 17 декабря 2014 г., Джефф Безансон [email protected]
написал:

вы можете эффективно реализовать это с помощью целочисленных сравнений.

Ага, так можно было думать, как компьютерный ученый 80-х :)

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/9381#issuecomment -67360582.

Я считаю, что -0.0 важнее NaN: код, который пытается сравнивать с NaN, всегда будет иметь проблемы. Большинство людей хочет найти 0 в массиве, содержащем -0.0 . Поэтому я думаю, что мы должны использовать либо «==, либо isequal», либо просто == . Определенно проще использовать конкретную функцию, поэтому я говорю, что мы сохраняем текущее поведение.

Как бы вы реализовали это эффективно для Sets? Я считаю, что массивы контейнеров должны вести себя как упорядоченные мультимножества, что для меня подразумевает использование isequal.

22 июня 2015 г. в 18:27 Джефф Безансон [email protected] написал:

Я считаю, что -0.0 важнее NaN: код, который пытается сравнивать с NaN, всегда будет иметь проблемы. Большинство людей хотят найти 0 в массиве, содержащем -0.0. Поэтому я думаю, что мы должны использовать либо «==, либо isequal», либо просто ==. Определенно проще использовать конкретную функцию, поэтому я говорю, что мы сохраняем текущее поведение.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

Я думаю, что для разных контейнеров обычно нормально использовать разные тесты. Отсутствие 0 в массиве с -0.0 может вызвать серьезные проблемы.

Мы также могли бы сделать isequal(-0.0, 0.0) .

Да, план @StefanKarpinski «S в https://github.com/JuliaLang/julia/issues/9381#issuecomment -67361516 звучит самое лучшее решение.

Спасибо, что нашли этот комментарий, @nalimilan. Чтобы процитировать это и добавить к нему:

  1. isequal(-0.0, 0.0) но isless(-0.0, 0.0) .
  2. isequal(nan1, nan2) для любых двух значений NaN, но имеет isless порядок NaN по битовому шаблону.
  3. все остальные классы эквивалентности одинаковы для isequal и isless .
  4. используйте isequal для проверки содержания коллекций (массивов и наборов).

Что мне не нравится в этом, так это то, что isequal и isless должны быть _sane_ сравнениями. На данный момент у меня есть половина желания сделать NaN == NaN и -0.0 == 0.0 и удалить isequal . Другой вариант - запретить NaN в качестве хеш-ключа.

Предлагаемое изменение делает их безумными сравнениями?

@StefanKarpinski Пун задумал? Думаю, он имел в виду «те же сравнения».

Однако я не уверен, что это действительно требование. И разве NaN == NAN противоречит общим ожиданиям?

не будет ли NaN == NaN противоречить общим ожиданиям?

Это забавный способ выразиться, я понимаю, что вы имеете в виду, но все же «общие ожидания» могут включать рефлексивность равенства :-). Я не занимаюсь математикой fp, поэтому не знаю, сколько людей привязано к этой части семантики IEEE.

Предлагаемое изменение делает их безумными сравнениями?

Я бы сказал так, да. Я определенно имел в виду «нормальный», а не «такой же». Идея isequal и isless заключалась в том, чтобы иметь корректные равные и меньшие предикаты, подчиняющиеся как можно большему количеству обычных математических свойств.

Я думаю, что меня бы устроили isequal(-0.0,0.0) и !isless(-0.0,0.0) . Тогда isequal отличается только для NaN.

В # 8463 также обсуждалось создание hash(0.0) == hash(-0.0) : это также сделало бы хэш-функцию немного проще.

Я думаю, что меня бы устроили isequal(-0.0,0.0) и !isless(-0.0,0.0) .

Мне что-то не хватает, но не вызовет ли это проблем с сортировкой?

не вызовет ли это проблем с сортировкой?

Я полагаю, вы получите нули и отрицательные нули в неопределенном порядке. Мы могли бы также предоставить предикат IEEE totalOrder. Но посмотрите, IEEE говорит, что -0.0 и 0.0 равны, так чего же они ожидают?

Оглядываясь назад на исходную мотивацию проблемы, я думаю, что нужно различать случай преднамеренного поиска NaN с использованием NaN in Y и написания x in y целом и рассмотрения поведения, когда x быть NaN. В первом случае вам действительно нужно использовать isnan . Во втором случае не имеет значения, что произойдет - любая программа, игнорирующая NaN, получит странные ответы, если где-то возникнут NaN.

Это поможет запретить использование NaN в качестве ключей dict. Было бы разумно провести такой тест, как

if !(key == key)
    error("key cannot be reliably identified and so cannot be inserted")
end

Тогда мы действительно могли бы рассмотреть возможность удаления isequal .

Независимо от того, что IEEE говорит о -0.0 и 0.0, являющихся ==, наличие отношения «многие-к-одному» при вставке чего-либо в набор или dict было бы не очень хорошо, ИМО. Это потеря информации.
NaN похоже на null, вы разрешаете нулевой ключ в наборе или dict?
Кроме того, все NaN не похожи.
Мне нравится, что приведенный выше тест дает ошибку - я думаю, что это согласуется с тем, как следует обрабатывать null, т.е.
нулевое значение не равно == чему-либо другому, даже другому нулю, поэтому приведенный выше тест будет обрабатывать это последовательно.

Из всех возможных проблем здесь мне особенно легко представить проблемы, вызванные различением -0.0 и 0.0. Скажем, в массиве -0.0, тогда кто-то должен спросить: "А здесь есть нули?" Определенно удивительно, если 0 in a не находит ноль.

С -0.0 вы должны быть очень осторожны при распространении знаков (например, это делает код в complex.jl намного более ... сложным). Если бы его не было, вам нужно было бы быть очень осторожным при вычислении 1 / x, где x может быть -Inf. Для меня это стирка.

Хорошо, тогда почему бы просто не сказать, что -0.0 преобразуется в 0.0, когда он используется в качестве ключа? Вместо того, чтобы пытаться сделать что-то сложное при поиске (сделать хэши равными, даже если битовые шаблоны разные и т. Д.)

Согласно https://github.com/JuliaLang/julia/issues/9381#issuecomment -114568028, сделать хэш -0.0 и 0.0 одинаково, учитывая то, что наша хэш-функция должна делать в любом случае.

Хотя нормализация от -0,0 до 0,0 при вставке ключа может быть хорошей идеей.

Я думаю, что это легче объяснить людям (это тот подход, который мы использовали для -0.0, и у нас не было никаких жалоб).

@JeffBezanson , если ты собираешься назвать что-то безумным, будет полезно, если ты объяснишь, почему ты считаешь это сумасшедшим. Вы возражаете против упорядочивания NaN? Я должен предположить это, поскольку вы сказали, что создание isequal(-0.0, 0.0) кажется вам приемлемым. Часть NaN не так уж необходима, но она упростит сортировку чисел с плавающей запятой, поскольку вам не нужно будет использовать стабильный алгоритм разделения при перемещении NaN в конец.

Наличие isequal(-0.0, 0.0) и isless(-0.0, 0.0) не должно быть проблемой для сортировки, поскольку мы никогда не используем isequal при сортировке, мы используем только isless . В общем случае isequal более детализирован, чем isequal , но вы хотите, чтобы isequal и hash согласились в том смысле, что isequal(x,y) подразумевает hash(x) == hash(y) и имеет место обратная импликация, за исключением коллизий, которые должно быть сложно построить.

Мне очень не нравится идея преобразования -0,0 в 0,0 при использовании его в качестве значения ключа. Тогда это будет _only_ значение, которое мы преобразуем в другое значение того же типа при использовании его в качестве ключа. Мне еще больше не нравится идея запретить NaN в качестве ключевого значения. Это попахивает бессмысленной исключительностью в стиле Matlab.

Я сказал почему:

Идея равенства и отсутствия заключалась в том, чтобы иметь хорошо себя ведающие равные и меньшие предикаты, подчиняющиеся как можно большему количеству обычных математических свойств.

Я думаю, будет справедливо сказать, что в идеале у нас были бы только == и <. Очень неприятно добавлять дополнительный набор функций сравнения, и они все еще не подчиняются ожидаемым свойствам.

Идея состоит не в том, чтобы специально запретить NaN, а в том, чтобы проверить правильность x == x . Конечно, NaN - единственное известное нам значение, которое нарушает это, но это кажется более оправданным, чем проверка isnan .

Если у вас есть нулевое значение в стиле SQL, оно требует такой же обработки, как x! = X.

Я не согласен с тем, что суть isequal и isless заключается в том, чтобы «подчиняться как можно большему количеству обычных математических свойств». Точка isequal должна использоваться для хеширования, а точка isless должна использоваться для заказа вещей - если они не справятся с этим, вам понадобится еще одна пара отношений для этих целей. Само понятие «подчиняться ожидаемым свойствам» плохо определено - разные люди ожидают разного. Я думаю, что мы должны убедиться, что isequal и isless подходят для хеширования и упорядочивания и живут с == и < качестве повседневных математические операторы, несмотря на их странности IEEE.

В этой демократии, которой является Джулия, я буду голосовать против отказа от использования NaN в качестве ключа. Боюсь, что это приведет к появлению в пользовательском коде всевозможных специальных регистров; например, я делаю группировку по столбцу фрейма данных, некоторые из значений которого равны NaN. Я ожидаю возврата словаря, ключи которого являются уникальным значением в этом столбце, но это больше невозможно.

Я согласен - запрет на использование NaN в качестве хеш-ключа будет катастрофой.

@malmaud Разве вам не нужно следить за тем, чтобы вы сохранили битовое значение NaN?
(поскольку существуют разные NaN, и если вы действительно хотите их сохранить, вы должны сохранять их как есть)
Возможно, проблема заключается в попытке использовать isequal / isless , которые имеют другое применение, в котором вы не хотите менять значение. Вместо этого для sets / dicts / etc. у вас есть sortsbefore и sortsequal (я не знаю, используются ли уже эти имена, если да, выберите другие), где запасным вариантом будет isless и isequal .
Для значений с плавающей запятой, допускающих значение NULL, вы можете иметь нулевые сортировки перед NaN, затем для любых чисел с плавающей запятой у вас будет NaN sortsequal с тем же битовым шаблоном и сортировка перед на основе их битовых значений, затем -Inf , все отрицательные числа с плавающей запятой, -0.0, 0.0, все положительные числа с плавающей запятой, +Inf .

Хорошо, я могу жить с NaN в качестве ключей dict.

Я думаю, что соответствие isequal с isless напрямую связано с полезностью для хеширования и упорядочивания. Например, у нас есть следующее определение, чтобы упорядочить пары лексикографически:

isless(p::Pair, q::Pair) = ifelse(!isequal(p.first,q.first), isless(p.first,q.first),
                                                             isless(p.second,q.second))

В соответствии с предлагаемым изменением это определение неверно. Все, что он делает, это реализует очевидное определение лексикографического порядка: если первые элементы равны, посмотрите на вторые элементы. В настоящее время isequal и isless обеспечивают долгожданную передышку от угловых случаев с плавающей запятой.

Хорошо, теперь мы к чему-то приближаемся - это хорошая практическая причина, по которой это изменение может быть проблематичным.

Вы могли бы написать это как

isless(a::Pair, b::Pair) = isless(a[1],b[1]) | !isless(b[1],a[1]) & isless(a[2],b[2])

Но более серьезный вопрос заключается в том, стоит ли вообще усложнять работу с isless и isequal .

Правильно.

Я согласен с тем, что сортировка NaN до конца массива полезна, но насколько важно отсортировать -0.0 перед 0.0? В конце концов, они равны.

Нам пришлось бы выполнить стабильную сортировку для той части массива, которая тогда равна ± 0,0, что не менее раздражает. Это == но не === или isequal и есть видимая разница между -0.0 и 0.0.

Они не идентичны, если вы хотите сказать, что у вас есть набор со всеми значениями, которые вы видите, вы бы хотели знать, что в наборе было -0.0, а не 0.0. В противном случае вы можете также преобразовать -0,0 в 0,0 при вставке.
То же самое и для NaN, вы просто используете печатное представление NaN, но как насчет всех других битовых шаблонов, которые являются NaN? Иногда они отображаются, например -NaN(s1234)

Конечно, они не идентичны, но === для этого. Что означает -0.0, в целом не согласовано; на самом деле это просто альтернативное представление нуля.

Очевидно, что можно сконструировать случаи, в которых вы хотите их различать и где нет. Однако я бы сильно взвесил вещи, чтобы не различать их. Я думаю, что гораздо хуже получить два нуля, когда вы думаете, что у вас есть уникальные значения.

Кажется, есть небольшая проблема, если у вас два разных NaN, скажем:

julia> a = reinterpret(Float64,0xfff0000000000001)
NaN
julia> b = reinterpret(Float64,0xfff0000000000002)
NaN
julia> c = reinterpret(Float64,0xfff0000000000001)
NaN
julia> a == b
false
julia> a === b
false
julia> a === c
true
julia> hash(a)
0x15d7d083d04ecb90
julia> hash(b)
0x15d7d083d04ecb90

То, как работает хеш, не согласуется ни с ===, ни с ==.

Также существует проблема с хешем 0.0 и -0.0:

julia> hash(0.0)
0x77cfa1eef01bca90
julia> hash(-0.0)
0x3be7d0f7780de548
julia> -0.0 === 0.0
false

Как указано в документации, hash соответствует isequal . Как описано в исходной проблеме, dicts используют isequal .

Функция hash должна согласовываться только с функцией isequal , которой она и является.

???
Обычно a == b подразумевает hash (a) == hash (b), верно?
Это совершенно не относится к NaN.
Кроме того, как я показал, hash (0.0)! = Hash (-0.0), хотя 0.0 == -0.0.

Да, hash согласуется с _некоторым_ понятием равенства, но какое? Ответ: isequal .

Тогда _why_ hash (0.0) возвращает _different_ результат от hash (-0.0)?

Нет, поскольку == реализует равенство IEEE, что очень не то, что вам нужно для хеширования. Рассмотрим , что тот факт , что NaN != NaN будет означать для этого d[NaN] = 1 повторно.

Таким образом, хеш НЕ согласуется с isequal, а не с 0,0 и -0,0.

julia> isequal(-0.0, 0.0)
false

Неважно ... итак, есть == , isequal и === ? Извините, я думал, что == - это то же самое, что isequal ,
как === совпадает с
Некоторые очень странные вещи происходят сейчас, например, с использованием NaN в качестве ключей в Dicts.
Мне это кажется ошибкой:

julia> g = Dict(1=>"one", 1.5=>"one and a half", 2=>"two", NaN=>"not a number")
Dict{Any,ASCIIString} with 4 entries:
  NaN => "not a number"
  2   => "two"
  1.5 => "one and a half"
  1   => "one"

julia> u = keys(g) ; a,p = next(u,1) ; reinterpret(UInt64,a[1])
0x7ff8000000000000

julia> setindex!(g,"silly",-0.0/0.0)
Dict{Any,ASCIIString} with 5 entries:
  NaN                => "silly"
  2                  => "two"
  1.5                => "one and a half"
  1                  => "one"

julia> u = keys(g) ; a,p = next(u,1) ; reinterpret(UInt64,a[1])
0xfff8000000000000

Кроме того, если вы сохраняете 0,0 в Dict, а затем сохраняете 0, вы перезаписываете запись 0,0.
Если вы храните 0x00000, он также заменяет его. Очень странно, что типы клавиш меняются,
если они рассматриваются как одно и то же значение.

@ScottPJones , см. Https://github.com/JuliaLang/julia/pull/6624 для контекста.

Критерий дизайна (ретроспективно). Предположим, что Dict{K,V} пытается преобразовать ключи и значения в типы K и V после вставки в dict. Вы также хотели бы, чтобы код, использующий объекты Dict{K1,V1} и Dict{K2,V2} вел себя одинаково (по значению) при вставке и поиске одних и тех же значений, предполагая, что ни один из них не выдает ошибку преобразования, потому что какой-то ключ of value нельзя преобразовать в K1 или K2 и т. д. Я почти уверен, что это означает, что вам нужно хешировать вещи по значению, как мы сейчас.

Я начинаю думать, что на самом деле не имеет значения, isless(-0.0, 0.0) . Давайте исправим несоответствие между массивами и dicts, указанными в описании проблемы, так или иначе, _that_ гораздо важнее. Тогда деталь -0.0 может быть пересмотрена, если мы найдем больше точек данных (использует случаи?) - Я был бы удивлен, если бы это нарушило существующий код.

Я позволил своему подсознанию обработать это за ночь, и я понял, что меня в этом беспокоило.
Это утечка реализации в абстракцию, ИМО.
Вы неявно предполагаете, что хеширование имеет какое-либо отношение к Set или Dict.

Я действительно не понимаю возражений. Вы можете использовать isequal для вещей, которые не реализованы с помощью hash . Главное свойство, которое ему нужно, - быть отношением эквивалентности, в отличие от == , и не быть таким детальным, как === .

Реализуя Set или Dict с использованием ассоциативного массива (реализованного либо с деревом B + для постоянного хранилища, либо с хешем (с упорядочиванием) или структурой ptrie для в памяти, вы бы преобразуйте все ключи в вектор байтов, который дает вам желаемый порядок, и если вы хотите, чтобы все NaN s (или -0.0, 0.0, 0, 0x0) возвращали один и тот же ключ, что означает, что вы не могу вернуть тип ключа, который вы здесь делаете
(если вы не потратите много дополнительного места для хранения исходного ключа).

Существует некоторое понятие равенства, при котором 0.0 и 0x0 равны, и некоторое понятие, при котором это не так. Оба вида отношений полезны. Насколько я могу судить, это фундаментально. Всегда можно будет иметь несколько представлений одного и того же (в некотором значимом смысле) значения на компьютере.

Если вы выступаете за === как за «единственный истинный оператор равенства», то я не полностью с этим не согласен. Это единственное, что надежно различает значения тогда и только тогда, когда их _ можно_ отличить.

Однако, если мы предположим, что кто-то хочет == и словарь, который его использует, у них нет причин жаловаться, когда ключ 0.0 заменяется на 0x0000 , поскольку эти значения равны в зависимости от отношения, которое они используют.

Да, оба полезны, и, надеюсь, можно было бы сделать Dict, в котором вы могли бы определить тесты на равенство и отсутствие.
По моему опыту, я никогда не видел словаря, в котором отношение было ==, и он всегда обновлял ключ при изменении поля. Ключ всегда хранился в канонической форме (т.е. Int (0) вместо Float64 (0), UInt8 (0), UIntxx (0), «0» и т. Д.). Это та часть, которая поразила меня тем, как Dict реализован в Julia. Это также усложняет сравнение двух Dict (== может быть дорогостоящим по сравнению с использованием === для преобразованных ключей).

Я мог бы представить себе параметризацию Dict типом равенства, которое вы хотите (по умолчанию наиболее разумным).

Да, оба полезны, и, надеюсь, можно было бы сделать Dict, в котором вы могли бы определить тесты на равенство и отсутствие.

Я утверждал ранее и до сих пор считаю, что это лучше достигается явной нормализацией значений перед их использованием в качестве ключей. Мы ввели dicts, поэтому вы уже можете принудительно преобразовать значения в некоторый ожидаемый тип, например Int . Если вам нужно хеширование идентификаторов объектов, то для этого и нужен ObjectIdDict .

Я собираюсь бросить это предложение:

  1. Избавьтесь от isequal и всегда используйте x == y || x != x && y != y или его эквивалент как каноническое «интуитивное» понятие эквивалентности. Функция isequal может оставаться как перехватчик, позволяющий переопределить реализацию этого отношения для различных типов, но она всегда должна _implement_ это отношение.

    1. Это означает, что существует один класс эквивалентности для вещей, которые не равны себе.

    2. Это также означает, что -0.0 и 0.0 эквивалентны и должны иметь одинаковый хэш.

  2. Оставьте isless чтобы разрешить настраиваемый порядок, по умолчанию isless(x,y) = x < y но позволяя типам переопределять этот порядок с более точным соотношением, но не с более грубым. То есть:

    1. Можно иметь isequal(x, y) и isless(x, y) , например, для -0.0, 0.0 или для разных NaN.

    2. Если isless(x, y) должно быть !isequal(x, y) .

В частности, это будет означать, что значения с плавающей запятой будут отсортированы в следующем порядке:

  1. -Inf
  2. отрицательные конечные значения
  3. -0,0
  4. 0,0
  5. положительные конечные значения
  6. + Inf
  7. NaN, отсортированные по битам

Классы 3 и 4 будут одним классом эквивалентности под isequal а класс 7 будет одним классом эквивалентности, даже если его содержимое будет упорядочено. При использовании isless вы должны быть осторожны, чтобы не предположить, что !isequal(x, y) ⟺ isless(x, y) || isless(y, x) поскольку будет иметь место только прямое значение, не обязательно обратное. В общем, это просто повлечет за собой определение общих методов isless в терминах более конкретных методов isless , а не использование isequal как указано выше. В большинстве случаев вы не будете беспокоиться об определении isless или isequal поскольку значения по умолчанию будут правильными.

Я бы поспорил с точки зрения пользователя.

Операторы == , < в некоторой степени драгоценны, и люди должны иметь возможность использовать их (для своих собственных типов) с любым обобщением равенства или упорядочением, которое они считают подходящим. Такое обобщение может оказаться бесполезным для поиска или сортировки, поэтому Джулия использует для них isequal и isless . То есть, в то время как isequal определяет классы эквивалентности, а isless определяет их общий порядок, операторы == и < не обязательно должны. (Это отличается от предложения @StefanKarpinski , приведенного выше.) Если isequal и isless ведут себя странным образом, что в основном неслыханно, за исключением семантики с плавающей запятой IEEE, слишком много людей будут удивлен.

По умолчанию isequal и isless определяются в терминах == и < . Это дает людям свободу и возлагает бремя использования немного подробных isequal и isless на тех, кто реализует сортировку и поиск общих типов, а это, возможно, меньшее количество людей.

Кроме того, поскольку числа с плавающей запятой уже имеют странное понятие равенства (без классов эквивалентности) и порядка (не упорядоченного в математическом смысле), это в основном вынуждает Джулию не использовать == и < .

Вопрос о том, как реализовать isequal и isless для чисел с плавающей запятой, затем обсуждается. Лично я бы сделал это, переинтерпретируя биты как целые числа, но, очевидно, рассматривая +0.0 и -0.0 как равные или все nans как равные, оба варианта имеют смысл. (Поскольку операции с плавающей запятой неточны, в любом случае нужно быть осторожным при сравнении, поэтому использование побитовой семантики имеет смысл, поскольку позволяет обойти проблему.) Если это не работает в конкретном случае, то всегда можно обойти это, введя (дешевый) класс-оболочка, или с помощью пользовательских операторов сравнения, или аналогичного механизма. Пример в документации для этого случая был бы хорош.

Я начинаю думать, что текущее положение дел, при котором x in keys(d) и x in collect(keys(d)) не всегда совпадают, не так уж и плохо. Например, изменение d с Dict на ObjectIdDict также меняет ответ. У разных контейнеров могут быть разные концепции сдерживания.

Я думаю, что единственный _реальный_ способ синхронизировать x in keys(d) и x in collect(keys(d)) - это полностью удалить различные используемые ими понятия равенства. Однако консенсус в том, что приравнивание NaN и NaN и различение -0,0 и 0,0 слишком важно, чтобы это было возможно. Поэтому мы должны сохранять вещи такими, какие они есть.

Если у нас есть isless(-0.0, 0.0) а также isequal(-0.0, 0.0) , тогда у нас фактически есть безымянное четвертое понятие равенства:

  1. ===
  2. ==
  3. == за исключением того, что NaN равны (isequal)
  4. isequal, кроме -0.0 и 0.0 различны (isless)

Одно из мест, где это вызывает головную боль, - это если вы хотите реализовать таблицу поиска, которая работает путем упорядочивания, а не хеширования. Вы не можете просто использовать isequal и isless так, как Dicts использует isequal и hash ; нужно быть очень осторожным. Таким образом, хотя это изменение улучшает некоторые случаи, я не думаю, что это серьезное улучшение в целом. Мы должны оставить все как есть или полностью придерживаться двух стандартных понятий равенства.

@StefanKarpinski , ты находишь это убедительным?

Удар.

Сравнение Dict с ObjectIdDict мне кажется полным несоответствием - конечно, эти типы ведут себя по-разному, они должны иметь совершенно разную семантику. С другой стороны, весь смысл итератора ключей состоит в том, чтобы быть эффективным прокси для сбора ключей в Dict . Если keys(d) и collect(keys(d)) будут вести себя по-разному, то я думаю, что эксперимент по возврату keys итератора провалился.

Что плохого в том, что in просто использует isequal вместо == ? Мне кажется, что это буквально операция, на которую рассчитан isequal . Единственное возражение, высказанное до сих пор, состоит в том, что согласно этому понятию включения -0.0 in [0, 0.5, 1] было бы ложным, но я думаю, что это аргумент, что мы должны иметь isequal(-0.0, 0.0) а не то, что мы не должны использовать isequal для тестирования сдерживания.

тогда я думаю, что эксперимент по возвращению ключей итератором провалился

Я делаю противоположный вывод. Скажем, единственный способ получить коллекцию ключей в dict дал их в виде массива. Тогда проверка k in keys(d::ObjectIdDict) будет искать k в массиве, теряя при этом тот факт, что в этом случае мы должны использовать === . Вместо этого возврат KeyIterator сохраняет правильную семантику сравнения. Учитывая, что разные типы dicts могут использовать разные сравнения, как могут keys(d) и collect(keys(d)) всегда вести себя одинаково?

Я был бы в порядке с isequal(-0.0, 0.0) , при условии, что isless согласуется с этим. Или in можно использовать x==y || isequal(x,y) .

Хорошо, это хороший момент по поводу того, что keys позволяет передавать семантику === в ObjectIdDict . Похоже, что единственный камень преткновения - это раздражающая штука -0.0 . Порядок и равенство IEEE 754 действительно ужасно плохи - безусловно, худшая часть стандарта. Если бы мы сделали isequal(-0.0, 0.0) && !isless(-0.0, 0.0) тогда у нас были бы либо значения -0.0 и 0.0 , отсортированные в том порядке, в котором они изначально появились во входном массиве, либо мы могли бы выполнить сортировку для набранного Массивы Float64 сортируют вещи иначе, чем нетипизированные массивы (которые фактически используют isless для сравнения). На самом деле я даже не уверен, как эффективно сортировать значения ±0.0 - мы делаем это для значений NaN , что очень неудобно, но они попадают в конец массива, то есть относительно прост в обращении. Нули попадают в середину, что сложнее.

Может быть, у Python есть алгоритм для этого? По умолчанию он использует стабильные сортировки. Однако быстрая сортировка Numpy задокументирована как нестабильная, но используется по умолчанию для массивов numpy.

Мне кажется странным заботиться о том, где в отсортированном массиве заканчиваются отрицательные нули. Я не понимаю, почему это имеет значение.

Кодирование длин серий данных с плавающей запятой? Понятия не имею. Но дело в том, что когда они просто заканчиваются в каком-то случайном порядке, кажется дрянным и сломанным. Готов поспорить, NumPy просто сортирует по битовому шаблону. Вероятно, им не нужно соглашаться со стабильной сортировкой для общего предиката сравнения. Что технически мы тоже могли бы сделать. Тогда сортировка типизированного массива Float64 просто закончится в другом порядке, чем сортировка тех же значений в абстрактно типизированном массиве.

У меня более приземленный вопрос - это то, что мы будем делать в версии 0.4?

Если мы скоро примем решение, тогда да, но сейчас неясно, что нам делать, поэтому я так не думаю.

Я думаю, это решено. Пока существует более одного вида равенства, разные контейнеры могут использовать разные типы, поэтому (a in b) == (a in collect(b)) не является идентификатором.

То, что меня до сих пор _ действительно_ беспокоит, - это оригинальный пример того, что NaN in [1,1.5,2,NaN] является ложным. Я понимаю, что это следует из неудачного определения == для NaN из-за IEEE 754, но это просто неприятная вещь для любой коллекции. Предложение об использовании дизъюнкции двух предикатов равенства также не является удивительным, поскольку оно неявно вводит еще один предикат равенства, но, возможно, проверяет наличие x === y || x == y поскольку, по крайней мере, тогда вы _всегда_ возвращаете истину при проверке _exact_ того же объект (или программно неотличимый от него) находится в коллекции.

Честная оценка; проверка x===y || x==y кажется вполне разумной. === подразумевает isequal , поэтому коллекции, в которых используется isequal не меняются. Думаю, здесь мы могли бы пойти вместе с питоном. Единственное беспокойство может быть связано с производительностью.

Я подозреваю, что для любых объектов, где == достаточно дешево, чтобы иметь значение, это будет оптимизировано. Если тест равен (x === y) | (x == y) кажется, что он оптимизирован как минимум для Int и Float64 .

По крайней мере, я бы не чувствовал себя полным неудачником, объясняя, что NaN in [1,1.5,2,-NaN] ложно, поскольку NaN и -NaN - это не один и тот же объект и они не равны друг другу.

Ну вот где я начинаю сомневаться. Это может показаться разумным, но это все же четвертый стандартный вид равенства (стандарт в том смысле, что Base использует его каким-то важным образом). Результаты NaN in [+-NaN] удивляют как тех, кто ожидает == и тех, кто ожидает isequal . Может быть, isequal должно приравнивать только бит-идентичные NaN?

В этом случае, я думаю, нам следует начать печатать знак и полезную нагрузку для NaN, что кажется раздражающим.

Если полезная нагрузка или важна ли она q или s , и я предполагаю, что она предназначена для определенных приложений (примечание: некоторые библиотеки необязательно распечатывают полезную нагрузку), тогда я бы хотел только бит-идентичные NaN должны рассматриваться как равные, по крайней мере, по некоторым критериям равенства (и при использовании его в качестве ключа я бы сделал одно место)

Использовать

isequal(x, y, flag0, flagNaN)
in(x, S, flag0, flagNaN)

для внутренней разработки, чтобы предотвратить разрыв между версиями?

FWIW, проблема -0.0 в Dict была поднята сегодня для пользователей julia: https://groups.google.com/d/msg/julia-users/mVWF_v8UNZg/xJXYgP5WBQAJ

Что касается NaN , возможно, мы могли бы выбрать полезную нагрузку по умолчанию, которая будет напечатана в этой короткой форме, и печатать NaN(payload) тогда, когда она отличается от значения по умолчанию?

Что касается NaN , возможно, мы могли бы выбрать полезную нагрузку по умолчанию, которая будет напечатана в этой короткой форме, и печатать NaN(payload) тогда, когда она отличается от значения по умолчанию?

Я думаю, что у нас обычно есть полезная нагрузка по умолчанию, но некоторые операции могут изменить знак:

julia> reinterpret(UInt64, NaN)
0x7ff8000000000000

julia> reinterpret(UInt64, -NaN)
0xfff8000000000000

Похоже, что нам следует воздержаться от изменения сравнения / равенства / сдерживания прямо сейчас, но есть много связанных идей, которые возникли в результате этого. Мы повторим и попробуем некоторые изменения для версии 0.6.

edit: нам, вероятно, следует разбить это на более мелкие и более сфокусированные части, поскольку в этом обсуждении много чего происходит

FWIW, я думаю, нам следует:

  • сделать in isequal везде использовать

    • это позволит == выдавать ошибки (# 15983).

  • заработать isequal(0.0, -0.0)

    • кажется странным, что это ложь, но isequal(0.0, 0) верно

  • заработать isless(-0.0, 0.0) == isless(0.0, -0.0)

    • Я считаю, что мы должны уважать порядок нулей со знаком, только когда он «бесплатный» (например, min / max , cf # 14621). На самом деле я думаю, что это могло бы быть лучше, особенно при использовании стабильных сортировок.

    • Поскольку отправка функций теперь выполняется быстро, пользователи могут передавать свои собственные предикаты в sort

  • создать предикат ieeetotalorder для всех, кто заботится о таких вещах, как произвольные стандарты и порядок подписанных нулей

    • также можно создать ieeemin и ieeemax для разрешения # 7866

isequal(0.0, -0.0) и isless(-0.0, 0.0) == isless(0.0, -0.0) , казалось бы, подразумевают, что нули должны сортироваться стабильно, что я даже не знаю, как это сделать эффективно: -. В отличие от ситуации с NaN, когда вы знаете, что все они попадут в конец массива, вы не знаете, где закончатся нули, поэтому вы не можете просто поместить их туда в качестве первого прохода.

Я не думаю, что это проблема, мы не гарантируем, что Any[0, 0.0] будет отсортировано стабильно.

Собственно, да. Мы используем нестабильные сортировки только для типов, для которых нестабильность не имеет значения.

Ах, понятно: не знаю, что мы тогда могли сделать.

Сумасшедшая идея: мы могли бы выполнить проход, чтобы проверить, содержит ли массив и -0,0, и 0,0, и если да, использовать стабильный алгоритм. Или просто откажитесь от обещания стабильности для -0,0 и 0,0.

Или просто откажитесь от обещания стабильности для -0,0 и 0,0.

Это означает, что для v::Vector{Float64} , sort(v) и sort(Any[v...]) не будут делать то же самое. Это может быть нормально, но я просто заявляю об этом прямо.

Я могу ошибаться здесь, но если вы выберете первый 0 в качестве первого поворота, не будет ли тогда быстрая сортировка стабильной?

Может быть? Вы можете аргументировать это, @simonbyrne? Я этого не вижу.

Ах нет, я понял, что неправильно. Однако может быть какой-нибудь умный способ сделать это.

Я предполагаю, что один из подходов - сначала просканировать массив, подсчитав количество отрицательных, нулевых и положительных значений, что точно определяет, куда им нужно идти. Затем вы можете снова просмотреть и переместить нули в нужную область.

Я не понимаю, почему -0.0 и 0.0 особенные. Несомненно, существует много определяемых пользователем типов данных, в которых == и === различаются, т.е. которые сравниваются одинаково, хотя имеют разные идентичности. Разве вышеупомянутое обсуждение стабильности сортировки не должно применяться и здесь? Разве это не было бы покрыто двумя подпрограммами, sort и stable_sort , где последним, возможно, придется использовать более медленный алгоритм? Или стабильная сортировка для чисел с плавающей запятой IEEE с понятием равенства IEEE при отсутствии nans часто встречается?

=== здесь действительно не присутствует, проблема в том, что == против isequal . На данный момент == и isequal различаются только для NaN и -0.0 - для всех типов без чисел с плавающей запятой они должны быть точно такими же и вам нужно только определить == . Стабильная сортировка номеров IEEE не является проблемой, поскольку IEEE определяет общий порядок, поэтому не имеет значения, стабильна ваша сортировка или нет. Но IEEE советует вам сортировать NaN по битовому шаблону, что быстро, но неудобно, поскольку NaN с установленным знаковым битом (сигнализация) сортируются раньше всех других значений, а NaN с выключенным знаковым битом (тихо) сортируются после всех других значений - имея Такое разделение NaN раздражает, а не то, что делает Matlab, он сортирует NaN до конца.

Настоящая проблема вот в чем. Вы хотите, чтобы isequal был совместим с isless в том смысле, что, когда определено isless(x,y) , тогда ровно одно из isless(x,y) , isless(y,x) или isequal(x,y) должно быть истинным, а когда isless(x,y) не определено, тогда isequal(x,y) должно быть ложным, а isless(y,x) также неопределенным. В то же время вы хотите, чтобы сортировка массива по умолчанию велась точно так же, как если бы вы стабильно отсортировали его с помощью функции сравнения isless . Вы уже можете выбрать нестабильную сортировку, запросив нестабильный алгоритм, например QuickSort . Для чисел с плавающей запятой мы оптимизируем, стабильно перемещая NaN в конец, а затем быстро сортируя остальные, что имеет тот же эффект, что и сортировка чисел с помощью isless но намного быстрее. Если мы сделаем isequal(-0.0, 0.0) тогда из-за первого ограничения у нас должно быть isless(-0.0, 0.0) false. Нам нужно либо найти способ их сортировки, как если бы они были стабильно отсортированы, по сравнению с isless , что означает, что нам нужно сохранить все нули в их исходном порядке по знаку, либо нам нужно отказаться о том, что sort по умолчанию выполняет стабильную сортировку - это означает, что знаки нулей и NaN располагаются в произвольном порядке в зависимости от деталей алгоритма нестабильной сортировки.

сигнализация против тишины - это первый бит мантиссы, а не бит знака

сигнализация против тишины - это первый бит мантиссы, а не бит знака

Ой! Спасибо за исправление. Как-то мне пришло в голову, что это знаковый бит.

это сообщение фиксации было неточным

изменения здесь не относятся к версии 0.6

Решение по сортировке: мы должны это изменить, и in следует использовать isequal 💥

Я должен был разместить это здесь (или, может быть, где-нибудь еще?) Вместо PR @JackDevine (извините).

Я думал об этом, а также о наших текущих планах для null / missing , и я заметил, что у нас есть интересное поведение для == которое IMO имеет те же проблемы как in как описано в OP:

julia> [NaN] == [NaN]
false

julia> Set(NaN) == Set(NaN)
true

julia> using Nulls

julia> [null] == [null]
null

julia> Set(null) == Set(null)
ERROR: MethodError: no method matching length(::Nulls.Null)
Closest candidates are:
  length(::SimpleVector) at essentials.jl:528
  length(::Base.MethodList) at reflection.jl:670
  length(::MethodTable) at reflection.jl:745
  ...
Stacktrace:
 [1] union!(::Set{Any}, ::Nulls.Null) at ./set.jl:126
 [2] Set(::Nulls.Null) at ./set.jl:19

Я бы сказал, что «два контейнера равны, если они содержат (проверено с помощью element in container ) одинаковые элементы». Я считаю, что было бы более последовательным, если бы == на контейнерах проверял isequal на элементах (что сделало бы четыре примера выше true ). Кто-нибудь знает, обсуждалась ли еще одна проблема?

(Для контекста я заинтересовался этим, потому что меня немного беспокоит то, как null «сбежал» из своего контейнера здесь, в [null] == [null] тогда как раньше ==(::Array, ::Array) --> Bool казался гарантированным).

Так что, похоже, это потенциально хорошая последовательная позиция:

  1. Сделайте isequal(missing, missing) истинным в дополнение к isequal(NaN, NaN) тогда как (missing == missing) === missing и (NaN == NaN) === false .
  2. Используйте isequal и isless для всего общего кода контейнера, как предлагает эта проблема.
  3. Определите равенство контейнеров в терминах isequal и isless - т.е. имеют [NaN] == [NaN] и [missing] == [missing] .

Это стратегия сдерживания «необычного» скалярного поведения, такого как NaN и предложенная трехзначная логика missing . Здесь есть некоторые опасности, поскольку если вы серьезно относитесь к трехзначной логике, missing может быть в любой непустой коллекции, а любое значение может быть в коллекции, содержащей missing ; но я думаю, что @andyferris , вероятно, прав: это безумие. Итак, общая политика будет следующей:

  1. Используйте == для скаляров, 3VL применяется к скалярам.
  2. Используйте isequal для контейнеров, 3VL не применяется к контейнерам.

Это становится немного странным, когда вы рассматриваете контейнеры как скаляры, т.е. когда выполняете missing == [1,2,3] . Это true , false или missing ?

Проблема Set(null) - отвлекающий маневр, выброс на самом деле NaN из-за определения eltype(::Float64) (как и для всех чисел). Set(Date()) завершается с той же ошибкой.

План @StefanKarpinski разумный и последовательный, но я не уверен, что это действительно хорошая идея. Позвольте мне подчеркнуть, что на практике мне все равно, как == ведет себя с массивами, но я не нахожу аргументы в пользу его изменения очень убедительными.

Во-первых, наличие == call isequal рекурсивно звучит очень странно и усложняет язык: если вы хотите isequal , почему бы не использовать его вместо == ? Мы должны просто предоставить оператор для isequal (Unicode и в идеале также ASCII). Обратите внимание, что, изменив это, вы исправите [NaN] != [NaN] , но получите взамен [0] != [-0.0] , что, возможно, звучит хуже, чем первое.

Во-вторых, мы выбрали политику распространения пропущенных значений повсюду на всякий случай. На практике это может быть довольно обременительно. Лично для моей работы мне было бы удобнее, если sum автоматически пропускает пропущенные значения (как это делают все основные проприетарные статистические программы), но это может скрыть ошибки в тех случаях, когда вы не ожидали бы пропущенных значений. присутствовать. Это та же проблема с равенством массивов: если отсутствует значение, вы в основном не знаете, равны ли массивы или нет.

В-третьих, я действительно не понимаю, почему == не возвращающее Bool было бы приемлемым для скаляров, но внезапно стало бы проблемой для контейнеров. Что в них такого особенного? Это звучит как наполовину реакция против 3VL, которая приведет только к несогласованности. Я бы лучше укусил пулю 3VL и применил ее везде. Я мог бы убедиться, если бы кто-нибудь раскрыл, почему с контейнерами следует обращаться определенным образом.

Я согласен с @nalimilan здесь:

Во-первых, рекурсивный вызов == isequal звучит очень странно и усложняет язык: если вы хотите isequal , почему бы не использовать его вместо == ?

Цитата @andyferris из # 24563

Я бы сказал, что контейнеры будут равны, если они содержат (т.е. element in container ) одинаковые элементы.

Сведение такого количества информации в одно предложение помогает прояснить ситуацию (спасибо Энди). Что я не понимаю в приведенном выше предложении, так это того, что подразумевается под «равным». Если # 24563 пройдет, то у нас будет:

Контейнеры являются isequal если они содержат (т.е. element in container ) одинаковые элементы.

Но у нас не было бы:

Контейнеры являются == если они содержат (т.е. element in container ) одинаковые элементы.

Как говорит Милан, если есть пропущенное значение, вы в основном не знаете, равны ли массивы или нет. Однако, если оба массива имеют пропущенное значение в одном и том же месте, тогда это isequal но не == .

Слегка связанный вопрос: должен ли иметь значение тип контейнера?

Например, если у меня есть Dict с последовательными целочисленными ключами и массив с такими же парами "ключ-значение", это будет isequal (или == )? (Я полагаю, не isequal ).

Для меня более интересным будет Set([1,2,3]) == OrderedSet([1,2,3]) (используя OrderedSet из DataStructures.jl)? Здесь следует ли учитывать порядок при сравнении контейнеров разных типов?

Другой способ сделать это - поместить missing в Base и удвоить 3VL везде.

Во-первых, наличие == call isequal рекурсивно звучит очень странно

Идея здесь в том, что == и isequal действительно должны быть одной и той же функцией и, следовательно, должны быть одинаковыми во всех возможных случаях. Однако добавление missing увеличивает необходимость различать == и isequal . Если требуется ([missing] == [missing]) === missing , я согласен, что мы должны поместить missing в Base.

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

function ==(l::Associative, r::Associative)
    ...
    if isa(l,ObjectIdDict) != isa(r,ObjectIdDict)
        return false

Мне довольно ясно, что у нас должна быть функция keycompare(dict) которая возвращает функцию сравнения ключей, которую использует dict ( isequal для Dict и === для ObjectIdDict ), и этот код должен проверять keycompare(l) != keycompare(r) .

Все это означает, что используемая функция сравнения определяет значение объекта, и поэтому == должен это учитывать. Исходя из этого, я думаю, что Set([NaN]) == Set([NaN]) оправдано, и то же самое, вероятно, применимо к missing .

Имеет смысл. Может быть, не так уж плохо сказать, что in использует == для массивов и кортежей, а isequal для dicts и set? Идея состоит в том, что вам нужно использовать isequal чтобы иметь возможность получить доступ к записи dict с ее ключом, но это не требуется для других коллекций. Использование == с массивами обеспечит -0.0 in [0] -> true и missing in [1] -> missing , которые кажутся мне двумя желательными свойствами. OTOH у нас будет NaN in [NaN] -> false , но я считаю это менее проблематичным, чем случай -0.0 (поскольку NaN не являются допустимыми числами, поэтому они в любом случае требуют особого обращения).

Удар,

Можно ли будет принять это решение до замораживания функции? Я не совсем уверен, что мы хотим здесь делать. Однако, если мы разработаем твердый план, я буду рад помочь.

Я вижу, что минимальная версия последовательной истории для контейнера == выглядит так:

  • Элементы AbstractSet s и ключи Associative s сравниваются с помощью isequal .
  • Элементы Tuple s, AbstractArray s и значения Associative s сравниваются с помощью == .
  • Другие типы, такие как ObjectIdDict могут при необходимости специализировать эти правила (я как бы вижу ObjectIdDict как нечто особенное ... Я не могу вспомнить многих случаев, когда было бы полезно отклониться от приведенного выше ).

(Мы также можем заявить, что ключи AbstractArray s (и Tuple s) сравниваются через isequal , потому что на самом деле нет разницы для целых чисел) .

Звучит неплохо, вы утверждаете, что мы заставляем in следовать приведенным выше правилам?

Извините, я забыл упомянуть in .

Да, в точном смысле моего вступительного заявления выше - версия с минимальными усилиями состоит в том, чтобы делать очень мало, а потом покрывать это удобной и несколько связной историей :)

Если говорить более подробно, люди, работающие с данными, по всей видимости, рассматривают missing как «любое одно из диапазона значений», так что предикат missing in [missing] может фактически рассматриваться как missing (т.е. какое-то неизвестное значение содержится в векторе, который содержит какое-то неизвестное значение? ответ - «возможно», это зависит от значений, о которых у вас нет информации). На самом деле, если вы доведете это прочтение значения missing до логического завершения, тогда missing in [1,2,3] будет missing , а 0 in [1,2,missing] будет missing , что кажется ... интересным ...

Извините, что я здесь философски воскликнул, но для меня выбор между in с использованием == или isequal становится скорее выбором того, является ли это проблемой "разработки программного обеспечения" контейнеров. , или "вопрос данных", который мы задаем о ценностях. С missing мы можем оказаться в ситуации, когда if a == b , if a < b и потенциально if a in B небезопасны около missing data (то, что мы считали безопасными, логические предикаты больше не будут такими, и нужно будет настроить Base и пользовательские пакеты; преимущества могут стоить усилий, поскольку с отсутствующими данными трудно справиться на многих других языках и фреймворках - узнаем).

Я думаю, ответ заключается в том, что иногда вы будете заниматься разработкой программного обеспечения, а иногда - наукой о данных, и в обоих случаях вы можете захотеть протестировать на сдерживание. Для == и < у нас есть isequal и isless для безопасного, консервативного вопроса разработки программного обеспечения. Для in , возможно, нам также понадобится функция партнера, например isin или contains , одна с использованием isequal а другая с использованием == в качестве теста ? (Можно сказать, что всем «предикатам», которые будут «заражены» (извините за отрицательный выбор слова) missing , потребуется «безопасная» версия, которая всегда возвращает Bool - если только так мы можем продолжать использовать if операторы :))

Обоснование того, что я написал в моем последнем посте, заключается в том, что проверка того, существует ли ключ, больше похожа на проблему программного обеспечения, где в getindex вы должны найти совпадение (или нет) и вернуть некоторые данные (или нет), и он может Не могу сказать, какой именно, но значения словарей и данных внутри массивов - это «пользовательские данные». Это выглядело как «разумная семантика по умолчанию», но я бы также предложил способ контролировать это по мере необходимости (поэтому я упоминаю о введении для этой цели isin или contains ).

Однако даже эта семантика по умолчанию является сложной, потому что иногда keys будет исходить из данных (скажем, операция groupby ), а иногда (часто) наборы являются «данными».

Учитывая это, мне интересно, может ли in всегда использовать == а новая функция contains всегда использовать isequal может быть самым простым путем вперед?

Это позволило бы нам использовать NaN и missing в качестве ключа при необходимости (например, haskey(A, k) = contains(k, keys(A)) будет работать и всегда возвращает Bool ) и по-прежнему позволять пользователям опрашивать " data "с помощью in .

Я не думаю, что нам нужны одновременно in и contains . И x in keys(dict) всегда должен использовать любую функцию сравнения ключей, которую использует dict.

Хорошо, поэтому я думаю, что текущий план состоит в том, чтобы in использовать isequal для Set s и ключи Associative , in использует == для массивов и кортежей, и мы должны при необходимости специализироваться для ObjectIdDict .

Если люди думают, что это разумно, я мог бы внести изменения и добавить немного в строку документации, которая описывает намерение in а также правила для людей, которые хотят создать новые методы для in .

Возвращаясь к этому, я склонен думать, что мы должны оставить все как есть. В настоящее время in имеет два типа поведения:

  1. Для контейнеров с явными представлениями о том, что они содержат (например, dicts и sets), контейнер определяет используемый тест на равенство.
  2. Все остальное использует == .

Это красиво и просто. Если мы не хотим изменять правило (2), чтобы использовать isequal вместо == , но мы хотим что-то изменить, тогда мы должны перейти к 3 видам поведения вместо 2. Я думаю, это было бы плохо; например, x in (f(y) for y in itr) должен дать тот же ответ, что и x in [f(y) for y in itr] .

Извините - тупой вопрос - что не контейнер но поддерживает in ? (Изменить: возможно, я немного неправильно это понял)

Была ли эта страница полезной?
0 / 5 - 0 рейтинги