Как отсортировать список строк a, bcd, ef и ghij в порядке убывания длины?

Пол Грэм спросил:

Как в вашем любимом языке программирования отсортировать список строк «a», «bcd», «ef» и «ghij» в порядке убывания длины?

Одним из предложенных решений было:

tag_negative_len(Str, NegLen-Str) :-
  atom_length(Str, StrLen),
  NegLen is -1*StrLen.

rem_tag(_-Val, Val).

sort_desc_len(StrLs, Sorted) :-
  maplist(tag_negative_len, StrLs, TaggedLs),
  keysort(TaggedLs, SortedTaggedLs),
  maplist(rem_tag, SortedTaggedLs, Sorted).

Я предполагаю, что приведенный выше код был написан для ISO Prolog, потому что он не использует особенности реализации. Пример использования:

?- sort_desc_len(["a", "bcd", "ef", "ghij"], L).
L = ["ghij", "bcd", "ef", "a"].

Я бы решил это аналогичным образом. Однако решение чрезвычайно многословно (по сравнению с одно- или двухстрочными строками других языков программирования) и загрязняет программу вспомогательными/вспомогательными предикатами. Есть ли лучший способ решить проблему в ISO Prolog?


person Flux    schedule 03.04.2020    source источник
comment
По моему не очень скромному мнению, на этом уровне концепция многословия только усиливает шум (см. также: игра в гольф с кодом) и может вдобавок ухудшить разборчивость. Когда речь идет о задаче, занимающей несколько сотен строк, это имеет больше смысла. Это не похоже на эффективность, которую можно измерить как количество операций, время (t) или энергию (Дж) даже на этом маленьком фрагменте кода. Ключевым моментом является то, что должна быть достаточно гибкая функция сортировки (на самом деле проблема библиотеки, а не проблема языка), которой можно гибко указать порядок сортировки (это проблема языка). Так что это хорошо.   -  person David Tonhofer    schedule 03.04.2020
comment
Это приводит к расширению: как и в функциональном программировании, выполните сортировку с использованием функции сравнения (возможно, определенной встроенной с помощью library(yall) и других), возвращающей -1,0,+1 для каждой пары элементов.   -  person David Tonhofer    schedule 03.04.2020
comment
Как ["z","a"] сортировать?   -  person false    schedule 03.04.2020
comment
@false Не имеет значения. Любой порядок в порядке. Имеет значение только длина строки.   -  person Flux    schedule 03.04.2020


Ответы (3)


sort_desc_len(L,S) :-
  findall(N-T,(member(T,L),atom_length(T,M),N is -M),LT),
  keysort(LT,ST),
  findall(T,member(_-T,ST),S).

то есть findall/3 реализует замыкания с помощью member/2. setof/3 позволит объединить первый вызов findall и keyort, но удалит дубликаты...

Обратите внимание, что atom_length/2 не может работать с процессором, совместимым с ISO. Строки в двойных кавычках — это списки кодов, а не атомов.

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

person CapelliC    schedule 03.04.2020
comment
В духе Теоремы о бесплатности Уодлера можем ли мы заключить (даже в отсутствие ввода), что findall/3 (или bagof/3) над member/2 на самом деле maplist/3? - person David Tonhofer; 03.04.2020
comment
@DavidTonhofer: я не думаю, что findall+member и maplist в целом сопоставимы, они выполняют очень разные задачи. Например, findall также может фильтровать элементы, в то время как maplist не может, но maplist может предоставить несколько решений... - person CapelliC; 03.04.2020
comment
Вы всегда можете установить для флага double_quotes значение atom, что соответствует стандарту ISO. К сожалению, некоторые системы Prolog поддерживают только настройку codes. - person Paulo Moura; 03.04.2020

В моем ответе в Твиттере на вопрос Пола Грэма используется библиотечный предикат Logtalk list::msort/3 и лямбда-выражение, вызывающее стандартный предикат ISO Prolog compare/3, и де фактический стандартный предикат length/2. Предполагая, что флаг double_quotes установлен на codes (наиболее переносимый параметр; фактически, единственный параметр, поддерживаемый некоторыми системами Prolog):

| ?- {types(loader)}.
yes

| ?- list::msort(
         [Order, List1, List2]>>(
             length(List1, N1), M1 is -N1,
             length(List2, N2), M2 is -N2,
             compare(Order, M1, M2)
         ),
         ["a", "bcd"",cd", "ef", "ghij"],
         Sorted
     ).
Sorted = [[98,99,100,34,44,99,100],[103,104,105,106],[101,102],[97]]
yes

При работе на поддерживаемых внутренних компиляторах Prolog, которые не предоставляют предикат length/2 в качестве встроенного предиката, вместо этого мы можем использовать:

| ?- {types(loader)}.
yes

| ?- list::msort(
         [Order, List1, List2]>>(
             list::length(List1, N1), M1 is -N1,
             list::length(List2, N2), M2 is -N2,
             compare(Order, M1, M2)
         ),
         ["a", "bcd"",cd", "ef", "ghij"],
         Sorted
     ).
Sorted = [[98,99,100,34,44,99,100],[103,104,105,106],[101,102],[97]]
yes

Как ни странно, это одно из самых переносимых решений, представленных на данный момент, работающее на 12 системах Prolog (т. е. на всех системах, поддерживаемых Logtalk).

Как заметил @CapelliC, реляционный язык всегда будет предоставлять более подробное решение по сравнению с функциональным языком.

person Paulo Moura    schedule 03.04.2020
comment
Аккуратный. Также устраняет проблему вспомогательных предикатов, плавающих в пространстве предикатов. - person David Tonhofer; 03.04.2020

Я просто думаю функционально:

По-видимому, предикатом для использования является predsort. Это на самом деле не ISO, а из library(list)

my_cmp(X,L,R) :- 
   atom_length(L,Llen),atom_length(R,Rlen),
   ((Llen < Rlen) -> X = '>' ;
    (Llen > Rlen) -> X = '<' ;
    X = '=').

sort_desc_len(L,S) :- predsort(my_cmp,L,S).

В зависимости от того, как вычисляется atom_length/2 (то есть в зависимости от того, нужно ли просто получить скрытое значение length, а не пройти по строке атома), это может быть довольно быстро.

:- begin_tests(sort_desc_len).
test(empty)      :- sort_desc_len([],[]).
test(nop)        :- sort_desc_len([aaa,bb,c],[aaa,bb,c]).
test(reverse)    :- sort_desc_len([a,bb,ccc],[ccc,bb,a]).
test(duplicates) :- sort_desc_len([a,bb,bb,ccc],[ccc,bb,a]).
test(stability)  :- sort_desc_len([i,j,k],[i,j,k]).
test(random)     :- sort_desc_len([a,xx,fg,hhh,jk],[hhh,xx,fg,jk,a]).
test(exercise)   :- sort_desc_len([a,bcd,ef,ghij],[ghij,bcd,ef,a]).
:- end_tests(sort_desc_len).

Итак, тесты I Herd U Liek...

?- run_tests(sort_desc_len).
% PL-Unit: sort_desc_len ....
ERROR: user://4:87:
        test stability: failed

ERROR: user://4:88:
        test random: failed

. done
% 2 tests failed
% 5 tests passed

К сожалению, predsort/3 удаляет дубликаты. Абсолютно должно быть predsort/4, указывающее, должны ли дубликаты исчезнуть или нет.

?- sort_desc_len([i,j,k],X).
X = [i].

?- sort_desc_len([a,xx,fg,hhh,jk],X).
X = [hhh, xx, a].

НЕУДАЧА.

Приложение

Менее шумно с использованием compare/3, как предложено в комментарии повторить.

Н.Б. compare/3 вызов с обменом левой и правой длин.

my_cmp(X,L,R) :- 
   atom_length(L,Llen),
   atom_length(R,Rlen),
   compare(X,Rlen,Llen).

sort_desc_len(L,S) :- predsort(my_cmp,L,S).
?- run_tests(sort_desc_len).
% PL-Unit: sort_desc_len ....
ERROR: user://1:20:
        test stability: failed

ERROR: user://1:21:
        test random: failed

. done
% 2 tests failed
% 5 tests passed
false.
person David Tonhofer    schedule 03.04.2020
comment
Если вы упростите my_cmp/3, чтобы он возвращал только ‹ или › (запасите целую строку!), вы также избегаете удаления дубликатов, поэтому лучше сравнивать с основными языками (кстати, в js вы можете написать L.sort((x,y)= ›y.length-x.length) трудно превзойти по короткости.) - person CapelliC; 03.04.2020
comment
Используйте compare/3 вместо ((Llen < Rlen) -> X = '>' ; (Llen > Rlen) -> X = '<' ; X = '='). - person repeat; 03.04.2020
comment
@CapelliC Вы имеете в виду вернуть '‹' (например) для атомов одинаковой длины? Есть ли гарантия, что алгоритм сортировки не будет сортировать вечно в этом случае (поскольку «aaa» ‹ «bbb» и «bbb» ‹ «aaa» оба могут быть истинными во время обработки). - person David Tonhofer; 03.04.2020
comment
@DavidTonhofer: раньше я использовал (Llen < Rlen -> X = '>' ; X = '<'). Угадай, работает... - person CapelliC; 03.04.2020
comment
@CapelliC Это симпатичный ниндзя. - person David Tonhofer; 03.04.2020